@prohost/cli 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,8 +4,104 @@ Versions follow [semver](https://semver.org/). Publishing is automated: merging
4
4
  a version bump to `main` triggers `.github/workflows/npm-publish-cli.yml`, which
5
5
  builds via `prepack`, runs the suite, publishes, and tags `cli-v<version>`.
6
6
 
7
+ ## 0.8.0
8
+
9
+ A paired agent woken as a bystander can now stay silent.
10
+
11
+ - **`reply_expected: false`.** When a team-chat message lands in a thread the
12
+ agent belongs to but is addressed to someone else (ProhostAI's participation
13
+ walk), the run event now says so. The prompt tells the agent the message was
14
+ not addressed to it and nobody is waiting on it — instead of "someone is
15
+ waiting on a reply" — and renders the server's `trigger.brief`, the same
16
+ bystander guidance ProhostAI's in-product agents get. Absent (every older
17
+ server) keeps today's prompt.
18
+ - **`[NO_REPLY]`.** An agent whose whole answer is `[NO_REPLY]` (any case,
19
+ surrounding whitespace ignored) posts nothing, and the run is completed
20
+ `{status: "succeeded", outcome: "silent"}` so ProhostAI records a deliberate
21
+ silence and clears the thinking indicator as one. Checked on every run, so
22
+ the literal token can never reach a thread. A server that predates `outcome`
23
+ ignores it and records a plain success.
24
+ - **`trigger.brief` is rendered** whenever the server sends one that is not just
25
+ the message restated — a work order used to be dropped on any run that also
26
+ had a message.
27
+
28
+ ## 0.7.0
29
+
30
+ Machines, accounts and agents: a paired AI employee now runs on a named
31
+ Claude Code or Codex login, and ProhostAI can show — and change — which one.
32
+
33
+ - **Accounts.** `prohost agent account add <label> --runtime claude|codex`
34
+ creates a login in its own config directory under `~/.prohost-accounts`
35
+ (`$PROHOST_ACCOUNTS_ROOT`), prints the exact sign-in command
36
+ (`CLAUDE_CONFIG_DIR=… claude auth login` / `CODEX_HOME=… codex login`) and
37
+ waits until the agent CLI reports it signed in. The CLI never reads the
38
+ login: it asks `claude auth status` / `codex login status` and keeps only
39
+ "signed in or not". `account list` / `account remove` round it out, and
40
+ `default` is the CLI's usual `~/.claude` / `~/.codex`, so existing agents
41
+ change nothing.
42
+ - **One account per agent, applied per run.** `--account <label>` on `pair`
43
+ and `install-daemon` stores it in `agent.json`; every run, idle usage probe
44
+ and Codex MCP probe is spawned with that account's `CLAUDE_CONFIG_DIR` /
45
+ `CODEX_HOME`, so a change applies from the next run with no restart. An agent
46
+ set to an account that is not on the machine refuses to run rather than spend
47
+ another subscription. **Nothing ever switches accounts automatically.**
48
+ - **Picked in ProhostAI.** An `agent.config_updated` event (signed, verified
49
+ like a run) or `auth_ok.agent_config` on reconnect moves the agent to the
50
+ account an operator picked in the web app, from its next run. An account
51
+ that isn't on this machine is ignored with a warning. Picks carry the
52
+ server's `requested_at`; one older than a pick already applied — or than the
53
+ floor `auth_ok` sends on connect — arrived out of order and is dropped.
54
+ - **A stable machine id.** The first agent on a computer writes a random UUID
55
+ to `~/.prohost-accounts/machine.json`; every agent there sends it on auth as
56
+ `machine_id`. ProhostAI keys the machine's accounts by it — the label
57
+ (hostname by default) is display only, since two computers can share one.
58
+ - **The status frame names the account.** `runtime_status` gains `account`
59
+ (random key, label, runtime, billing as the login reports it, sign-in state) and `machine_accounts`
60
+ (every account on the machine), plus per-model weekly windows
61
+ (`weekly_model`, e.g. Fable) from Claude Code's `get_usage`.
62
+ - **Fix: the 5-hour window disappeared after five idle hours.** Claude Code
63
+ omits a rolled-over window from its own `rate_limit_event`, reports an idle
64
+ account's 5-hour window with no reset time, and the harness dropped any
65
+ window without a future reset — so an idle agent showed only its weekly
66
+ window. A window with known usage is now always reported: rolled over or not
67
+ started yet, it is `0%` with `resets_at: null`. New observations are merged
68
+ per window kind instead of replacing the whole list.
69
+ - **`prohost agent list`** shows every agent home on the machine (default,
70
+ `$PROHOST_HOME`, and every home a `ai.prohost.agent.*` launchd plist pins),
71
+ its account, and whether its daemon or a harness is running — plus every
72
+ account with its runtime, billing and sign-in state.
73
+ - **`$PROHOST_HOME/run.lock`.** A second `agent run` on the same home exits
74
+ with the pid that holds it, instead of executing every run twice. A lock
75
+ left by a dead process is taken over.
76
+ - **`prohost agent workspace init --developer --repo <checkout>`** (opt-in)
77
+ adds the ProhostAI PR-protocol skill (a pointer to the repo's
78
+ `pr_protocol.md`, not a copy) and the fleet's `gh pr create` gate as a
79
+ `PreToolUse` hook to the agent workspace's `.claude/`.
80
+
7
81
  ## 0.6.0
