Skip to main content
A profile is a named runtime context for Task Manager: its own data directory, auth storage, HTTP port, and (for client profiles) its own remote api_url and api_key. Profiles give you machine-local isolation without mixing data, sessions, or credentials between setups.

Where profiles live

~/.taskmanager/profiles/<profile-name>/
  config.json     # the profile's own settings
  data/           # SQLite database and related files (server profiles only)
  auth/           # auth.json, recovery key, cli-api-keys.json (server profiles only)
A small default-profile pointer lives one level up, alongside the profiles directory:
~/.taskmanager/config.json     # { "default_profile": "main" }

Profile roles

Every profile has a role, set by the wizard mode you choose:
RoleCreated byWhat this machine does
serverhirotaskmanager --setup-serverRuns the API server and acts as its own local client. The CLI on the same machine uses http://127.0.0.1:<port> automatically.
clienthirotaskmanager --setup-clientTalks to a remote server only. Contains api_url and api_key; runs nothing locally.
You never set role by hand — the setup mode picks it. To convert a profile from one role to the other, delete it and re-run the appropriate wizard.

Configuration schema

Field matrix

FieldServer profileClient profile
rolerequired ("server")required ("client")
portrequired— (must not be set)
data_dirrequired
open_browseroptional (default true)
bind_addressoptional (default "127.0.0.1")
require_cli_api_keyoptional (default derived from bind_address)
api_url— (auto-derived as http://127.0.0.1:<port> for the local client)required (absolute URL)
api_keyoptional (used by the local client when require_cli_api_key: true)required
Auth directory is fixed. The web passphrase hash and CLI API key hashes always live at <profileRoot>/auth/ (i.e. ~/.taskmanager/profiles/<name>/auth/). There is no auth_dir field — the wizard does not prompt for it, and adding one to config.json by hand has no effect.

Validation rules (enforced when the config is loaded)

  1. role must be "server" or "client".
  2. Server profile: port and data_dir are required; api_url must not be present (it is auto-derived from port).
  3. Client profile: api_url (absolute URL) and api_key are required; port, data_dir, open_browser, bind_address, require_cli_api_key must not be present.
  4. Public exposure requires a key. If bind_address is non-loopback (anything other than 127.0.0.1, localhost, ::1), require_cli_api_key: false is rejected.
  5. Bootstrap warning (not an error): if require_cli_api_key === true on a server profile and no api_key is set, the loader warns that the local CLI will fail with auth_cli_key_required until you set one (or pass --api-key per command). External callers may still authenticate via cli-api-keys.json.
Validation failures throw CliError(invalid_config) (exit code 9). The error names the offending field and the profile path.

Example — same-machine server profile (no key)

{
  "role": "server",
  "port": 3001,
  "data_dir": "~/.taskmanager/profiles/main/data",
  "open_browser": true,
  "bind_address": "127.0.0.1"
}

Example — hardened single-host (loopback + forced key)

{
  "role": "server",
  "port": 3001,
  "data_dir": "~/.taskmanager/profiles/main/data",
  "bind_address": "127.0.0.1",
  "require_cli_api_key": true,
  "api_key": "tmk-a3f8c1...local CLI's copy of a key minted via server api-key generate"
}

Example — public bind on the LAN (advanced, hand-edited)

{
  "role": "server",
  "port": 3001,
  "data_dir": "~/.taskmanager/profiles/main/data",
  "bind_address": "0.0.0.0",
  "require_cli_api_key": true,
  "api_key": "tmk-a3f8c1...local CLI's copy of a key minted via server api-key generate"
}
See Advanced: exposing the API on a network interface for when to use this and what to set together.

Example — client profile (talks to a remote server)

{
  "role": "client",
  "api_url": "https://tasks.example.com",
  "api_key": "tmk-a3f8c1...paste full key here"
}

Default-profile pointer

~/.taskmanager/config.json:
{ "default_profile": "main" }
Both --setup-server and --setup-client offer to write this pointer at the end of the wizard. Change it later with:
hirotaskmanager profile use <name>
profile use errors if <name> doesn’t exist on disk.

How the CLI picks a profile

Resolution order, per command:
  1. --profile <name> on the command line (highest priority).
  2. default_profile in ~/.taskmanager/config.json.
  3. If neither is set and exactly one profile exists on disk, use it implicitly.
  4. Otherwise the CLI errors with the list of available profiles and a hint to run hirotaskmanager profile use <name> (or pass --profile).

How a profile is created

Use the wizards in hirotaskmanager. Each one writes the profile’s config.json, optionally mints a CLI API key, and optionally sets the default-profile pointer.
ModeUse it whenWizard flag
ServerThis machine runs the server (works for both same-machine and VPS installs).hirotaskmanager --setup-server
ClientThis machine only talks to a remote server.hirotaskmanager --setup-client
Plain hirotaskmanager with no configured profile asks “server or client?” once, then routes to the right flow. To re-run setup for an existing profile (change port, paths, etc.), use:
hirotaskmanager --setup --profile <name>
Re-running pre-fills your existing answers and refuses role changes — delete the profile and re-setup to convert.

Updating a profile

  • Interactive: hirotaskmanager --setup --profile <name>.
  • Manual: edit ~/.taskmanager/profiles/<name>/config.json carefully (keep valid JSON). Back up the file first. Validation errors will surface on the next CLI invocation.

Advanced: exposing the API on a network interface

bind_address is treated as an advanced, config-file-only setting. The setup wizard never asks for it: new profiles always default to 127.0.0.1 (loopback), and re-running the wizard preserves whatever value is already in config.json. When loopback is the right answer:
  • Same machine. The CLI on this machine talks to http://127.0.0.1:<port> automatically.
  • Remote access via reverse proxy. Put Caddy, nginx, or similar in front of the loopback API and terminate TLS there. The API stays unreachable from the network directly.
When you want the API socket itself reachable from the network (LAN, Tailscale, etc.), edit config.json by hand and set both fields together:
{
  "bind_address": "0.0.0.0",
  "require_cli_api_key": true
}
The validator enforces this pairing: any non-loopback bind_address (anything other than 127.0.0.1, localhost, ::1) must also have require_cli_api_key: true, set explicitly. Setting bind_address to 0.0.0.0 without require_cli_api_key, or with require_cli_api_key: false, is rejected with invalid_config (exit code 9) on the next CLI invocation. Mint a CLI API key for any caller that will reach the public socket:
hirotaskmanager server api-key generate --profile <name>
Plain HTTP on a non-loopback host sends traffic in cleartext; prefer a reverse proxy with TLS for any access beyond a trusted local network.

Hiro developers only

For Hiro developers working on Task Manager itself, --dev and --profile are independent:
hirotm server start --dev --profile dev
hirotm server start --dev --profile staging
hirotm server start --profile dev
  • --dev changes runtime behavior, including the default port (3002) and dev CORS.
  • --profile selects the profile directory for config, auth, and data.
  • The dev profile is not magic. It is just a profile name.
  • All profiles, including dev, store data under ~/.taskmanager/profiles/<name>/data/ unless you override the path.
See Advanced setup → For developers for a worked dual-profile example.