@prohost/cli 0.5.0 → 0.7.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 +101 -0
- package/README.md +61 -0
- package/dist/agent/account_runtime.d.ts +75 -0
- package/dist/agent/account_runtime.js +130 -0
- package/dist/agent/accounts.d.ts +162 -0
- package/dist/agent/accounts.js +413 -0
- package/dist/agent/agent_commands.d.ts +22 -0
- package/dist/agent/agent_commands.js +170 -0
- package/dist/agent/api.d.ts +15 -1
- package/dist/agent/api.js +19 -2
- package/dist/agent/claude.d.ts +42 -0
- package/dist/agent/claude.js +218 -0
- package/dist/agent/command.d.ts +18 -1
- package/dist/agent/command.js +88 -4
- package/dist/agent/contract.d.ts +43 -0
- package/dist/agent/contract.js +13 -0
- package/dist/agent/credentials.d.ts +5 -0
- package/dist/agent/events.d.ts +104 -0
- package/dist/agent/events.js +175 -0
- package/dist/agent/list.d.ts +41 -0
- package/dist/agent/list.js +126 -0
- package/dist/agent/lock.d.ts +37 -0
- package/dist/agent/lock.js +91 -0
- package/dist/agent/run.d.ts +15 -2
- package/dist/agent/run.js +227 -19
- package/dist/agent/runtime_status.d.ts +257 -0
- package/dist/agent/runtime_status.js +681 -0
- package/dist/agent/workspace.d.ts +40 -0
- package/dist/agent/workspace.js +97 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +27 -5
- package/dist/version.d.ts +2 -2
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime, model and remaining capacity — what `agent run` tells ProhostAI about itself.
|
|
3
|
+
*
|
|
4
|
+
* ProhostAI shows every paired AI employee with its runtime ("Claude Code"),
|
|
5
|
+
* model ("Opus 5.5"), machine and how much of the operator's Claude or Codex
|
|
6
|
+
* subscription is left. Only this machine can see those things, so the harness
|
|
7
|
+
* reports them in a `runtime_status` frame on the socket it already holds.
|
|
8
|
+
*
|
|
9
|
+
* **Credentials never leave this machine, and this module never reads them.**
|
|
10
|
+
* Every number comes from the agent CLI answering with its own login:
|
|
11
|
+
*
|
|
12
|
+
* - Claude Code: the `rate_limit_event` and `system/init` lines of a run's own
|
|
13
|
+
* `stream-json` output, and — when idle — the `get_usage` control request to
|
|
14
|
+
* an unmodified `claude -p --input-format stream-json` (no model turn).
|
|
15
|
+
* - Codex: the rollout file Codex writes for each run under
|
|
16
|
+
* `$CODEX_HOME/sessions`, and — when idle — `account/rateLimits/read` on
|
|
17
|
+
* `codex app-server`.
|
|
18
|
+
*
|
|
19
|
+
* Nothing here opens the Claude Code keychain item or `~/.codex/auth.json`, and
|
|
20
|
+
* nothing forwards an account id, email or org. Only labels, percentages and
|
|
21
|
+
* reset times go on the wire. Unknown or missing data is omitted, never sent as
|
|
22
|
+
* zero.
|
|
23
|
+
*
|
|
24
|
+
* Every parser here fails soft, in the same spirit as `readClaudeStream`: the
|
|
25
|
+
* shapes belong to other tools and grow without asking us.
|
|
26
|
+
*/
|
|
27
|
+
import { spawn as nodeSpawn } from 'node:child_process';
|
|
28
|
+
import type { AccountBlock } from './accounts.js';
|
|
29
|
+
/** Feature name the server lists in `auth_ok.features` when it accepts the frame. */
|
|
30
|
+
export declare const RUNTIME_STATUS_FEATURE = "runtime_status";
|
|
31
|
+
/** Routine report cadence while connected. */
|
|
32
|
+
export declare const STATUS_INTERVAL_MS: number;
|
|
33
|
+
/**
|
|
34
|
+
* Minimum spacing between frames. The server accepts one per 30 s per socket
|
|
35
|
+
* and drops the rest; a second of margin keeps clock jitter from costing us
|
|
36
|
+
* the frame that carried a change.
|
|
37
|
+
*/
|
|
38
|
+
export declare const MIN_FRAME_SPACING_MS = 31000;
|
|
39
|
+
/** Ask the agent CLI for fresh numbers when the last ones are this old. */
|
|
40
|
+
export declare const QUOTA_REFRESH_AFTER_MS: number;
|
|
41
|
+
/** Ceiling on one idle quota probe. */
|
|
42
|
+
export declare const PROVIDER_TIMEOUT_MS = 10000;
|
|
43
|
+
export type QuotaKind = 'session' | 'weekly' | 'weekly_model';
|
|
44
|
+
export interface QuotaWindow {
|
|
45
|
+
kind: QuotaKind;
|
|
46
|
+
/** 0–100. */
|
|
47
|
+
used_percent: number;
|
|
48
|
+
/**
|
|
49
|
+
* ISO-8601, or `null` for a window whose usage is known but which has not
|
|
50
|
+
* started — Claude's 5-hour window after five idle hours. Reported as such
|
|
51
|
+
* rather than dropped: "0%, starts on next use" is an answer, a missing
|
|
52
|
+
* window is not.
|
|
53
|
+
*/
|
|
54
|
+
resets_at: string | null;
|
|
55
|
+
/** The model a `weekly_model` window is scoped to, e.g. `Fable`. */
|
|
56
|
+
model_label?: string;
|
|
57
|
+
}
|
|
58
|
+
/** What one source managed to learn. Every field is optional; absent means "didn't learn it". */
|
|
59
|
+
export interface RuntimeObservation {
|
|
60
|
+
runtime_version?: string;
|
|
61
|
+
model?: string;
|
|
62
|
+
plan_label?: string;
|
|
63
|
+
quota_windows?: QuotaWindow[];
|
|
64
|
+
}
|
|
65
|
+
/** A percentage on a 0–``scale`` scale, as 0–100 rounded to one decimal. */
|
|
66
|
+
export declare function percent(value: unknown, scale: 1 | 100): number | undefined;
|
|
67
|
+
/** Epoch seconds or an ISO string, as ISO-8601; anything else is `undefined`. */
|
|
68
|
+
export declare function isoTime(value: unknown): string | undefined;
|
|
69
|
+
/** First `X.Y.Z…` in a `--version` line, e.g. `2.1.282 (Claude Code)` or `codex-cli 0.146.0`. */
|
|
70
|
+
export declare function parseVersion(stdout: string): string | undefined;
|
|
71
|
+
/**
|
|
72
|
+
* Windows from a stream `rate_limit_event.rate_limit_info`.
|
|
73
|
+
*
|
|
74
|
+
* `unifiedWindows.*.utilization` is a FRACTION (0–1, and it can exceed 1) with
|
|
75
|
+
* epoch-second resets — unlike `get_usage`, which is 0–100 with ISO strings.
|
|
76
|
+
* The field is marked internal upstream, so an unexpected shape yields nothing.
|
|
77
|
+
*/
|
|
78
|
+
export declare function fromClaudeRateLimitInfo(info: unknown): QuotaWindow[];
|
|
79
|
+
/**
|
|
80
|
+
* Windows from a `get_usage` control response (0–100, ISO strings).
|
|
81
|
+
*
|
|
82
|
+
* A window with a usage but a `null` reset is kept, with `resets_at: null` —
|
|
83
|
+
* that is how an idle account's 5-hour window reads, and dropping it is what
|
|
84
|
+
* left paired agents showing a weekly window and nothing else (B3).
|
|
85
|
+
*
|
|
86
|
+
* Per-model weekly windows come from `rate_limits.limits[]` rows of kind
|
|
87
|
+
* `weekly_scoped` (classified on `kind`, as the schema asks, never on a
|
|
88
|
+
* label), falling back to `model_scoped[]`.
|
|
89
|
+
*/
|
|
90
|
+
export declare function fromClaudeGetUsage(usage: unknown): QuotaWindow[];
|
|
91
|
+
/** `get_usage.subscription_type` → display label; `undefined` for anything else. */
|
|
92
|
+
export declare function claudePlanLabel(subscriptionType: unknown): string | undefined;
|
|
93
|
+
/**
|
|
94
|
+
* Windows from Codex `rate_limits` (rollout, snake_case) or `rateLimits`
|
|
95
|
+
* (app-server, camelCase).
|
|
96
|
+
*
|
|
97
|
+
* Classified by window LENGTH, never by slot: on some plans `primary` is the
|
|
98
|
+
* weekly window and there is no 5-hour one at all.
|
|
99
|
+
*/
|
|
100
|
+
export declare function fromCodexRateLimits(rateLimits: unknown, lineTimestamp?: string): QuotaWindow[];
|
|
101
|
+
/** `plan_type` / `planType` → `ChatGPT Pro`; `undefined` when there is no plan. */
|
|
102
|
+
export declare function codexPlanLabel(planType: unknown): string | undefined;
|
|
103
|
+
/** Model named in the exec string itself (`-m gpt-5` / `--model gpt-5` / `--model=gpt-5`). */
|
|
104
|
+
export declare function modelFromExec(exec: string): string | undefined;
|
|
105
|
+
/** `$CODEX_HOME`, defaulting to `~/.codex` exactly as Codex does. */
|
|
106
|
+
export declare function codexHome(env?: NodeJS.ProcessEnv): string;
|
|
107
|
+
/**
|
|
108
|
+
* The top-level `model = "…"` in `$CODEX_HOME/config.toml` — Codex's default.
|
|
109
|
+
*
|
|
110
|
+
* Only the top level, before the first `[table]`: a `model` inside a profile
|
|
111
|
+
* table is not the default. A first guess only; the rollout's `turn_context`
|
|
112
|
+
* replaces it after the first run.
|
|
113
|
+
*/
|
|
114
|
+
export declare function codexConfiguredModel(env?: NodeJS.ProcessEnv): string | undefined;
|
|
115
|
+
/**
|
|
116
|
+
* Locate the rollout for one thread: `sessions/YYYY/MM/DD/rollout-*-<thread_id>.jsonl`.
|
|
117
|
+
*
|
|
118
|
+
* The date directory is local time and a resumed thread lives under the day it
|
|
119
|
+
* started, so the date is never guessed — days are scanned newest first.
|
|
120
|
+
*/
|
|
121
|
+
export declare function findCodexRollout(threadId: string, env?: NodeJS.ProcessEnv): string | undefined;
|
|
122
|
+
/**
|
|
123
|
+
* What a Codex rollout says: the model the turn really used (after `-m`,
|
|
124
|
+
* profiles and `-c`), the CLI version, and the last rate-limit snapshot.
|
|
125
|
+
*/
|
|
126
|
+
export declare function parseCodexRollout(content: string): RuntimeObservation;
|
|
127
|
+
/** Read the rollout for a finished Codex thread, if it can be found. */
|
|
128
|
+
export declare function readCodexObservation(threadId: string | undefined, env?: NodeJS.ProcessEnv): RuntimeObservation | undefined;
|
|
129
|
+
/**
|
|
130
|
+
* Where fresh numbers come from when no run has produced any lately.
|
|
131
|
+
*
|
|
132
|
+
* Implementations ask the unmodified agent CLI through its own protocol, so
|
|
133
|
+
* the CLI answers with its own login and this process never holds a
|
|
134
|
+
* credential. Resolve `undefined` on any failure — the reporter keeps what it
|
|
135
|
+
* had and omits what it doesn't know.
|
|
136
|
+
*/
|
|
137
|
+
export interface QuotaProvider {
|
|
138
|
+
refresh(): Promise<RuntimeObservation | undefined>;
|
|
139
|
+
}
|
|
140
|
+
type SpawnFn = typeof nodeSpawn;
|
|
141
|
+
/**
|
|
142
|
+
* Claude Code's `get_usage` control request. No model turn is made, so no
|
|
143
|
+
* quota is spent. The `initialize` response carries account identity; it is
|
|
144
|
+
* never read.
|
|
145
|
+
*/
|
|
146
|
+
export declare function claudeQuotaProvider(options: {
|
|
147
|
+
binary: string;
|
|
148
|
+
spawnImpl?: SpawnFn;
|
|
149
|
+
timeoutMs?: number;
|
|
150
|
+
/** Account environment, read at each probe so a switch applies to the next one. */
|
|
151
|
+
env?: () => NodeJS.ProcessEnv;
|
|
152
|
+
}): QuotaProvider;
|
|
153
|
+
/** Codex's `account/rateLimits/read` over `codex app-server` stdio JSON-RPC. */
|
|
154
|
+
export declare function codexQuotaProvider(options: {
|
|
155
|
+
binary: string;
|
|
156
|
+
clientVersion: string;
|
|
157
|
+
spawnImpl?: SpawnFn;
|
|
158
|
+
timeoutMs?: number;
|
|
159
|
+
/** Account environment, read at each probe so a switch applies to the next one. */
|
|
160
|
+
env?: () => NodeJS.ProcessEnv;
|
|
161
|
+
}): QuotaProvider;
|
|
162
|
+
/**
|
|
163
|
+
* Fold new windows into the ones already known, one KIND at a time.
|
|
164
|
+
*
|
|
165
|
+
* Sources see different slices: a run's `rate_limit_event` carries only the
|
|
166
|
+
* 5-hour and weekly windows (and omits one whose reset has passed), while the
|
|
167
|
+
* idle `get_usage` probe also carries the per-model weekly ones. Replacing the
|
|
168
|
+
* whole list with whatever arrived last is how a stream event used to erase
|
|
169
|
+
* the windows it simply didn't mention. A kind the observation names replaces
|
|
170
|
+
* every window of that kind; a kind it doesn't name is kept.
|
|
171
|
+
*/
|
|
172
|
+
export declare function mergeWindows(known: QuotaWindow[] | undefined, observed: QuotaWindow[]): QuotaWindow[];
|
|
173
|
+
/** What the harness knows about accounts on this machine, for the frame. */
|
|
174
|
+
export interface AccountsSnapshot {
|
|
175
|
+
/** The account this agent runs on. */
|
|
176
|
+
current?: AccountBlock;
|
|
177
|
+
/**
|
|
178
|
+
* Every account on the machine, identity and sign-in state only. Omitted
|
|
179
|
+
* when the list would be partial: the server treats it as authoritative
|
|
180
|
+
* and would forget whatever it doesn't name.
|
|
181
|
+
*/
|
|
182
|
+
all?: AccountBlock[];
|
|
183
|
+
}
|
|
184
|
+
export interface RuntimeStatusReporterOptions {
|
|
185
|
+
/** `claude_code` | `codex` | `goose` | `custom`. */
|
|
186
|
+
runtime: string;
|
|
187
|
+
/** Idle quota source; omit for a runtime that has none. */
|
|
188
|
+
provider?: QuotaProvider;
|
|
189
|
+
/**
|
|
190
|
+
* Accounts on this machine and which one this agent uses. Re-read on the
|
|
191
|
+
* same cadence as the idle quota probe (it asks each login whether it is
|
|
192
|
+
* signed in), and straight away after {@link RuntimeStatusReporter.accountsChanged}.
|
|
193
|
+
*/
|
|
194
|
+
accounts?: () => Promise<AccountsSnapshot | undefined>;
|
|
195
|
+
/** A run is executing — it will bring fresh numbers, so don't probe beside it. */
|
|
196
|
+
isBusy?: () => boolean;
|
|
197
|
+
now?: () => number;
|
|
198
|
+
/** Timer seam for tests. Returns a cancel function. */
|
|
199
|
+
schedule?: (fn: () => void, ms: number) => () => void;
|
|
200
|
+
intervalMs?: number;
|
|
201
|
+
log?: (line: string) => void;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Holds the latest runtime snapshot and decides when to send it.
|
|
205
|
+
*
|
|
206
|
+
* Sends only while attached — i.e. only on a socket whose server advertised
|
|
207
|
+
* {@link RUNTIME_STATUS_FEATURE}. Cadence: once on attach, every
|
|
208
|
+
* {@link STATUS_INTERVAL_MS}, and promptly when the model, plan or runtime
|
|
209
|
+
* version changes or capacity crosses a band — but never closer together than
|
|
210
|
+
* {@link MIN_FRAME_SPACING_MS}.
|
|
211
|
+
*/
|
|
212
|
+
export declare class RuntimeStatusReporter {
|
|
213
|
+
private readonly options;
|
|
214
|
+
private readonly now;
|
|
215
|
+
private readonly schedule;
|
|
216
|
+
private state;
|
|
217
|
+
private quotaObservedAt;
|
|
218
|
+
private send;
|
|
219
|
+
private lastSentAt;
|
|
220
|
+
private cancelPending;
|
|
221
|
+
private cancelTick;
|
|
222
|
+
private refreshing;
|
|
223
|
+
private accountGeneration;
|
|
224
|
+
private accounts;
|
|
225
|
+
private accountsObservedAt;
|
|
226
|
+
constructor(options: RuntimeStatusReporterOptions);
|
|
227
|
+
/** Merge what a source learned. Absent fields keep their previous value. */
|
|
228
|
+
observe(observation: RuntimeObservation | undefined): void;
|
|
229
|
+
/**
|
|
230
|
+
* Record the accounts snapshot. Moving to another account drops what was
|
|
231
|
+
* known about the previous one's capacity and plan — they describe a
|
|
232
|
+
* different subscription — and asks for fresh numbers straight away.
|
|
233
|
+
*/
|
|
234
|
+
setAccounts(snapshot: AccountsSnapshot | undefined, options?: {
|
|
235
|
+
quiet?: boolean;
|
|
236
|
+
}): void;
|
|
237
|
+
/**
|
|
238
|
+
* The account choice changed (an operator picked one in ProhostAI): re-read
|
|
239
|
+
* accounts and capacity on the next tick rather than in fifteen minutes.
|
|
240
|
+
*/
|
|
241
|
+
accountsChanged(): void;
|
|
242
|
+
/** The model to report on a run's completion, when nothing more specific is known. */
|
|
243
|
+
model(): string | undefined;
|
|
244
|
+
/** Start reporting on a socket that advertised the feature. */
|
|
245
|
+
attach(send: (frame: Record<string, unknown>) => void): void;
|
|
246
|
+
/** Stop reporting — the socket is gone. */
|
|
247
|
+
detach(): void;
|
|
248
|
+
/** The frame as it would be sent now. */
|
|
249
|
+
frame(): Record<string, unknown>;
|
|
250
|
+
/** Routine tick: refresh idle quota if it is stale, send, and re-arm. */
|
|
251
|
+
private tick;
|
|
252
|
+
private refresh;
|
|
253
|
+
/** Send now if spacing allows, else once it does. Coalesces. */
|
|
254
|
+
private sendSoon;
|
|
255
|
+
private flush;
|
|
256
|
+
}
|
|
257
|
+
export {};
|