8
82
 
83
+ The thread now shows what a paired agent is doing, not just that it is doing
84
+ something.
85
+
86
+ - **Tool calls stream into the conversation.** With Claude Code, every
87
+ `tool_use` / `tool_result` in the `--output-format stream-json` stream is
88
+ reported to ProhostAI as it happens — the tool, a one-line preview of its
89
+ input (the command, the path, the query), whether it succeeded, and a short
90
+ line of what came back — and rendered in the thread exactly as an
91
+ in-product AI employee's tool calls are, beneath the "Working on
92
+ \<machine\>…" line. Events are batched on a one-second debounce
93
+ (`POST /v1/agent-runs/{id}/events`, at most 50 per request) and always
94
+ flushed before the reply is posted and before the run is completed.
95
+ - **Best-effort, and additive.** The path comes from the run event
96
+ (`events.path`); a server that doesn't advertise it gets no requests at
97
+ all, and one that answers `404` is told so once per run and then left alone.
98
+ No event failure can fail the run or delay its reply. The 60-second
99
+ heartbeat and its one-line `progress` are unchanged.
100
+ - **What stays on your machine.** Previews are capped at 200 characters and
101
+ only the headline field of a tool's input travels — a `Write`'s file
102
+ content, an `Edit`'s old and new text, never do. The server clips again and
103
+ redacts before anything reaches a browser.
104
+
9
105
  ProhostAI can now show which runtime, model and machine a paired AI employee
10
106
  runs on, and how much of the operator's Claude or Codex subscription is left.
11
107
 
package/README.md CHANGED
@@ -117,6 +117,10 @@ nothing is posted and the run is left open.
117
117
  4. Your command's **stdout** is the reply. It's posted into the conversation
118
118
  verbatim, so print the message and nothing else — no preamble, no reasoning.
119
119
  (Claude Code and Codex are the exceptions: see below.)
120
+ To reply with nothing, print exactly `[NO_REPLY]`: nothing is posted and the
121
+ run is completed as a deliberate silence. That is the expected answer when
122
+ your agent is woken only because it is in a thread where someone else was
123
+ addressed — the prompt says so when that is the case.
120
124
  5. The run is completed either way. A command that crashes, times out, or prints
121
125
  nothing reports `failed`; a run is never left hanging.
122
126
 
@@ -442,6 +446,46 @@ unmodified CLI through its own protocol (`get_usage`,
442
446
  `codex app-server`), which answers with its own login. Account ids, emails and
