@prohost/cli
v0.16.21
Published
Run your own AI agent as a ProhostAI teammate, and stream your account's webhooks to your laptop.
Maintainers
Readme
@prohost/cli
The ProhostAI command-line tool. Four things it does:
prohost host— add this computer to your workspace once, and it works for every AI employee you place on it: Claude Code or Codex, on the AI logins signed in here, several things at once, plus the terminal sessions you open on it. See Computers.prohost agent— run your own local agent as a ProhostAI teammate. Someone @-mentions it in the app, it runs on your machine, and its answer is posted back into the conversation.prohost listen— stream your account's webhooks to a local HTTP endpoint while you develop against them (likestripe listen).prohost usage— show your workspace how much Claude Code and Codex capacity every login on this machine has left, and how many tokens each one used, without pairing an agent.
You need a ProhostAI account and Node.js 18 or newer. Nothing to install —
npx fetches it on demand:
npx @prohost/cli --help # all commands and flags
npx @prohost/cli --version # the version you're runningQuick start
Pair once, then leave the agent running:
# 1. In ProhostAI: AI Employees → Computers → Pair a computer. It shows this
# command with your code filled in. Codes are single-use and expire 15
# minutes after they're generated.
npx @prohost/cli agent pair --code <pairing-code>
# 2. Point it at whatever agent you already use.
npx @prohost/cli agent run --exec 'claude -p'
# 3. Optional, but you probably want it: keep it running across reboots.
npx @prohost/cli agent install-daemon --exec 'claude -p'That's it — @-mention the agent in ProhostAI and it answers from your machine. The rest of this page is detail on each step.
Computers: prohost host
A computer is added to a workspace once and then serves every AI employee placed on it — one background service, one connection, many employees, each running Claude Code or Codex on the AI logins signed in on this machine. It also hosts the terminal sessions people open on it from ProhostAI.
npx @prohost/cli host pair # prints a code; enter it in ProhostAI → AI Employees → Computers
npx @prohost/cli host install # keep it running in the background
npx @prohost/cli host status # what it is doingThen pick the computer under Where it works when you hire (or edit) an AI
employee. prohost agent run keeps working exactly as before for anything you
paired that way.
Commands
| Command | What it does |
| --- | --- |
| host pair [--name <n>] [--expect-account <id>] | Device-code pairing: prints a code and the page to enter it on, waits for approval, stores the computer credential in $PROHOST_HOME/host/computer.json (0600). |
| host pair --token <redeem_token> | Redeems a one-time token from the signed-in desktop app instead (--token - reads it from stdin; $PROHOST_REDEEM_TOKEN also works). The token is never printed. |
| host run [--machine <n>] [--timeout <s>] [--idle-timeout <s>] [--workload coding\|chat] | The service, in the foreground. |
| host install [--label <id>] [--dedicated --service-user <name> [--service-home <dir>] [--claude-token-file <path>]] / host uninstall [--label <id>] | The background service for this OS (below). Safe to re-run: nothing that is already right is touched. |
| host unpair | Remove this computer from its workspace (DELETE /v1/computers/{id} with its own key), then delete computer.json. Exits 0 only when the server removed it, or had already. In a home that handed its computer to a dedicated service it refuses and names the service. |
| sudo prohost host unpair --label <id> [--service-user <name>] [--from-home <dir>] | Remove a dedicated service's computer: as root / an administrator, with the key in the service's own home; then uninstall that service (stopped, its definition removed) and delete the key there and the hand-off note in the home it came from. Nothing changes when the server refuses. The label must be one of ours with an installed definition; the service's home and account come from that definition only (the home must be the account's own folder, no link), and a note that disagrees with it is refused. --dedicated without --label takes the label from this home's note, and then acts only when that service runs the note's computer. With $PROHOST_UNPAIR_RESULT_FILE set to an absolute path it writes what happened there as JSON — {version: 1, outcome, label, computer_id, revoked, cleanup_complete, message} — on every exit: revoked (the exit code is 0 exactly then) and cleanup_complete (definition removed, service seen stopped, its key and the note gone). |
| host status [--json] [--label <id>] | Online, paused, capacity, what is running, AI logins, hardware, recent events. Always exits 0. |
| host logins [--json] | The AI logins found on this machine, with their plan and (locally only) email. |
| host logins add --runtime claude_code\|codex [--label <l>] | A new AI login in its own config directory (~/.prohost-accounts), signed in right here (claude auth login / codex login). |
| host pause / host resume | Take no new work and open no new terminal sessions / take them again. Work already going finishes. |
| host migrate [--from <home>]… [--home <dir,…>] [--keep-services] [--dry-run] [--json] | Adopt existing agent homes and the desktop app's terminal-hosting home, and become a computer through their agent keys (below). |
Windows elevated unpair reports results through its exit code, without an
administrator writing a result file into a user-controlled folder. New Desktop
requests PROHOST_UNPAIR_RESULT_EXIT=2: 22 confirms removed and cleaned up,
20 means removed with cleanup pending, and 21 confirms cleanup without server
revocation. Legacy mode 1 keeps 0/20/21; commands without a mode keep ordinary
0/1. New Desktop treats an older CLI's ordinary exit 0 as unconfirmed cleanup.
--expect-account works as it does for agent pair: a computer approved in any
other workspace is refused and nothing is stored. $PROHOST_PAIR_RESULT_FILE
gets {"version": 1, "outcome", "computer_id", "account": {"id", "name"}, "message"}
(outcomes: paired, expired, token_rejected, workspace_unconfirmed,
declined, rate_limited, unreachable, invalid_arguments, failed), written
0600 and atomically, never holding the code, token or key.
Once approved, pairing prints the workspace it was approved in. In a terminal,
unless --expect-account already vouched for it, it asks "Add this computer to
? [y/N]" before saving anything; no (or a workspace that doesn't match
--expect-account) revokes the new key on the server and saves nothing. A
program (no terminal) relies on --expect-account.
How a run works on a computer
- ProhostAI sends
agent.run_requestedon the computer's stream (signed with the pairing's secret; an unsigned frame is refused and never run). - The run waits its turn (below). When a slot is free and its AI login has
room, the computer claims it (
POST /v1/computers/{id}/runs/{run_id}/claim). Exactly one computer ever gets a claim; a409means another computer took it, and it is skipped. A409 conversation_busymeans an earlier turn of the same employee's conversation is still open: the run stays queued (its slot goes to other work) and is claimed when ProhostAI sends its frame again, or after a backoff of 30 s doubling to at most 2 min. - The claim returns the run, a short-lived run token and the employee's
skills. The skills are written into the employee's workspace —
.claude/skills/<key>/…for Claude Code;.codex/skills/<key>/…plus a list inAGENTS.mdfor Codex — and skills no longer granted are removed. - The run executes exactly like an
agent runrun (same prompt, sessions, tool events, reply, completion) but as the employee, with the run token: the heartbeat, tool events, reply, its MCP config (a file of its own, removed afterwards) and its completion all carry that token, never the computer's key. The completion adds the run'speak_mem_mb, theloginit finished on (runtime,account_key), itssteps(each tool's name and whether it failed, plus aBash/Taskcall's own one-line description, at most 40; never arguments, output or file contents) and, if it moved,login_moved. - A run claimed but never reported (the service stopped mid-run) is reported failed on the next start, with its own token — it is never run again.
Employees on a computer skip permission prompts (Claude
--dangerously-skip-permissions, Codex --yolo); message approval rules still
apply on the server. Each employee has its own home under
$PROHOST_HOME/host/employees/<agent_id>/ (its sessions, workspace and
extra-mcp.json if you add one).
Checkouts, a GitHub token and secrets held on the computer
A work order may ask the computer for more than a run
(host_capabilities in the claim's payload, set by ProhostAI for employees
that need it, e.g. the support agent):
{ "repos": ["backend-service", "mobile"], "github_token": true,
"secrets": ["SENTRY_AUTH_TOKEN", "DB_HOST", "DB_USER", "DB_PASS", "DB_DB"], "pr_gate": true }The work order only names things; the values stay on the computer and go to
that run's agent command alone (never the host's own environment, the sign-in
checks, a connected team's thread, or a log). Without host_capabilities, a
run is unchanged.
repos: the host keeps a clone of each (github.com/<owner>/<name>, ownerProhostAIunlessgithub_ownersays otherwise) underrepos_dir, fetches it before every run that names it, and gives the conversation its own worktree, put back onorigin/mainbefore each run (detached; uncommitted and ignored files removed; branches it made are kept). They are at$PROHOST_RUN_REPOSand$REPOS_DIR. A repository that can't be fetched is used as last fetched; one that can't be cloned is left out.github_token: a GitHub App installation token minted on the computer for the run, scoped torepos(never unscoped: with no usable repository there is no token), asGH_TOKEN/GITHUB_TOKENand as git'shttp.extraheaderfor github.com (throughGIT_CONFIG_*, never a URL or an argument). It lasts about an hour. Checkouts are reset once per claimed run; a run that moves to another login after a usage limit keeps the first attempt's work.pr_gate: thegh pr creategate (prohost_agents/platform/pr_create_gate.shand.py), read frombackend-service's fetchedorigin/maininto the run's own folder and registered as aPreToolUsehook with--settings(withdisableAllHooks: falseandonFailure: "block"; run by/bin/bash, and any exit but pass or refuse refuses). It needs Claude Code 2.1.295+ andpython3. When it is asked for and wouldn't be enforced (a Codex run, a gate missing from main, an older or unreadableclaude --version, managed settings withdisableAllHooksorallowManagedHooksOnlyin effect frommanaged-settings.jsonand itsmanaged-settings.d/*.jsondrop-ins (merged in name order, hidden files skipped) or macOS managed preferencescom.anthropic.claudecode(read withplutil), or any of those present but unreadable), the GitHub token is withheld (so is any gated run on Windows: the hook needs/bin/bash). The gate is fast feedback, not a sandbox: the agent runs as this computer's user and could rewrite it. Server-managed policy (a claude.ai organization's) can't be read from here: don't put a gated employee on a login whose organization turns hooks off.secrets: each name this computer holds for runs. Others (and a value with a NUL in it) are skipped and their names logged.
A support run (payload.support: mode case/mention/heartbeat, prompt_repo,
prompt_files, env) is required, not best-effort: the agent starts in the
fresh checkout of prompt_repo (instead of the employee's workspace, which is
added with --add-dir so its skills still load; a checkout that can't be
prepared fails the run), the
listed files are read from its origin/main and appended to Claude's system
prompt (--append-system-prompt-file, each followed by its source path, as the
fleet did), and env is set for the run. It never sets one of the computer's
own names (PROHOST_HOME, PATH, …) or one the computer holds or was asked
for (DB_HOST comes from the computer). A missing checkout or prompt, a Codex
run, or a support run without host_capabilities is reported failed.
Set up on the computer (as the user the service runs as):
$PROHOST_HOME/host/secrets.env, mode 0600,NAME=valuelines: the variables runs may be given, plus the App credential the host mints tokens with (GITHUB_APP_ID,GITHUB_APP_INSTALLATION_ID, andGITHUB_APP_PRIVATE_KEYorGITHUB_APP_PEM_PATH). A file others can read is ignored. Never handed to a run: the App credential,DOPPLER_*, and process plumbing (PATH,HOME,NODE_*,GIT_*,PROHOST_*,CLAUDE_*, …).Optionally
$PROHOST_HOME/host/capabilities.json(nothing secret in it):{ "secrets_file": "/abs/path/secrets.env", "repos_dir": "/abs/path/repos", "github_owner": "ProhostAI", "doppler": { "project": "agents", "config": "prd", "bin": "/opt/homebrew/bin/doppler", "allow": ["SENTRY_AUTH_TOKEN", "GRAFANA_VIEWER_TOKEN"] } }With
doppler,doppler secrets downloadis read (every 5 minutes at most), authenticated byDOPPLER_TOKENfrom the secrets file or the CLI's own login; only itsallownames may go to runs, and the file wins on a name both have.
Both are read at each run, so a change applies without a restart.
Run order
Someone waiting (a mention, a DM, a customer's message) goes first; then the employees take turns (the one with the fewest runs going, then the one that started longest ago); then oldest first. Runs in one conversation always take turns. Terminal sessions never wait — they count against capacity, so they shrink what the employees get. Each employee's Up to N caps its own runs, and Keep one ready keeps a slot free for it while it has nothing running.
Capacity and holds
Capacity is how many things the computer works on at once. It is set in
ProhostAI; until it is, the recommendation is used: memory after a reserve of
max(8 GB, 25 %), divided by ~1.5 GB per chat run or ~4 GB per coding run
(--workload), at most half the cores, between 1 and 16.
New work is held (work going is never stopped) while the computer is paused
(host pause, or in ProhostAI), under critical memory pressure, or on battery —
each hold can be switched off in ProhostAI. Holds are reported as they start and
end. The service reports every 30 seconds and on any change: CPU, memory, memory
pressure (macOS kern.memorystatus_vm_pressure_level; Linux
/proc/pressure/memory; Windows performance counters through typeperf),
battery, slots, AI logins and what is running with each run's peak memory
(the agent process and everything it started, sampled every 5 s).
AI logins and sharing between them
A login is a Claude Code or Codex sign-in: ~/.claude, ~/.codex, and every
account in ~/.prohost-accounts (prohost agent account add). The plan comes
from claude auth status --json (subscriptionType, e.g. "Claude Max") or
Codex's rate-limit read ("ChatGPT Pro"). The email in claude auth status
is shown by host logins on this machine only; it is never sent.
A conversation sticks to the login holding its session. New conversations go to
the login with the most room. When a login hits its usage limit it rests
until its reset, and ProhostAI is told (login_resting). Then:
- Sharing on (per computer, per kind, in ProhostAI): the conversation's
session file is copied to another login of the same kind — Claude Code's
projects/<project>/<session>.jsonlunder the otherCLAUDE_CONFIG_DIR, or Codex's rollout under the otherCODEX_HOME(a WSLCODEX_HOMEgiven aswsl://<distro>/<path>is reached through\\wsl.localhost\<distro>\…) — and the run resumes there, told it was interrupted and to check what is already done (login_moved). Claude Code and Codex are never mixed. - Sharing off, or no other login has room: a run going is reported failed with the limit and its reset time, so ProhostAI runs it again after the reset; runs not started yet wait for the earliest reset.
The background service
| | host install | host install --dedicated --service-user <name> |
| --- | --- | --- |
| macOS | LaunchAgent <label> (while you are logged in) | LaunchDaemon /Library/LaunchDaemons/<label>.plist with UserName |
| Linux | systemd user unit (loginctl enable-linger to outlive your session) | system unit in /etc/systemd/system with User= |
| Windows | Task Scheduler task at logon | the same task at boot, running as the service user (S4U: no password stored) |
The label is ai.prohost.host for ~/.prohost (unit prohost-host.service,
task \ProhostAI\Host) and one derived from $PROHOST_HOME for any other home,
so two homes never replace each other's service; --label sets it (the desktop
app passes ai.prohost.desktop.host / ai.prohost.desktop.dev.host).
Dedicated mode keeps working when no one is logged in; install it as root / from an administrator prompt. It:
- creates the service account if it doesn't exist (macOS: a hidden user, ids 401–499; Linux: a system user; on Windows the account must already exist — creating one there means choosing a password policy);
- uses that account's
~/.prohost(--service-homesets another), copies yourcomputer.jsoninto it if it has none (pair as yourself, then install; under sudo "yours" is$SUDO_USER's~/.prohost, or--from-home <dir>), and installs--claude-token-file(aclaude setup-tokentoken) there for every Claude run (adopted Claude agents too) and Claude login check asCLAUDE_CODE_OAUTH_TOKEN(never to Codex, never on a command line, so not to terminal sessions either: they would carry it in tmux's arguments) — read once, never printed, re-read each time so installing a new one needs no restart. Everything in the account's home is written as the account (sudo -u/runuser), never by root through a path the account controls (a--service-homethe account can't create is made by root only under a root-owned folder, then handed over). On Windows the ProgramData folders are made in one step or, if they were there, kept only when Administrators / SYSTEM own them; each is taken over top-down (owner Administrators,/reset, exactly our grants), checked again afterwards (owner, no junction, no other principal), and the token is always a new file (wx) inC:\ProgramData\Prohost\secrets\<label>(the account may only read it), and the account's service home isC:\ProgramData\Prohost\homes\<account>: made in one step with administrators-only access, given the credential, and only then opened to the account (an existing one is never written into). If your own (personal) service runs on the same computer key, install says so and how to remove it; - hosts terminal sessions only with
--remote-terminal(--no-remote-terminalor nothing: none) — decided in the service's definition, shown asremote_terminalinhost status --jsonand declared as theremote_terminalcapability only when on; - logs where the service account can't redirect it: the journal on Linux,
/Library/Logs/Prohost/<label>.log(in a root-owned folder) on macOS,C:\ProgramData\Prohost\Logs(ACL: SYSTEM, administrators, the service account) on Windows; - publishes its state for anyone logged in on the computer, without login
emails, in a root-owned
/Library/Application Support/Prohost/hosts/<label>,/var/lib/prohost/hosts/<label>orC:\ProgramData\Prohost\hosts\<label>: the service owns onlystate.json,activity.jsonandevents.jsonthere, andcontrol.jsonis the pause switch anyone on the computer may flip.host status --label <id>andhost pause|resume --label <id>run as the logged-in user address it there (unlabelled, they act on their own home only), andsudo prohost host uninstallfinds the service by it (install prints the exact command with its--label).
On Windows it is a boot-start task rather than a Windows service, because Node cannot answer the Service Control Manager without a native wrapper.
Every variant runs <executable> <cli> host run with the PATH it was installed
from: Node, or — under the desktop app — the app's Electron binary, with
ELECTRON_RUN_AS_NODE=1 (and PROHOST_CLI_RUN=1) kept in the service's
environment so it runs the CLI rather than opening the app. Personal services
log to $PROHOST_HOME/host/host.log. Re-running host install changes nothing
that is already right (the desktop app runs it after every update); a changed
definition is loaded after the old service drains. Only one host run
runs per PROHOST_HOME (host/service.lock); a second one refuses to start.
The systemd unit uses KillMode=mixed, so a stop reaches the host first and its
runs can finish. Re-installing on macOS waits for the previous service to finish
draining (up to its 10-minute stop timeout) before loading the new one. A dedicated install hands the service user's PROHOST_HOME to
that user (chown); on Windows it checks for an elevated prompt first. One key, one
holder: once the service home has the computer, the home it was taken from (--from-home,
or $SUDO_USER's) keeps only a note — {handed_to: {label, service_user, service_home, at},
computer_id, name, account, code_host_home?}, no key — written as that home's owner.
host status there reports handed_to; unpair, pair and a personal install refuse and
name the service. The note is part of the takeover: if it can't be written, the install is
undone (the service uninstalled, the key it copied removed) and fails, so it never leaves a second
copy of the key behind. The copy is written whole or not at all (a fresh file beside it, linked or
renamed into place), and the undo deletes only the files this install wrote, and only while they are
still those files (same inode or file index, same bytes): a copy it found there, or one replaced since,
is left in place and named. One exception: when the service home already held the key before this
install (an older setup that copied it), that copy stays, so the key is in both homes until the
note can be written — never run prohost host unpair from the original home meanwhile; sudo
prohost host uninstall --label <id> then removes the service. A failed install leaves the key
where it was. sudo prohost host uninstall --label <id> gives the key back to
that home (so prohost host install runs it there again); sudo prohost host unpair --label <id>
removes the computer. Both read the service's home from its installed definition (root's file),
never from the note, which the home's owner can edit. Updates drain:
when a newer CLI is installed over the running one (or the service is told to
stop), it takes no new work, lets the runs going finish, then exits (code 75
after an update) for the service manager to start the new one. A second
Ctrl-C (or signal) stops the runs going too.
Migrating an existing setup: host migrate
host migrate brings agents you already run under the host service without
pairing anything again. When this home has no computer yet, it becomes one
through an adopted home's agent key (POST /v1/computers/pairing/adopt): the
pairing's creator owns it, no code to enter. --from <home> (repeatable) adopts
only those homes — nothing is discovered, nothing is stopped, the homes are only
read — which is how the desktop app hands over its own; exit 0 means adopted, or
nothing to adopt.
- Agent homes — every home a launchd agent
ai.prohost.agent.*runs (its--exec,--workdir,--concurrency,--repos, timeouts and--safe-toolsare read from the plist),$PROHOST_HOME, and each--home(the desktop app passes the homes of the agents it supervises). Their conversation sessions are copied to the employee's host home. Until ProhostAI places that employee on this computer,host runkeeps serving it with its own credential, asagent rundid; once it is placed, the computer serves it and the old connection is stopped. - The terminal-hosting home — a
--host-onlyhome (including the desktop app'sai.prohost.desktop[.dev].code-hostagent), or one with Code-thread hosting on. The service hosts terminal sessions from it, so open terminals, opened folders and the remote switches carry over. - Old launchd agents it adopted are stopped and removed (
--keep-serviceskeeps them; then stop them yourself, or each employee is answered twice).
It cannot adopt: a systemd unit or Windows task written by hand from
agent install-daemon (pass --home and stop the unit yourself); an agent the
desktop app supervises (the app stops it); an --exec that is neither Claude
Code nor Codex (it keeps running on its own credential and is never served as
the computer); a home whose agent.json is missing, unreadable or revoked; the
sessions of a home that ran in a custom --workdir, once the computer serves it
(Claude Code files sessions by directory, and the host never runs an employee in
a checkout — those conversations start fresh; until then its own connection
keeps running in that --workdir); prohost listen and prohost usage
daemons. Running host migrate again keeps what it adopted before, including
settings read from plists it has since removed. It refuses while the host
service is running (the service owns the run ledger): stop it, migrate, then
start it again. --dry-run shows what it would do; --json prints the report.
For the desktop app
host status --json(always exits 0) →{paired, computer: {id, name, account: {id, name}}, online, paused, slots: {total, busy, held, held_reason}, running: [{run_id, agent: {id, name, avatar_url}, title, started_at, kind}], logins: [{account_key, label, runtime, subscription_type, plan_label, email_local, auth_state, windows}], capacity, recommended_capacity, hardware: {memory_gb, cpu_cores, os, chip}, events: [{id, kind, at, …}], service: {running, pid, adopted, dedicated, activity_file?}, version}.onlineis false when the service has not written its state for 2 minutes.eventsare the last 24 h of what the service sent (login_resting,login_moved,hold_started,hold_ended, with the/eventsfields pluscomputer_id/computer_name), newest last, each with a stableid.servicesays whether a service runs for this home (host/service.lock), how many homes it adopted, and — with--label <id>of a dedicated service — that it is the dedicated service (activity_fileis then its shared copy).remote_terminalsays whether remote terminal sessions are on.handed_to({label, service_user, service_home}) is there when this home gave its computer to a dedicated service: it then readspaired: truewith that computer andonline: falsehere — ask the service with--label.host logins --json→{logins: [...same shape as status...]}.$PROHOST_HOME/host/activity.json→{v: 1, busy, running: [...as in status...], updatedAt}.$PROHOST_HOME/host/state.jsonis the service's own state (0600); readstatus --jsonrather than this file.host pause/resumewrite$PROHOST_HOME/host/control.json(and a dedicated service's sharedcontrol.json); the running service picks it up within 2 seconds. Paused, it also starts no new terminal session (the start is reported failed, "this computer is paused") and lets no new viewer dial in; terminals already open, their replies and approvals carry on.prohost --helpnames what this build can do:ELECTRON_RUN_AS_NODE(services written for the desktop app),--claude-token-file,host unpair.- Terminal sessions authenticate to
/v1/code-hosts/controlwith the computer key; a server from before computer keys (not_paired) is asked again with the adopted terminal-hosting home's agent key. A revoked or invalid key never falls back.
Bring your own agent
Any agent that reads a prompt on stdin and writes its answer to stdout works — Claude Code, Codex CLI, goose, or a shell script you wrote yourself. The CLI doesn't know or care which one it is.
1. Get a pairing code
In ProhostAI: AI Employees → Computers → Pair a computer. You'll get a one-time code, already filled into the command it shows you. It is single-use and expires 15 minutes after it's generated.
2. Pair
npx @prohost/cli agent pair --code <pairing-code>This exchanges the code for a scoped API key and stores it in
~/.prohost/agent.json (chmod 600). The key is never printed in full — only a
masked prefix — and the file should never be committed or copied between
machines; re-pair instead.
That gives you a stream-only pairing: events arrive over the websocket and nothing is ever POSTed to you, which is what you want on a laptop with no public HTTPS address. Events are still signed, and the CLI still verifies every one.
Pass --webhook-url https://… only if something else of yours also needs the
events pushed over HTTPS. It changes where events are delivered; it is not
required to receive runs.
You can also pass the code as $PROHOST_PAIRING_CODE.
Pairing only with the workspace you expect
A pairing code doesn't say which workspace it belongs to, and redeeming one makes this computer that workspace's runtime. If you know which workspace you mean to pair with, say so, and a code for any other workspace is refused:
npx @prohost/cli agent pair --code <pairing-code> --expect-account <workspace-id><workspace-id> is the workspace's id (a UUID). You can pass it as
$PROHOST_EXPECT_ACCOUNT instead. The ProhostAI desktop app does this for you
with the workspace it is signed in to.
- A code for another workspace is refused and left unspent: it stays valid for whoever it belongs to, and if this computer was already paired, that pairing keeps working.
- A refusal is never retried without the check. The server answers one generic 404 whether the code is invalid, expired, already used, or simply for a different workspace — or if it is a server that doesn't support this yet — so the CLI tells you all of those and stops.
- A value that isn't a workspace id stops the pairing before the code is spent.
- On success the CLI prints the workspace it paired in and records it in
agent.jsonasprohost_account({ "id", "name" }). Pairing without--expect-accountrecords it too, when the server reports it.
--expect-account is unrelated to --account, which picks the Claude Code or
Codex login an agent runs on.
Needs CLI 0.11.0 or later. An older CLI ignores a flag it doesn't know and
pairs without the check, so a script that depends on it should confirm the
version first (prohost --version), or that prohost --help mentions
--expect-account.
If a program runs agent pair for you
What agent pair prints is written for a person. It includes text the server
chose — the agent's name, the workspace's name — and those can be anything,
including a sentence this CLI prints itself. So a program must not decide what
happened by searching the output. Set $PROHOST_PAIR_RESULT_FILE to an
absolute path and read the JSON the CLI writes there instead:
{ "version": 1, "outcome": "paired", "revoked_previous_credential": false, "message": "Paired." }| outcome | What happened |
| --- | --- |
| paired | The code was redeemed and the credential stored. |
| code_rejected | Pairing without --expect-account got a 404: the code is invalid, expired or already used. |
| bound_rejected | Pairing with --expect-account got a 404: any of the above, or the code is for another workspace, or the server has no workspace-bound pairing. Not retried without the check. |
| workspace_unconfirmed | Pairing with --expect-account succeeded, but the answer did not name the expected workspace. The code is spent; nothing was saved. |
| rate_limited | Too many attempts from this IP. |
| unreachable | The request got no answer. |
| no_subscription | Redeemed, but no event subscription was provisioned. The code is spent; nothing was saved. |
| invalid_arguments | Stopped before the code was spent: a missing code, a flag with no value, an expectation that isn't a workspace id (or is set but empty), a refused URL, an unknown --account. |
| failed | Anything else. |
outcomeandrevoked_previous_credentialare set by what the CLI did, never worked out from any text.revoked_previous_credentialistrueexactly when this run found the credential stored for the same agent already revoked by the redeem and removed it from disk.messageis what the CLI said about the outcome, for display only. It can contain server-chosen text; don't parse it. Onpairedit is the fixed stringPaired.- The file never contains the pairing code, the API key (not even its masked
prefix), the signing secret, or anything from
agent.json. - It is written on every exit
agent paircontrols, atomically (a temp file beside it, then a rename) andchmod 600, replacing whatever was at the path. The directory must already exist. - It never changes what pairing does. A path that isn't absolute, or one that can't be written, is a one-line warning on stderr and nothing more. Without the variable, nothing is written and the output is unchanged.
The names live in src/agent/pair_result.ts (PAIR_RESULT_FILE_ENV,
PAIR_RESULT_VERSION, PAIR_OUTCOMES).
3. Run
npx @prohost/cli agent run --exec 'claude -p'Leave it running. Each time ProhostAI asks this agent for work — an @-mention
in a team thread, a 1:1 DM, a task comment, a trigger — you'll see:
14:22:07 ● connected as "Ada" — waiting for agent.run_requested (exec: claude -p)
14:24:31 ▶ run 01JQ8Z4K2M started (conversation 9f3c…)
14:24:58 ✓ run 01JQ8Z4K2M replied in 26.4s (412 chars)Other agents, same command:
npx @prohost/cli agent run --exec 'codex exec'
npx @prohost/cli agent run --exec 'goose run -i -'
npx @prohost/cli agent run --exec './my-agent.sh'Try it without sending anything first:
npx @prohost/cli agent run --exec 'claude -p' --dry-run--dry-run prints the reply your agent produced and makes no writes at all —
nothing is posted and the run is left open.
How a run works
- Someone @-mentions your agent. ProhostAI emits an
agent.run_requestedevent on the authenticated websocket. Your machine shows up in ProhostAI as connected whileagent runholds the socket open. - The CLI verifies the event's HMAC signature. An event that doesn't verify is not executed — the whole point of the signature is that a run event makes your machine execute a command.
- It builds a prompt — who your agent is, the standing instructions you wrote
in its ProhostAI profile, what it's replying to, the recent messages in the
thread, anything the account remembers that bears on it, and the message that
mentioned it — and runs your
--execcommand with that prompt on stdin. Images and files on those messages appear as URLs the agent can download. - Your command's stdout is the reply. It's posted into the conversation
verbatim, so print the message and nothing else — no preamble, no reasoning.
(Claude Code and Codex are the exceptions: see below.)
To reply with nothing, print exactly
[NO_REPLY]: nothing is posted and the run is completed as a deliberate silence. That is the expected answer when your agent is woken only because it is in a thread where someone else was addressed — the prompt says so when that is the case. - The run is completed either way. A command that crashes, times out, or prints
nothing reports
failed; a run is never left hanging.
Where your agent runs
By default the command runs in ~/.prohost/workspace, which the CLI creates —
not the directory you started agent run from. That matters more than it
sounds like it does: coding agents read their working directory as
configuration. Start one inside a code checkout and it picks up that repo's
CLAUDE.md, its .claude/settings.json permission list, and its .mcp.json
servers — so your agent answers a customer's question carrying instructions
about a codebase, and quietly refuses tools that repo happens to deny.
Point --workdir somewhere else if you want the agent to work in a particular
directory. Just choose it deliberately.
If your agent is Claude Code
When --exec starts with claude and asks for print mode (-p / --print),
the CLI does three extra things. Codex gets the same three through its own
flags — see "If your agent is Codex" below. Every other command — goose, your
own script — is left exactly alone.
It keeps the conversation. Each run is a fresh process, so without this your
agent meets every message as its first: someone answers its clarifying question
and gets a reply that has forgotten asking. The CLI reads the session_id out
of Claude Code's JSON output, records it per conversation in
~/.prohost/sessions.json, and --resumes it next time. Sessions don't live
forever; when one is gone the run is retried once from scratch rather than
failing. A run stopped by your Claude usage limit is not retried here: it fails
with Claude Code's own message, which says when the limit resets, and the
conversation keeps its session for the next run. The failure also tells
ProhostAI when the limit lifts: if that is within a few hours, ProhostAI asks
the agent again shortly after (unless someone asks again first), and either way
it alerts whoever asked.
It lets the agent actually do things. Runs are invoked with
--dangerously-skip-permissions, so tool calls execute without stopping to ask.
This is deliberate: in print mode there is nobody at a terminal to approve
anything, so a permission-gated brain silently declines to do the work you hired
it for. Read the trust model below before you leave this running. Pass
--safe-tools (or --no-skip-permissions) to gate tools instead — expect the
agent to answer rather than act.
It gives the agent your ProhostAI tools. The MCP endpoint from your pairing
is written to ~/.prohost/mcp.json (chmod 600 — it holds your API key) and
passed with --mcp-config, so the agent can look things up instead of guessing.
--strict-mcp-config is passed too: the run gets only this server, never the
MCP servers you use for your own work.
A pairing is deliberately narrow, so not every tool will work: today's default scopes cover conversations, tasks, cleanings and approvals, but not reservations or listings — those tools return a permission error. The prompt tells the agent to say what it couldn't reach rather than answer as if it had, and refused tools are printed in the run log.
The one exception is a ProhostAI back-office agent. Before a run, the CLI asks
the MCP server's tools/list (cached for ten minutes). If the list includes the
back-office read tools, which only an internal key with backoffice:read sees,
and a read-only call to the schema tool is allowed, the prompt tells the agent that read-only cross-account investigation is
pre-authorized. Writes and customer-facing sends still go through approval.
It reads the reply out of JSON. The run is invoked with
--output-format json, and the result field is what gets posted. If that
output isn't JSON — a login prompt, a crash banner — the run is reported failed
rather than pasting it into a customer's conversation.
Detection is deliberately narrow: it matches only when claude is the first
word of the command and print mode is asked for. Anything else — npx claude
-p, env FOO=1 claude -p, a wrapper script, or any command containing a pipe,
redirect, or && — keeps the plain stdin/stdout contract untouched. So
--exec 'sh -c "claude -p"' is the way to opt out.
Giving the agent other MCP servers
One server by default is a boundary, not an oversight: a run executes with permission prompts off, on prompt text written by whoever mentioned your agent. Every MCP server reachable from that run is a server your workspace teammates can drive.
If you want to widen it anyway, create ~/.prohost/extra-mcp.json by hand. The
CLI never creates this file — its existence is you saying yes on purpose. The
shape is the same mcpServers object Claude Code's --mcp-config takes, so you
can lift a block straight out of an existing .mcp.json:
{
"mcpServers": {
"linear": {
"type": "http",
"url": "https://mcp.linear.app/mcp",
"headers": { "Authorization": "Bearer lin_api_…" }
},
"notes": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/me/notes"]
}
}
}Read this before you use it. Every server in that file is exercisable by
anyone who can trigger your agent — a teammate, a guest whose message routes to
it — with permission prompts off and no human in the loop. A filesystem server
scoped at / is a filesystem server they can read. Scope each one as narrowly
as it will go.
Works on both Claude Code and Codex. Details:
- The name
prohostis reserved. An entry using it is rejected with a line in the log and the ProhostAI server stays pointed where pairing put it. - On Codex, a name your own config already uses is also rejected. Codex merges the two definitions rather than replacing one with the other, so an extra that changes only the URL would inherit the configured server's headers and send its credential to the new host. Rename the extra to use it.
- Malformed JSON costs you the extras, not the run: the CLI warns and starts without them. The same is true of an entry whose value isn't an object.
- The file is re-read before every run. Deleting an entry revokes it for the next invocation of an already-running daemon; no restart is required.
- Which extras loaded is printed when the grant set changes, so
agent.loganswers "did my server actually load" without guessing. - On Codex the non-secret parts of each definition become
-coverrides, which means URLs, commands, and arguments land on a command line other users can read viaps. Literalheaders,http_headers, and stdioenvvalues are moved to the child environment instead. On Claude Code the definitions go into~/.prohost/mcp.json, which ischmod 600. - Also on Codex: a value with something TOML can't express (an infinity, say) makes that server skipped with a warning, rather than emitted as an override that would fail to parse and take the ProhostAI server down with it. Any server name works, dots included.
- Delete the file to go back to one server. There is no flag to disable it, because a file you have to write by hand is already the opt-in.
If your agent is Codex
When --exec starts with codex and uses the exec subcommand, the CLI does
the same three things it does for Claude Code, through Codex's own flags. The
detection is just as narrow: interactive codex, a wrapper script, a command
that already says codex exec resume / codex exec review, or one
carrying its own unquoted prompt (codex exec Be-terse) keep the plain
stdin/stdout contract untouched. That last one matters: Codex takes exactly one
prompt positional, so the CLI cannot add its own alongside yours. Shell syntax
inside a recognisable Codex command — quoting, escapes, expansions, globs,
comments, redirects, pipelines or lists — instead fails before Codex starts.
The harness cannot safely make a whitespace scan agree with both POSIX shells
and cmd.exe, and treating such a command as plain would silently drop MCP
isolation. Ordinary unquoted Windows path separators remain supported. Put
standing instructions in the agent's ProhostAI profile instead — they arrive at
the top of the prompt, ahead of everything else.
| | Claude Code | Codex |
| --- | --- | --- |
| Reply read from | --output-format json | --json (JSONL event stream) |
| Conversation kept with | --resume <id> | codex exec resume <thread> |
| Tools wired via | --mcp-config | one verified -c mcp_servers={…} override |
| API key travels in | ~/.prohost/mcp.json (chmod 600) | the child's environment |
| Tools ungated by | --dangerously-skip-permissions | --yolo |
--skip-git-repo-check is also passed on every Codex run, and it is not
optional: your agent works in ~/.prohost/workspace, which is not a git
repository, and Codex refuses to start there without it.
If you already pass --yolo (or --dangerously-bypass-approvals-and-sandbox)
in your own --exec, the CLI won't add a second one — passing both is an
argument error that would fail every run. --safe-tools works the same way it
does for Claude Code, and it removes a bypass flag you wrote yourself rather
than just declining to add one, so the gating it promises is real. (It covers
the full bypass only; --full-auto still auto-approves inside Codex's sandbox.)
Your own MCP servers are switched off for agent runs. Codex has no
--strict-mcp-config, so the CLI does the same job a different way: before each run
it asks your binary what servers it has (codex mcp list --json) and passes one
-c mcp_servers={"<name>"={enabled=false}, …} covering all of them. A run then
holds the ProhostAI server, plus anything in your extra-mcp.json, and nothing
else — the same boundary the Claude Code path gets. Each server's own definition
is left intact; only this invocation sees it disabled.
Two mechanisms that look like they should do this do not, and are worth naming
so nobody reaches for them. Replacing the mcp_servers table does not replace
it — Codex deep-merges config overrides, so your servers survive alongside
whatever the override adds. And --ignore-user-config works, but throws away
your model, profile and sandbox settings along with your servers.
The enumeration asks the binary rather than reading ~/.codex/config.toml,
because that file is not the whole story: on a normal install codex mcp list
reports servers that appear nowhere in it. It also handles names a dotted-path
override cannot address — a server called fly.io written as
-c mcp_servers.fly.io.enabled=false addresses a three-segment path and Codex
rejects the whole config, which is why the name goes inside the value instead.
Every probe also runs in the exact directory the eventual Codex process will
use. That matters because a trusted project's .codex/config.toml is another
config layer: probing where the harness was launched and then running in
$PROHOST_HOME/workspace or an explicit --workdir would certify one MCP
inventory and execute another.
The override is checked before it is used. {enabled=false} merges onto a
server's existing definition, so an entry Codex cannot make sense of takes the
whole config down rather than disabling anything. The CLI therefore runs the
disable past codex mcp list first, falls back to checking each server
individually if the combined form is rejected, and then verifies the exact final
override. If any configured server cannot be disabled, or if the final listing
does not show the ProhostAI server and every accepted extra enabled, the run
fails before Codex starts.
Codex plugins get a second mechanism, because mcp_servers cannot reach
them. A plugin declared in your config — say [plugins."github@openai-curated"]
— contributes an MCP server (github) that has no mcp_servers entry for
{enabled=false} to merge onto, so forcing one leaves a table with no transport
and Codex rejects the config outright: invalid transport in mcp_servers.github.
Nothing in codex mcp list --json marks a server as plugin-provided, so the CLI
measures instead of guessing: it lists again with plugins off, and the names that
vanish were the plugin's. Those are shut off for the run with
-c features.plugins=false, verified by re-listing like any other disable.
This is applied only when a plugin actually supplies an MCP server. Plugins
also carry skills and hooks, and switching the system off costs you those, so an
install whose plugins contribute no server keeps everything and gets exactly the
command line it got before. Your own prohost server and your extra-mcp.json
servers are unaffected either way. Per-plugin spellings are deliberately not
used: plugins."github@openai-curated".enabled=false and every variant of it
load without error and disable nothing at all, which is precisely the kind of
override the verification step exists to catch.
Your apps (connectors) are switched off for every run, unconditionally.
Codex "apps" are the third way tools reach a run, and the only one with no
inventory to read: an app is a set of MCP tools, served under the name
codex_apps, and it appears in no codex mcp list --json output at any
setting. Apps are account-level — they come with your ChatGPT login and are
cached under $CODEX_HOME/cache/codex_apps_tools, not declared in
mcp_servers. On the install this was measured against they were 28 tools, and
they are not read-only: sites_create_site, sites_deploy_site_version,
sites_add_custom_domain, sites_update_environment_variables,
sites_generate_siwc_bypass_token, plugin_management_update_app_permissions
and plugin_management_uninstall_app publish sites and rewrite app permissions
on your account.
So this one is not applied on evidence, the way the plugins switch is — there is
no listing to take evidence from. Every isolated run carries
-c features.apps=false, and the CLI reads the result back out of
codex features list to confirm the apps row really flipped. If it cannot
confirm that, the run fails before Codex starts, for the same reason a server
that will not disable fails it: an override that loads happily and changes
nothing is the failure mode this whole path exists to catch.
features.plugins=false is not a substitute. It costs you your plugin
skills and hooks, it leaves the apps instructions in the prompt, and on an
install with no plugin-backed server it is never applied at all — which is
exactly the shape that was quietly receiving these tools before 0.4.2. Turning
apps off leaves plugins on, so plugin skills and hooks survive; what a run
loses is your connectors, which is the intent. The run executes on behalf of
whoever mentioned the agent, and your connectors are yours.
If a server still cannot be disabled, the run fails before Codex starts and
names it. Two ways out: remove that server or plugin from ~/.codex/config.toml,
or give the daemon a CODEX_HOME of its own that holds only auth.json —
mkdir -p ~/.prohost/codex-home
cp ~/.codex/auth.json ~/.prohost/codex-home/auth.json
CODEX_HOME=~/.prohost/codex-home prohost agent run --exec 'codex exec'A dedicated CODEX_HOME is the cleanest answer in general: the agent inherits
your login and nothing else, your own Codex setup is untouched, and no future
plugin or server you install can change what a paired run can reach.
It is not, however, an answer to apps. A CODEX_HOME holding only auth.json
fetches your account's apps on its first run, writes them to its own cache, and
serves the full set from the second run onwards — the login is what carries
them, so a fresh directory only delays it by one run. That is why the apps
switch is unconditional rather than something an operator can opt out of by
tidying their config.
If the enumeration fails outright — an older Codex without mcp list --json,
or output the CLI doesn't recognise — the run fails before Codex starts. A
paired run never proceeds under --yolo when its MCP boundary cannot be proven.
Nothing here changes what your Codex config does outside agent runs.
One gap that cannot be closed from here: profiles. If your --exec runs
Codex under a named profile (codex exec -p work), that profile's
work.config.toml can define MCP servers of its own — and codex mcp list has
no way to be asked about a profile (-p and --profile are rejected, and the
-c profile="…" form is refused as a removed legacy spelling). Servers defined
only inside a profile are therefore invisible to the sweep. The CLI fails the
run before Codex starts when it sees a profile flag; run the agent without -p.
The same fail-closed rule applies to -c / --config, --enable, --disable,
--ignore-user-config, and -C / --cd in --exec. Those flags can change
config, plugin loading, or the project config discovery directory for the real
invocation, while the separate codex mcp list verifier cannot safely replay
an arbitrary shell command's arguments. Use the harness's --workdir when you
need a different directory; dedicated flags such as --model and --sandbox
remain supported.
The survey is retaken before every invocation, because
install-daemon runs for weeks and Codex rereads its configuration on every
invocation — a server you install on Tuesday would otherwise be reachable from
the next run. The log stays quiet unless the answer changes.
Refused tools are reported, as of 0.2.3. Codex has no permission_denials
field, but a refused call arrives as an mcp_tool_call item with
status: "failed" and user cancelled MCP tool call, and the CLI prints those
in the same line the Claude Code path uses. A tool call that failed for any
other reason — an MCP server that 500s — is deliberately not reported as a
denial, so the log doesn't send you to your permissions when the problem is
your network.
Your ProhostAI API key is passed to Codex in the child process's environment
rather than in the -c override, because a command line is readable by anyone
on the machine via ps and a process environment is not. Literal headers,
http_headers, and stdio env values in extra-mcp.json get the same
treatment: the CLI translates them to Codex's environment-indirection fields,
so their values do not appear in process arguments.
Up to three runs are handled at once, in different conversations (see "Running
several conversations at once"; --concurrency 1 is one at a time), and each
run executes at most once on this machine — even across restarts. Started runs are recorded in
~/.prohost/runs.json before the command launches, so a webhook redelivery
that arrives after a crash or restart will close the run out rather than run
your agent a second time. That matters because your agent holds a credential:
a second execution could file the same task or send the same email again, which
no amount of message deduplication would undo.
What ProhostAI sees about your runtime
When the server supports it, agent run reports which runtime it drives
(Claude Code, Codex, goose or a custom command), that CLI's version, the model,
your plan's name, and how much of your 5-hour and weekly capacity is used, as
percentages with reset times. ProhostAI shows this next to the AI employee.
Your Claude or Codex login never leaves your machine, and prohost never reads
it: the numbers come from the agent CLI's own output (Claude Code's
stream-json events, Codex's session rollout files) or from asking the
unmodified CLI through its own protocol (get_usage,
codex app-server), which answers with its own login. Account ids, emails and
org names are never sent.
Accounts: which login an agent runs on
A machine can hold several Claude Code or Codex logins — say a work Max plan and a personal one — and each paired agent uses exactly one. Several agents can share one account; they then share its limits.
prohost agent account add work --runtime claude # prints the sign-in command, waits for it
# CLAUDE_CONFIG_DIR=~/.prohost-accounts/accounts/claude_code/work claude auth login
prohost agent install-daemon --exec 'claude -p' --account work
prohost agent account list
prohost agent list # every agent here, its account and daemon- Each account is its own config directory under
~/.prohost-accounts(or$PROHOST_ACCOUNTS_ROOT). You sign in with the agent CLI's own login command;prohostonly asks the CLI whether it is signed in and never reads the login. defaultis the CLI's usual~/.claude/~/.codex, so an agent paired before accounts existed keeps working unchanged.- The account is applied to every run (and every usage check) separately, so
picking another one — with
--account, or in ProhostAI — takes effect from the agent's next run, with no restart. - Accounts never switch on their own. When an account's limit is reached, the agent rests until it resets; moving it to another subscription is always a person's decision.
- ProhostAI sees a random id for each account and the label you gave it. Never the email, org or anything from the login.
Only one agent run can serve an agent home at a time: a second one exits,
naming the pid that holds $PROHOST_HOME/run.lock.
Running several conversations at once
Up to three runs execute at once, so a one-line question in one thread does
not sit behind a two-hour job in another. --concurrency <n> (1 to 16) sets
the limit:
prohost agent run --exec 'claude -p' --concurrency 5 --repos ~/code/app,~/code/site--concurrency 1 is one run at a time, in the order they arrived — what every
version before 0.12.0 did unless told otherwise.
- Runs in the same conversation still take turns, in order. Each turn resumes the session the last one left, so two turns of one thread never overlap. Only different conversations run side by side.
- Every run spends the same login's quota. Three runs at once use a subscription's 5-hour window about three times as fast. Lower the limit if the window runs out at busy times.
- They share the machine. The agent is told other runs may be executing
beside it and, without
--repos, that no git checkout is its own. If it edits code, pass--repos. - A daemon follows the default.
install-daemonrecords--concurrencyonly when you give it, so a daemon installed without it runs three at once after upgrading to 0.12.0 and restarting. Reinstall with--concurrency 1to keep it at one.
--repos takes a comma-separated list of git checkouts the agent works on.
Each conversation then gets its own git worktree of every one, at
~/.prohost/worktrees/<conversation>/<repo-name>, made on that conversation's
first run. The agent is told to do its repository work there and to leave your
originals alone; the directory is also exported to the command as
$PROHOST_RUN_REPOS. Its working directory does not change — it is still the
workspace, which is where its configuration and its sessions live.
A conversation keeps its checkouts between runs, so a follow-up finds the
branch the last run left. After 14 days without a run they are removed with
git worktree remove, which refuses a checkout holding uncommitted or
untracked files — those are kept and logged. Committed branches live in your
original repository and are never touched. Each worktree is a full working
copy, and dependencies installed inside it are not shared, so budget disk for
it.
--repos works at any --concurrency, 1 included. If a checkout cannot be made (a
full disk, a path that is no longer a repository), the run still happens: the
agent is told to treat that repository as read-only for the run.
Every command the harness starts has PROHOST_PAIRED_RUN=1 in its
environment, so instructions your agent reads from disk can tell a paired run
from any other use of the same files.
A developer workspace
For an agent that works on the ProhostAI codebase, prohost agent workspace
init --developer --repo <backend-service checkout> adds two things to its
workspace's .claude/: a skill that points it at the repo's PR protocol
(prohost_agents/support/prompts/pr_protocol.md, referenced, not copied) and
the gh pr create gate hook the fleet uses. Nothing else about the default
workspace changes.
Hosting Code threads on this Mac (off by default)
ProhostAI can show agent terminals — Claude Code, Codex or a plain shell — as Code threads in the inbox. This machine can host them, but only once its owner says so, here, on this machine. Nothing the server sends can turn it on.
prohost agent code-host enable # host Code threads (needs --repos on agent run)
prohost agent code-host remote on # also let *you* attach and type from ProhostAI
prohost agent code-host status
prohost agent code-host remote off # refuses the next viewer at once
prohost agent code-host disable
prohost agent code-host folders add ~/code/app # open a folder for Code threads (the desktop app does this)
prohost agent code-host folders list [--json]
prohost agent code-host folders remove ~/code/appRestart agent run (or the daemon) after enable / disable.
Host only: Code threads without an agent that answers. prohost agent run
--host-only (or install-daemon --host-only) takes no --exec. It keeps
everything hosting needs — the connection, presence, status, the desktop
app's local socket, activity.json — and executes nothing anyone sends: every
run requested of this agent (a mention, a DM) is closed at once as failed with
"This computer only hosts Code threads; it doesn't answer messages." It
declares the host_only capability. --host-only and --exec can't be used
together, and prohost --help lists --host-only on builds that have it. A
host-only agent runs no agent of its own, so it has no account: its Code
threads run Claude Code or Codex on this Mac's default login, as a terminal you
opened would (--account is refused with --host-only, and an account already
on the pairing is reported as unused at start). The
desktop app's "This Mac" daemon runs this way.
Folder threads, in place. A thread can also run in a folder instead of a repository: in that folder itself, with no worktree and no branch, the way a terminal you open there would. The desktop app's folder picker adds what you pick to this Mac's opened folders (
folders add;$PROHOST_HOME/code-host/folders.json,0600, the 50 most recent, each stored as its realpath). A thread started from anywhere else — your phone, the web — may only run in one of those folders or a folder inside one, checked after resolving symlinks, so a link that points out of an opened folder does not count as inside it. Anything else is refused with "This folder hasn't been opened on this Mac." A thread that starts moves its folder to the front of the list. The list (with whether each is a git repository, and your home directory for showing~/…) is reported to ProhostAI so your phone can offer it;--reposis not needed for folders.Work in a separate copy. For a folder in a git repository, a thread can instead get its own worktree and branch of that checkout, exactly as a repository thread does, opened in the same subfolder. Your checkout is never switched. A folder outside git can't, and says so; neither can a subfolder the branch doesn't have (untracked or ignored) or one that is a link out of the copy on that branch — the thread is refused, never started elsewhere.
The goal is optional. A thread with no goal opens Claude Code or Codex with no prompt, or just the shell.
One worktree and one tmux window per repository thread. The repository is matched against
--reposby itsoriginremote (else by directory name), checked out under$PROHOST_HOME/worktrees/, and switched to the thread's branch. The windows live in a private tmux server (tmux -L prohost-<id>; your own tmux is untouched), so they survive the agent restarting and are re-adopted. You needtmuxinstalled (brew install tmux).Hooks without touching your config. Claude Code is started with
--settingsand Codex with-c notify=…, naming a shim that does nothing unless$PROHOST_TERMINAL_IDis set — your ownclaudesessions never see them, and~/.claude/~/.codexare never written. They report the prompt, the end of each turn, permission prompts and Esc/Ctrl+C to the thread, using a narrow token the relay issues ($PROHOST_HOME/code-host/hook.json,0600, expires in a day) — never your API key, never a Claude or Codex credential. ProhostAI saves the conversation, not your terminal. An Allow / Deny chosen in ProhostAI answers the prompt in the terminal (Enter, Down+Enter, Escape) — only if that prompt is still the one on screen.Reply recovery after a permission menu. New Claude Code windows check the installed version before choosing their hooks. Claude Code 2.1.29