443
447
  org names are never sent.
444
448
 
449
+ ### Accounts: which login an agent runs on
450
+
451
+ A machine can hold several Claude Code or Codex logins — say a work Max plan
452
+ and a personal one — and each paired agent uses exactly one. Several agents can
453
+ share one account; they then share its limits.
454
+
455
+ ```sh
456
+ prohost agent account add work --runtime claude # prints the sign-in command, waits for it
457
+ # CLAUDE_CONFIG_DIR=~/.prohost-accounts/accounts/claude_code/work claude auth login
458
+ prohost agent install-daemon --exec 'claude -p' --account work
459
+ prohost agent account list
460
+ prohost agent list # every agent here, its account and daemon
461
+ ```
462
+
463
+ - Each account is its own config directory under `~/.prohost-accounts` (or
464
+ `$PROHOST_ACCOUNTS_ROOT`). You sign in with the agent CLI's own login command;
465
+ `prohost` only asks the CLI whether it is signed in and never reads the login.
466
+ - `default` is the CLI's usual `~/.claude` / `~/.codex`, so an agent paired
467
+ before accounts existed keeps working unchanged.
468
+ - The account is applied to every run (and every usage check) separately, so
469
+ picking another one — with `--account`, or in ProhostAI — takes effect from
470
+ the agent's next run, with no restart.
471
+ - **Accounts never switch on their own.** When an account's limit is reached,
472
+ the agent rests until it resets; moving it to another subscription is always
473
+ a person's decision.
474
+ - ProhostAI sees a random id for each account and the label you gave it. Never
475
+ the email, org or anything from the login.
476
+
477
+ Only one `agent run` can serve an agent home at a time: a second one exits,
478
+ naming the pid that holds `$PROHOST_HOME/run.lock`.
479
+
480
+ ### A developer workspace
481
+
482
+ For an agent that works on the ProhostAI codebase, `prohost agent workspace
483
+ init --developer --repo <backend-service checkout>` adds two things to its
484
+ workspace's `.claude/`: a skill that points it at the repo's PR protocol
485
+ (`prohost_agents/support/prompts/pr_protocol.md`, referenced, not copied) and
486
+ the `gh pr create` gate hook the fleet uses. Nothing else about the default
487
+ workspace changes.
488
+
445
489
  ### The trust model — read this once
446
490
 
447
491
  `agent run` is a standing instruction to execute a command on your machine when
@@ -514,6 +558,12 @@ for it:
514
558
  which is what stops the server reaping a run that is still being worked on.
515
559
  Against an older ProhostAI server the heartbeat endpoint answers `404`; the
516
560
  CLI says so once per run and otherwise behaves exactly as it did before.
561
+ - **The thread sees the tool calls.** With Claude Code, each tool the agent
562
+ calls — and whether it succeeded — is reported to ProhostAI as it happens
563
+ and shown in the conversation under the "Working on your machine…" line,
564
+ the way an in-product AI employee's tool calls are. Only a one-line preview
565
+ travels (the command, the path, the query; never a file's contents), and a
566
+ server that doesn't support it is told nothing.
517
567
  - **Cancel works.** Stopping a run in ProhostAI is answered on the next
518
568
  heartbeat: the command's whole process group gets `SIGTERM`, then `SIGKILL`
519
569
  five seconds later, and the run is reported as cancelled. No reply is posted.
@@ -576,6 +626,7 @@ decision to make.
576
626
  | Flag | Default | What it does |
577
627
  | --- | --- | --- |
578
628
  | `--code` | `$PROHOST_PAIRING_CODE` | The one-time pairing code. |
629
+ | `--account` | the home's current account, else `default` | Which Claude Code / Codex login this agent runs on (see Accounts). |
579
630
  | `--webhook-url` | none | Also push events to this HTTPS endpoint. Omit for stream-only, which is the normal laptop setup. |
580
631
  | `--base-url` | `https://connect.prohost.ai` | Public API base. |
581
632
 
@@ -0,0 +1,75 @@
1
+ /**
2
+ * The account a running harness uses — resolved per spawn, switched on request.
3
+ *
4
+ * `agent run` reads the account from `agent.json` at every run and every idle
5
+ * probe rather than once at start-up, so a switch (made here with
6
+ * `--account`, or in ProhostAI through `agent.config_updated`) takes effect on
7
+ * the next run without restarting or reinstalling the daemon.
8
+ */
9
+ import type { AccountRecord, AccountRuntime, CommandRunner, ConfigUpdateOutcome } from './accounts.js';
10
+ import type { AccountsSnapshot } from './runtime_status.js';
11
+ export type AccountResolution = {
12
+ ok: true;
13
+ record: AccountRecord;
14
+ } | {
15
+ ok: false;
16
+ error: string;
17
+ };
18
+ export declare class HarnessAccount {
19
+ readonly runtime: AccountRuntime;
20
+ private readonly env;
21
+ private readonly binary;
22
+ private readonly run;
23
+ /** Label from `agent.json` at start-up; the file wins once it has one. */
24
+ private readonly initialLabel;
25
+ /** Label the most recent run was spawned with — what a run in flight is using. */
26
+ private spawnedLabel;
27
+ /** `requested_at` of the newest pick applied, so a late older one is dropped. */
28
+ private appliedRequestedAt;
29
+ constructor(options: {
30
+ runtime: AccountRuntime;
31
+ /** Environment that locates `$PROHOST_HOME` and the accounts root. */
32
+ env?: NodeJS.ProcessEnv;
33
+ initialLabel?: string;
34
+ /** Agent CLI binary used for sign-in checks. */
35
+ binary?: string;
36
+ run?: CommandRunner;
37
+ });
38
+ /** The label this agent home names now. */
39
+ label(): string;
40
+ /**
41
+ * The account for the next spawn. An account named but missing from this
42
+ * machine is an error: running on some other login would spend a
43
+ * subscription nobody picked.
44
+ */
45
+ resolve(): AccountResolution;
46
+ /** {@link resolve} for a run about to be spawned; remembers which account it got. */
47
+ resolveForSpawn(): AccountResolution;
48
+ /** Whether the chosen account differs from the one the last run was spawned with. */
49
+ changedSinceSpawn(): boolean;
50
+ /** Spawn environment for the current account; empty when it can't be resolved. */
51
+ spawnEnv(): NodeJS.ProcessEnv;
52
+ /**
53
+ * Apply an account picked in ProhostAI. Picks carry `requested_at` (the
54
+ * server stamps them in the order they were made); one older than a pick
55
+ * already applied arrived out of order and is ignored. A pick without a
56
+ * stamp (an older server) always applies.
57
+ */
58
+ apply(key: unknown, requestedAt?: unknown): ConfigUpdateOutcome;
59
+ /**
60
+ * Raise the floor without applying anything: `auth_ok` carries the newest
61
+ * pick's stamp even when nothing is pending, so a live pick still in flight
62
+ * from before the connect — or one a later pick cancelled — is dropped.
63
+ */
64
+ noteRequestedAt(requestedAt: unknown): void;
65
+ /**
66
+ * Accounts for the status frame: this agent's, plus every account on the machine.
67
+ *
68
+ * While a run executes, "this agent's" is the account it was spawned with,
69
+ * not a newer pick — the server would otherwise mark the pick fulfilled and
70
+ * credit that run's capacity to an account it isn't using.
71
+ */
72
+ snapshot(busy?: boolean): Promise<AccountsSnapshot>;
73
+ /** The command that signs the current account in, for a log line. */
74
+ signInHint(): string | undefined;
75
+ }
@@ -0,0 +1,130 @@
1
+ /**
2
+ * The account a running harness uses — resolved per spawn, switched on request.
3
+ *
4
+ * `agent run` reads the account from `agent.json` at every run and every idle
5
+ * probe rather than once at start-up, so a switch (made here with
6
+ * `--account`, or in ProhostAI through `agent.config_updated`) takes effect on
7
+ * the next run without restarting or reinstalling the daemon.
8
+ */
9
+ import { DEFAULT_ACCOUNT_LABEL, MAX_REPORTED_ACCOUNTS, accountBlock, accountEnv, applyAccountKey, checkAuth, loadAccounts, resolveAgentAccount, runtimeName, signInCommand, } from './accounts.js';
10
+ import { tryLoadCredentials } from './credentials.js';
11
+ export class HarnessAccount {
12
+ runtime;
13
+ env;
14
+ binary;
15
+ run;
16
+ /** Label from `agent.json` at start-up; the file wins once it has one. */
17
+ initialLabel;
18
+ /** Label the most recent run was spawned with — what a run in flight is using. */
19
+ spawnedLabel;
20
+ /** `requested_at` of the newest pick applied, so a late older one is dropped. */
21
+ appliedRequestedAt;
22
+ constructor(options) {
23
+ this.runtime = options.runtime;
24
+ this.env = { ...process.env, ...options.env };
25
+ this.binary = options.binary;
26
+ this.run = options.run;
27
+ this.initialLabel = options.initialLabel;
28
+ }
29
+ /** The label this agent home names now. */
30
+ label() {
31
+ return tryLoadCredentials(this.env)?.account ?? this.initialLabel ?? DEFAULT_ACCOUNT_LABEL;
32
+ }
33
+ /**
34
+ * The account for the next spawn. An account named but missing from this
35
+ * machine is an error: running on some other login would spend a
36
+ * subscription nobody picked.
37
+ */
38
+ resolve() {
39
+ const label = this.label();
40
+ const record = resolveAgentAccount(label, this.runtime, this.env);
41
+ if (record)
42
+ return { ok: true, record };
43
+ return {
44
+ ok: false,
45
+ error: `this agent is set to the ${runtimeName(this.runtime)} account "${label}", which is not on this machine — ` +
46
+ `add it (prohost agent account add ${label} --runtime ${this.runtime === 'claude_code' ? 'claude' : 'codex'}) ` +
47
+ 'or pick another account for this agent in ProhostAI',
48
+ };
49
+ }
50
+ /** {@link resolve} for a run about to be spawned; remembers which account it got. */
51
+ resolveForSpawn() {
52
+ const resolved = this.resolve();
53
+ if (resolved.ok)
54
+ this.spawnedLabel = resolved.record.label;
55
+ return resolved;
56
+ }
57
+ /** Whether the chosen account differs from the one the last run was spawned with. */
58
+ changedSinceSpawn() {
59
+ return this.spawnedLabel !== undefined && this.spawnedLabel !== this.label();
60
+ }
61
+ /** Spawn environment for the current account; empty when it can't be resolved. */
62
+ spawnEnv() {
63
+ const resolved = this.resolve();
64
+ return resolved.ok ? accountEnv(resolved.record) : {};
65
+ }
66
+ /**
67
+ * Apply an account picked in ProhostAI. Picks carry `requested_at` (the
68
+ * server stamps them in the order they were made); one older than a pick
69
+ * already applied arrived out of order and is ignored. A pick without a
70
+ * stamp (an older server) always applies.
71
+ */
72
+ apply(key, requestedAt) {
73
+ const at = typeof requestedAt === 'string' ? Date.parse(requestedAt) : Number.NaN;
74
+ if (!Number.isNaN(at) && this.appliedRequestedAt !== undefined && at < this.appliedRequestedAt) {
75
+ return { applied: false, reason: 'a newer account pick was already applied' };
76
+ }
77
+ const outcome = applyAccountKey(key, this.runtime, this.env);
78
+ if (outcome.applied && !Number.isNaN(at))
79
+ this.appliedRequestedAt = at;
80
+ return outcome;
81
+ }
82
+ /**
83
+ * Raise the floor without applying anything: `auth_ok` carries the newest
84
+ * pick's stamp even when nothing is pending, so a live pick still in flight
85
+ * from before the connect — or one a later pick cancelled — is dropped.
86
+ */
87
+ noteRequestedAt(requestedAt) {
88
+ const at = typeof requestedAt === 'string' ? Date.parse(requestedAt) : Number.NaN;
89
+ if (Number.isNaN(at))
90
+ return;
91
+ if (this.appliedRequestedAt === undefined || at > this.appliedRequestedAt)
92
+ this.appliedRequestedAt = at;
93
+ }
94
+ /**
95
+ * Accounts for the status frame: this agent's, plus every account on the machine.
96
+ *
97
+ * While a run executes, "this agent's" is the account it was spawned with,
98
+ * not a newer pick — the server would otherwise mark the pick fulfilled and
99
+ * credit that run's capacity to an account it isn't using.
100
+ */
101
+ async snapshot(busy = false) {
102
+ const label = busy && this.spawnedLabel ? this.spawnedLabel : this.label();
103
+ const record = resolveAgentAccount(label, this.runtime, this.env);
104
+ const resolved = record ? { ok: true, record } : { ok: false, error: '' };
105
+ const records = loadAccounts(this.env);
106
+ // The current account first, so a machine with more than the cap still
107
+ // reports the one this agent is on.
108
+ const ordered = resolved.ok
109
+ ? [resolved.record, ...records.filter((r) => r.key !== resolved.record.key)]
110
+ : records;
111
+ const all = await Promise.all(ordered.slice(0, MAX_REPORTED_ACCOUNTS).map(async (record) => {
112
+ // Only the login of this runtime's CLI is checked with this binary;
113
+ // another runtime's accounts are checked with that CLI's default name.
114
+ const check = await checkAuth(record, {
115
+ binary: record.runtime === this.runtime ? this.binary : undefined,
116
+ run: this.run,
117
+ });
118
+ return accountBlock(record, check.state, check.billing);
119
+ }));
120
+ const current = resolved.ok ? all.find((a) => a.key === resolved.record.key) : undefined;
121
+ // More accounts than a frame carries (a hand-edited registry): send none
122
+ // rather than a partial list the server would prune to.
123
+ return { current, all: ordered.length > MAX_REPORTED_ACCOUNTS ? undefined : all };
124
+ }
125
+ /** The command that signs the current account in, for a log line. */
126
+ signInHint() {
127
+ const resolved = this.resolve();
128
+ return resolved.ok ? signInCommand(resolved.record) : undefined;
129
+ }
130
+ }
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Brain accounts — which Claude Code or Codex login a paired agent runs on.
3
+ *
4
+ * A machine can hold several logins ("work", "personal"), each in its own
5
+ * config directory: `CLAUDE_CONFIG_DIR` for Claude Code, `CODEX_HOME` for
6
+ * Codex. Every agent home on the machine uses exactly one of them, named in its
7
+ * `agent.json` as `account`, and several agents may share one (and so share its
8
+ * quota). The implicit `default` account is today's `~/.claude` / `~/.codex`,
9
+ * so an agent paired before accounts existed keeps running exactly as it did.
10
+ *
11
+ * **This module never reads a credential.** Signing in is done by the operator
12
+ * running the agent CLI's own login command with the account's directory; we
13
+ * only ever ask the unmodified CLI whether it is signed in (`claude auth status`
14
+ * → `loggedIn`, `codex login status` → its exit code) and keep nothing else
15
+ * from the answer — not the email, org or account id either one prints.
16
+ *
17
+ * **Accounts never switch on their own.** An exhausted subscription makes the
18
+ * agent rest until its window resets; only an operator (here, or through the
19
+ * picker in ProhostAI, which arrives as `agent.config_updated`) moves an agent
20
+ * to another account.
21
+ *
22
+ * The registry lives in a machine-wide root shared by every agent home —
23
+ * `~/.prohost-accounts`, or `$PROHOST_ACCOUNTS_ROOT` — so `account add` once
24
+ * serves every agent on the machine.
25
+ */
26
+ /** Server → CLI: an operator picked another account for this agent in ProhostAI. */
27
+ export declare const EVENT_AGENT_CONFIG_UPDATED = "agent.config_updated";
28
+ /** Label of the implicit account that is the agent CLI's own default config dir. */
29
+ export declare const DEFAULT_ACCOUNT_LABEL = "default";
30
+ /** Most accounts reported to the server in one frame (the server caps it too). */
31
+ export declare const MAX_REPORTED_ACCOUNTS = 16;
32
+ export type AccountRuntime = 'claude_code' | 'codex';
33
+ export type AccountBilling = 'subscription' | 'api_key';
34
+ export type AuthState = 'ok' | 'expired' | 'unknown';
35
+ export interface AccountRecord {
36
+ /** Random UUID made here when the account is created — the only identity the server sees. */
37
+ key: string;
38
+ /** Operator-chosen, unique per runtime on this machine. */
39
+ label: string;
40
+ runtime: AccountRuntime;
41
+ billing: AccountBilling;
42
+ /** Config directory for this login; `null` for the implicit default (the CLI's own default). */
43
+ dir: string | null;
44
+ created_at: string;
45
+ }
46
+ /** An account as it goes on the wire (`runtime_status.account` / `machine_accounts[]`). */
47
+ export interface AccountBlock {
48
+ key: string;
49
+ label: string;
50
+ runtime: AccountRuntime;
51
+ billing: AccountBilling;
52
+ auth_state: AuthState;
53
+ }
54
+ export declare class AccountError extends Error {
55
+ }
56
+ export declare function isValidLabel(label: string): boolean;
57
+ /** `claude` → `claude_code`; anything else that is not `codex` is `undefined`. */
58
+ export declare function parseAccountRuntime(value: string | undefined): AccountRuntime | undefined;
59
+ /** Machine-wide root shared by every agent home. */
60
+ export declare function accountsRoot(env?: NodeJS.ProcessEnv): string;
61
+ /** Every account on this machine. An unreadable registry is an empty one. */
62
+ export declare function loadAccounts(env?: NodeJS.ProcessEnv): AccountRecord[];
63
+ /**
64
+ * This computer's id: a random UUID made once and kept in the machine-wide
65
+ * accounts root, so every agent home on the machine sends the same one. It is
66
+ * what ProhostAI keys a machine's accounts by — the label (hostname by
67
+ * default) is display only, since two computers can share one. Nothing about
68
+ * the computer goes into it. `undefined` when it can't be stored, in which
69
+ * case the agent reports no accounts rather than risk another machine's.
70
+ */
71
+ export declare function machineId(env?: NodeJS.ProcessEnv): string | undefined;
72
+ export declare function findAccount(label: string, runtime: AccountRuntime, env?: NodeJS.ProcessEnv): AccountRecord | undefined;
73
+ /**
74
+ * The implicit default account for a runtime, created on first use so it has
75
+ * a stable key the server can recognise across restarts.
76
+ */
77
+ export declare function ensureDefaultAccount(runtime: AccountRuntime, env?: NodeJS.ProcessEnv): AccountRecord;
78
+ /** Create an account and its config directory. Signing in is the operator's next step. */
79
+ export declare function addAccount(options: {
80
+ label: string;
81
+ runtime: AccountRuntime;
82
+ billing?: AccountBilling;
83
+ }, env?: NodeJS.ProcessEnv): AccountRecord;
84
+ /**
85
+ * Forget an account and delete its config directory (the login inside it goes
86
+ * with it). The implicit default can't be removed — it is the CLI's own login.
87
+ */
88
+ export declare function removeAccount(label: string, runtime: AccountRuntime, env?: NodeJS.ProcessEnv): AccountRecord;
89
+ export declare function runtimeName(runtime: AccountRuntime): string;
90
+ /** Environment a spawn needs to run as this account. Empty for the default. */
91
+ export declare function accountEnv(record: AccountRecord | undefined): NodeJS.ProcessEnv;
92
+ /** POSIX single-quote a path for a shell command (pasted, or run as a hook). */
93
+ export declare function shellQuote(value: string): string;
94
+ /** The exact command that signs this account in, for the operator to run. */
95
+ export declare function signInCommand(record: AccountRecord): string;
96
+ export interface AuthCheck {
97
+ state: AuthState;
98
+ /** What the login itself says it bills to, when it says. */
99
+ billing?: AccountBilling;
100
+ }
101
+ /** Runs one short command with extra env; resolves exit code and stdout. Never rejects. */
102
+ export type CommandRunner = (command: string, args: string[], env: NodeJS.ProcessEnv) => Promise<{
103
+ code: number | null;
104
+ stdout: string;
105
+ }>;
106
+ export declare const defaultCommandRunner: CommandRunner;
107
+ /**
108
+ * Ask the agent CLI whether this account is signed in.
109
+ *
110
+ * Claude Code: `claude auth status --json` — only `loggedIn` and `authMethod`
111
+ * are read (the same JSON carries the email and org, which are dropped here).
112
+ * Codex: `codex login status` — exit 0 means signed in; its one line says
113
+ * whether that is ChatGPT or an API key. A CLI that can't be run, or answers
114
+ * something we don't recognise, is `unknown` — never `expired` on a guess.
115
+ */
116
+ export declare function checkAuth(record: AccountRecord, options?: {
117
+ binary?: string;
118
+ run?: CommandRunner;
119
+ }): Promise<AuthCheck>;
120
+ /**
121
+ * Poll until the operator has finished signing in, or until the deadline.
122
+ * Resolves the last check either way.
123
+ */
124
+ export declare function waitForSignIn(record: AccountRecord, options: {
125
+ timeoutMs: number;
126
+ intervalMs?: number;
127
+ check?: (record: AccountRecord) => Promise<AuthCheck>;
128
+ sleep?: (ms: number) => Promise<void>;
129
+ now?: () => number;
130
+ }): Promise<AuthCheck>;
131
+ /**
132
+ * Wire block for one account. `billing` is what the login itself reported
133
+ * when it said; the value given at `account add` is only the fallback.
134
+ */
135
+ export declare function accountBlock(record: AccountRecord, state: AuthState, billing?: AccountBilling): AccountBlock;
136
+ /**
137
+ * The account an agent home runs on: `agent.json`'s `account`, or the default.
138
+ *
139
+ * `undefined` when the home names an account that is not on this machine —
140
+ * the caller refuses to run rather than quietly using another subscription.
141
+ */
142
+ export declare function resolveAgentAccount(label: string | undefined, runtime: AccountRuntime, env?: NodeJS.ProcessEnv): AccountRecord | undefined;
143
+ export type ConfigUpdateOutcome = {
144
+ applied: true;
145
+ record: AccountRecord;
146
+ changed: boolean;
147
+ } | {
148
+ applied: false;
149
+ reason: string;
150
+ };
151
+ /**
152
+ * Apply an account picked in ProhostAI (`agent.config_updated` or
153
+ * `auth_ok.agent_config`): find it on this machine and persist it to the agent
154
+ * home's `agent.json`, so it is used from the next run and survives a restart.
155
+ *
156
+ * An unknown key, or one belonging to another runtime, changes nothing.
157
+ */
158
+ export declare function applyAccountKey(key: unknown, runtime: AccountRuntime, env?: NodeJS.ProcessEnv): ConfigUpdateOutcome;
159
+ /** Persist an account choice made on this machine (`--account`). Validates it exists. */
160
+ export declare function setAgentAccount(label: string, runtime: AccountRuntime | undefined, env?: NodeJS.ProcessEnv): void;
161
+ /** Whether the registry file exists at all (for `agent list`'s empty state). */
162
+ export declare function hasRegistry(env?: NodeJS.ProcessEnv): boolean;