@indigoai-us/hq-cli 5.345.91 → 5.345.93

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.
Files changed (47) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/assets/scaffold/core/scripts/qmd-reindex-bg.sh +17 -4
  3. package/dist/command-catalog.generated.d.ts +252 -0
  4. package/dist/command-catalog.generated.js +306 -0
  5. package/dist/command-registration-plan.d.ts +6 -0
  6. package/dist/command-registration-plan.js +1 -0
  7. package/dist/commands/capacity.d.ts +71 -0
  8. package/dist/commands/capacity.js +666 -0
  9. package/dist/commands/integrations-connect.js +64 -5
  10. package/dist/commands/lanes-accounts.d.ts +1 -0
  11. package/dist/commands/lanes-accounts.js +6 -0
  12. package/dist/lib/capacity/accounts.d.ts +36 -0
  13. package/dist/lib/capacity/accounts.js +90 -0
  14. package/dist/lib/capacity/collect.d.ts +33 -0
  15. package/dist/lib/capacity/collect.js +164 -0
  16. package/dist/lib/capacity/collectors/claude-oauth.d.ts +145 -0
  17. package/dist/lib/capacity/collectors/claude-oauth.js +1211 -0
  18. package/dist/lib/capacity/collectors/claude-statusline.d.ts +72 -0
  19. package/dist/lib/capacity/collectors/claude-statusline.js +436 -0
  20. package/dist/lib/capacity/collectors/codex.d.ts +120 -0
  21. package/dist/lib/capacity/collectors/codex.js +1134 -0
  22. package/dist/lib/capacity/collectors/grok.d.ts +86 -0
  23. package/dist/lib/capacity/collectors/grok.js +588 -0
  24. package/dist/lib/capacity/lane-login.d.ts +29 -0
  25. package/dist/lib/capacity/lane-login.js +91 -0
  26. package/dist/lib/capacity/ledger.d.ts +399 -0
  27. package/dist/lib/capacity/ledger.js +1441 -0
  28. package/dist/lib/capacity/project.d.ts +12 -0
  29. package/dist/lib/capacity/project.js +264 -0
  30. package/dist/lib/capacity/read.d.ts +62 -0
  31. package/dist/lib/capacity/read.js +264 -0
  32. package/dist/lib/capacity/refusal.d.ts +30 -0
  33. package/dist/lib/capacity/refusal.js +183 -0
  34. package/dist/lib/capacity/scheduler.d.ts +76 -0
  35. package/dist/lib/capacity/scheduler.js +800 -0
  36. package/dist/lib/capacity/sign-in.d.ts +103 -0
  37. package/dist/lib/capacity/sign-in.js +517 -0
  38. package/dist/lib/capacity/widget.d.ts +22 -0
  39. package/dist/lib/capacity/widget.js +418 -0
  40. package/dist/lib/lanes/spawn.js +12 -0
  41. package/dist/lib/mesh/live/daemon/run.d.ts +5 -0
  42. package/dist/lib/mesh/live/daemon/run.js +29 -1
  43. package/dist/lib/search-index/background.d.ts +31 -8
  44. package/dist/lib/search-index/background.js +86 -7
  45. package/dist/lib/search-index/index.d.ts +24 -0
  46. package/dist/lib/search-index/index.js +25 -0
  47. package/package.json +1 -1
@@ -0,0 +1,666 @@
1
+ import * as crypto from "node:crypto";
2
+ import * as fs from "node:fs";
3
+ import * as os from "node:os";
4
+ import * as path from "node:path";
5
+ import * as readline from "node:readline";
6
+ import { LedgerCorrupt, LedgerMissing, READING_SOURCES, capacityRoot, nowViewJsonSchema, readCollectorMark, readLedgerEvents, readNow, rebuildNow, setCollectorEnabled, } from "../lib/capacity/ledger.js";
7
+ import { listCapacityAccounts, registryAccountIds } from "../lib/capacity/accounts.js";
8
+ import { claudeIdentityKeys } from "../lib/capacity/collectors/claude-oauth.js";
9
+ import { projectCapacityView } from "../lib/capacity/project.js";
10
+ import { capacityWidgetTimeZone, renderCapacityWidget } from "../lib/capacity/widget.js";
11
+ import { resolveLanesHqRoot } from "../lib/lanes/hq-root.js";
12
+ import { SignInUsageError, executeCapacityLogin, formatCredits, formatLoginText, formatRefreshLine, formatRoster, generateAccountId, loginJson, presentAccount, promptsAllowed, removeCapacityAccount, renameCapacityAccount, rosterFrom, } from "../lib/capacity/sign-in.js";
13
+ import { ingestClaudeStatuslinePayload, installStatuslineSpoolScript } from "../lib/capacity/collectors/claude-statusline.js";
14
+ import { CapacityUsageError, REFUSAL_HELP, ingestRefusal, } from "../lib/capacity/refusal.js";
15
+ export const CAPACITY_SCHEMA_VERSION = nowViewJsonSchema.$id;
16
+ export const DOCTOR_SCHEMA_VERSION = "hq-capacity-doctor.v1";
17
+ export const PARSER_FAILURE_WINDOW_MS = 24 * 60 * 60 * 1000;
18
+ /** now.json older than this is stale: the collector is not keeping the view current. */
19
+ export const CAPACITY_STALE_MS = 5 * 60 * 1000;
20
+ /** Short versions inside test/fixtures/capacity/cli-versions.json. */
21
+ export const PINNED_CLI_VERSIONS = {
22
+ claude: "2.1.152",
23
+ codex: "0.162.1",
24
+ grok: "1.0.25",
25
+ };
26
+ const TABLE_HEADERS = [
27
+ "LIMIT ID",
28
+ "KIND",
29
+ "SCOPE",
30
+ "USED PERCENT",
31
+ "RESETS IN",
32
+ "CREDITS",
33
+ "PLAN",
34
+ "SOURCE",
35
+ "OBSERVED-AT AGE",
36
+ "STATE",
37
+ ];
38
+ const HISTORY_HEADERS = [
39
+ "OBSERVED AT",
40
+ "PROVIDER",
41
+ "LIMIT ID",
42
+ "KIND",
43
+ "SCOPE",
44
+ "USED PERCENT",
45
+ "SOURCE",
46
+ "CLI VERSION",
47
+ ];
48
+ export function capacityOptionsFromEnv(env = process.env) {
49
+ const raw = env.HQ_CAPACITY_NOW?.trim();
50
+ if (!raw)
51
+ return { env };
52
+ const parsed = Date.parse(raw);
53
+ if (Number.isNaN(parsed)) {
54
+ throw new Error(`HQ_CAPACITY_NOW must be a UTC timestamp, got '${raw}'`);
55
+ }
56
+ return { env, now: () => new Date(raw) };
57
+ }
58
+ export function withSchemaVersion(view) {
59
+ return { ...view, schemaVersion: CAPACITY_SCHEMA_VERSION };
60
+ }
61
+ /** Stale when generatedAt is missing or older than five minutes. */
62
+ export function capacityFreshness(generatedAt, nowMs) {
63
+ const at = Date.parse(generatedAt);
64
+ if (Number.isNaN(at)) {
65
+ return {
66
+ stale: true,
67
+ ageMs: 0,
68
+ message: "collector not running, numbers may be stale; last update unknown",
69
+ };
70
+ }
71
+ const ageMs = Math.max(0, nowMs - at);
72
+ const stale = ageMs > CAPACITY_STALE_MS;
73
+ return {
74
+ stale,
75
+ ageMs,
76
+ message: stale
77
+ ? `collector not running, numbers may be stale; last update ${formatAge(generatedAt, nowMs)}`
78
+ : null,
79
+ };
80
+ }
81
+ export function parseSince(since, nowMs) {
82
+ const relative = /^(\d+)(m|h|d)$/.exec(since.trim());
83
+ if (relative) {
84
+ const amount = Number(relative[1]);
85
+ const unit = relative[2];
86
+ const ms = unit === "m" ? 60_000 : unit === "h" ? 3_600_000 : 86_400_000;
87
+ return nowMs - amount * ms;
88
+ }
89
+ if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?Z$/.test(since) && !Number.isNaN(Date.parse(since))) {
90
+ return Date.parse(since);
91
+ }
92
+ throw new Error(`--since must be a duration like 24h or a UTC timestamp, got '${since}'`);
93
+ }
94
+ export function selectReadings(events, filter = {}) {
95
+ return events.filter(isReading).filter((reading) => {
96
+ if (filter.provider && reading.provider !== filter.provider)
97
+ return false;
98
+ if (filter.sinceMs !== undefined && Date.parse(reading.observedAt) < filter.sinceMs)
99
+ return false;
100
+ return true;
101
+ });
102
+ }
103
+ export function formatCapacityTable(view, nowMs, accounts = []) {
104
+ if (view.providers.length === 0)
105
+ return "No readings.";
106
+ return view.providers.map((provider) => {
107
+ const head = `${provider.provider} ${provider.state}`;
108
+ if (!provider.accounts || provider.accounts.length === 0) {
109
+ return `${head}\n${formatLimitTable(provider.limits, nowMs)}`;
110
+ }
111
+ const blocks = provider.accounts.map((account) => {
112
+ const labels = account.labels.map((label) => {
113
+ const row = accounts.find((item) => item.provider === provider.provider && item.label === label);
114
+ return row?.lanes === false ? `${label} (usage only)` : label;
115
+ }).join(", ");
116
+ const title = account.organization ? `${labels} ${account.organization}` : labels;
117
+ const presented = presentAccount(account, provider.provider, os.homedir());
118
+ if (presented.state === "expired" || presented.state === "signed_out") {
119
+ const reason = presented.reason ?? account.reason ?? presented.state;
120
+ const fix = presented.command ? formatRefreshLine(provider.provider, presented.command) : "";
121
+ return fix ? `${title}\n${reason}\n${fix}` : `${title}\n${reason}`;
122
+ }
123
+ return `${title}\n${formatLimitTable(account.limits, nowMs)}`;
124
+ });
125
+ return [head, ...blocks].join("\n");
126
+ }).join("\n\n");
127
+ }
128
+ function formatLimitTable(limits, nowMs) {
129
+ const rows = limits.map((limit) => [
130
+ limit.limitId,
131
+ limit.kind,
132
+ cell(limit.scope),
133
+ limit.usedPercent === null ? "—" : `${limit.usedPercent}%`,
134
+ formatSpan(limit.secondsToReset),
135
+ formatCredits(limit.balance),
136
+ cell(limit.planType),
137
+ limit.source,
138
+ formatAge(limit.observedAt, nowMs),
139
+ limit.state,
140
+ ]);
141
+ return formatTable(TABLE_HEADERS, rows);
142
+ }
143
+ export function formatHistory(readings) {
144
+ if (readings.length === 0)
145
+ return "No readings.";
146
+ const rows = readings.map((reading) => [
147
+ reading.observedAt,
148
+ reading.provider,
149
+ reading.limit.limitId,
150
+ reading.limit.kind,
151
+ cell(reading.limit.scope),
152
+ reading.limit.usedPercent === null ? "—" : `${reading.limit.usedPercent}%`,
153
+ reading.source,
154
+ reading.cliVersion,
155
+ ]);
156
+ return formatTable(HISTORY_HEADERS, rows);
157
+ }
158
+ export function buildDoctorReport(options) {
159
+ const resolved = capacityClock(options);
160
+ const view = loadNow(resolved.options);
161
+ const events = readLedgerEvents(resolved.options);
162
+ const readings = selectReadings(events);
163
+ const cutoff = resolved.nowMs - PARSER_FAILURE_WINDOW_MS;
164
+ const parserFailures = events.filter(isParserFailure).filter((event) => {
165
+ const at = Date.parse(event.observedAt);
166
+ return at >= cutoff && at <= resolved.nowMs;
167
+ });
168
+ const sources = READING_SOURCES.map((source) => {
169
+ const hits = readings.filter((reading) => reading.source === source);
170
+ const last = hits[hits.length - 1];
171
+ return { source, lastSuccess: last?.observedAt ?? null };
172
+ });
173
+ const installed = new Map();
174
+ for (const reading of readings) {
175
+ const at = Date.parse(reading.observedAt);
176
+ const prev = installed.get(reading.provider);
177
+ if (!prev || at >= prev.at)
178
+ installed.set(reading.provider, { at, version: reading.cliVersion });
179
+ }
180
+ const names = new Set([
181
+ ...Object.keys(PINNED_CLI_VERSIONS),
182
+ ...readings.map((reading) => reading.provider),
183
+ ...view.providers.map((provider) => provider.provider),
184
+ ]);
185
+ const versions = [...names].sort((a, b) => a.localeCompare(b)).map((provider) => {
186
+ const pinned = PINNED_CLI_VERSIONS[provider] ?? null;
187
+ const seen = installed.get(provider)?.version ?? null;
188
+ return { provider, pinned, installed: seen, match: pinned !== null && seen === pinned };
189
+ });
190
+ const providers = view.providers.map((provider) => ({
191
+ provider: provider.provider,
192
+ state: provider.state,
193
+ }));
194
+ const freshness = capacityFreshness(view.generatedAt, resolved.nowMs);
195
+ return {
196
+ schemaVersion: DOCTOR_SCHEMA_VERSION,
197
+ collector: readCollectorMark(resolved.options)?.enabled ? "on" : "off",
198
+ sources,
199
+ versions,
200
+ parserFailures,
201
+ providers,
202
+ ok: providers.every((provider) => provider.state !== "unknown"),
203
+ stale: freshness.stale,
204
+ ageMs: freshness.ageMs,
205
+ };
206
+ }
207
+ export function formatDoctor(report) {
208
+ const sources = formatTable(["SOURCE", "LAST SUCCESS"], report.sources.map((source) => [source.source, source.lastSuccess ?? "—"]));
209
+ const versions = formatTable(["PROVIDER", "PINNED", "INSTALLED", "MATCH"], report.versions.map((version) => [
210
+ version.provider,
211
+ version.pinned ?? "—",
212
+ version.installed ?? "—",
213
+ version.match ? "yes" : "no",
214
+ ]));
215
+ const failures = report.parserFailures.length === 0
216
+ ? "none"
217
+ : report.parserFailures.map((event) => `${event.observedAt} ${event.provider} ${event.source} ${event.error}`).join("\n");
218
+ const states = report.providers.length === 0
219
+ ? "none"
220
+ : report.providers.map((provider) => `${provider.provider} ${provider.state}`).join("\n");
221
+ const warning = report.stale
222
+ ? report.ageMs === 0
223
+ ? "collector not running, numbers may be stale; last update unknown"
224
+ : `collector not running, numbers may be stale; last update ${formatSpan(Math.floor(report.ageMs / 1000))} ago`
225
+ : null;
226
+ return [
227
+ ...(warning ? [warning] : []),
228
+ `collector ${report.collector}`,
229
+ sources,
230
+ versions,
231
+ "parser_failure (24h)",
232
+ failures,
233
+ "provider state",
234
+ states,
235
+ ].join("\n");
236
+ }
237
+ export function registerCapacityCommand(program) {
238
+ const capacity = program
239
+ .command("capacity")
240
+ .description("Show provider capacity from the local ledger (read-only; enable, login, and ingest write to it)")
241
+ .addHelpText("after", "\nCodex rate limits are read with `codex app-server` only when Codex has been idle for 30 minutes and no codex process is running. account/rateLimits/read does not refresh tokens, but starting app-server can still rewrite ~/.codex/auth.json for a managed ChatGPT login, so the read stays idle-gated.\n")
242
+ .option("--json", "Print now.json plus schemaVersion")
243
+ .option("--widget", "Print an HTML usage card for chat");
244
+ capacity.action((opts) => {
245
+ runCli(() => {
246
+ if (opts.json && opts.widget) {
247
+ throw new CapacityUsageError("--widget cannot be combined with --json");
248
+ }
249
+ const clock = capacityClock();
250
+ if (opts.widget) {
251
+ console.log(widgetStdout(clock.options, clock.nowMs));
252
+ return;
253
+ }
254
+ const presented = presentLedger(clock.options);
255
+ const view = presented.view;
256
+ const freshness = capacityFreshness(view.generatedAt, clock.nowMs);
257
+ if (freshness.message)
258
+ console.error(freshness.message);
259
+ const mark = readCollectorMark(clock.options);
260
+ const body = {
261
+ ...withSchemaVersion(view),
262
+ stale: freshness.stale,
263
+ ageMs: freshness.ageMs,
264
+ collectorEnabled: mark ? mark.enabled : null,
265
+ };
266
+ console.log(opts.json
267
+ ? JSON.stringify(body, null, 2)
268
+ : formatCapacityTable(view, clock.nowMs, presented.accounts));
269
+ });
270
+ });
271
+ capacity
272
+ .command("history")
273
+ .description("Print readings from the ledger event log")
274
+ .option("--provider <name>", "Only this provider")
275
+ .option("--since <when>", "Duration (24h, 7d, 30m) or a UTC timestamp")
276
+ .option("--json", "Print readings as JSON")
277
+ .action((opts, command) => {
278
+ runCli(() => {
279
+ const clock = capacityClock();
280
+ const sinceMs = opts.since ? parseSince(opts.since, clock.nowMs) : undefined;
281
+ const readings = selectReadings(readLedgerEvents(clock.options), {
282
+ provider: opts.provider,
283
+ sinceMs,
284
+ });
285
+ console.log(wantsJson(opts, command) ? JSON.stringify(readings, null, 2) : formatHistory(readings));
286
+ });
287
+ });
288
+ capacity
289
+ .command("doctor")
290
+ .description("Report last success, pinned versus installed CLI versions, and parser failures")
291
+ .option("--json", "Print JSON")
292
+ .action((opts, command) => {
293
+ runCli(() => {
294
+ const report = buildDoctorReport(capacityClock().options);
295
+ console.log(wantsJson(opts, command) ? JSON.stringify(report, null, 2) : formatDoctor(report));
296
+ if (!report.ok || report.stale)
297
+ process.exitCode = 1;
298
+ });
299
+ });
300
+ capacity
301
+ .command("enable")
302
+ .description("Create the ledger directory and mark the collector on")
303
+ .action(() => {
304
+ runCli(() => {
305
+ const options = capacityOptionsFromEnv();
306
+ setCollectorEnabled(true, options);
307
+ installStatuslineSpoolScript(capacityRoot(options.env ?? process.env, options.home));
308
+ rebuildNow(options);
309
+ console.log(`Collector on. ${capacityRoot(options.env ?? process.env)}`);
310
+ });
311
+ });
312
+ capacity
313
+ .command("disable")
314
+ .description("Mark the collector off without deleting readings")
315
+ .action(() => {
316
+ runCli(() => {
317
+ const options = capacityOptionsFromEnv();
318
+ setCollectorEnabled(false, options);
319
+ console.log(`Collector off. ${capacityRoot(options.env ?? process.env)}`);
320
+ });
321
+ });
322
+ const ingest = capacity
323
+ .command("ingest")
324
+ .description("Record a provider capacity reading");
325
+ ingest
326
+ .command("claude-statusline")
327
+ .description("Read one Claude Code statusline JSON payload from stdin and record its rate limits")
328
+ .action(() => {
329
+ runCli(() => {
330
+ if (process.stdin.isTTY) {
331
+ console.error("claude-statusline ingest reads a payload on stdin, not a terminal.");
332
+ process.exitCode = 1;
333
+ return;
334
+ }
335
+ const text = fs.readFileSync(0, "utf8");
336
+ const result = ingestClaudeStatuslinePayload(text, capacityOptionsFromEnv());
337
+ if (result.error) {
338
+ console.error(result.error);
339
+ process.exitCode = 1;
340
+ return;
341
+ }
342
+ if (result.failures.length > 0) {
343
+ console.error(result.failures.map((item) => item.error).join("\n"));
344
+ }
345
+ const count = result.readings.length;
346
+ console.log(count === 0 ? "No rate limits in payload." : `Recorded ${count} readings.`);
347
+ });
348
+ });
349
+ ingest
350
+ .command("refusal")
351
+ .description("Record a classified provider rate-limit refusal as an exhausted reading")
352
+ .option("--provider <name>", "claude, codex, or grok")
353
+ .option("--limit-id <id>", "Limit to mark; default is the provider's weekly_all id in now.json, else weekly")
354
+ .option("--resets-at <when>", "UTC timestamp or epoch when this limit resets")
355
+ .option("--account <hash>", "Hashed account id; default is the account on the chosen limit")
356
+ .option("--json", "Print the recorded limit as JSON")
357
+ .addHelpText("after", `\n${REFUSAL_HELP}\n`)
358
+ .action((opts, command) => {
359
+ runCli(() => {
360
+ const record = ingestRefusal({
361
+ provider: opts.provider,
362
+ limitId: opts.limitId,
363
+ resetsAt: opts.resetsAt,
364
+ accountId: opts.account,
365
+ }, capacityOptionsFromEnv());
366
+ if (wantsJson(opts, command)) {
367
+ console.log(JSON.stringify(record, null, 2));
368
+ return;
369
+ }
370
+ console.log(`Recorded refusal for ${record.provider} ${record.limitId}; state ${record.state}.`);
371
+ });
372
+ });
373
+ capacity
374
+ .command("login")
375
+ .description("Show signed-in accounts, or sign into a provider through hq lanes accounts login")
376
+ .argument("[provider]", "claude, codex, or grok")
377
+ .option("--account <name>", "Registry account id. Omit to name it from the organization after the first read")
378
+ .option("--repair", "Sign in again for signed-out accounts. Expired accounts are listed, not logged in again")
379
+ .option("--json", "Print the account list and commands as JSON. Never prompts")
380
+ .action(async (provider, opts, command) => {
381
+ try {
382
+ process.exitCode = 0;
383
+ const clock = capacityClock();
384
+ const rows = readRoster(clock.options);
385
+ const interactive = promptsAllowed({
386
+ stdinTty: Boolean(process.stdin.isTTY),
387
+ stdoutTty: Boolean(process.stdout.isTTY),
388
+ json: wantsJson(opts, command),
389
+ });
390
+ const result = await executeCapacityLogin({
391
+ provider,
392
+ account: opts.account,
393
+ repair: opts.repair,
394
+ json: wantsJson(opts, command),
395
+ interactive,
396
+ }, {
397
+ rows,
398
+ nowMs: clock.nowMs,
399
+ ask: confirmSignIn,
400
+ loginAccount: (name, accountId) => loginThroughLanes(name, accountId),
401
+ readAccount: async (name, accountId, home) => {
402
+ const { collectCapacityOnce } = await import("../lib/capacity/collect.js");
403
+ return collectCapacityOnce({
404
+ accounts: [{ provider: parseProvider(name), label: accountId, home, source: "registry" }],
405
+ env: process.env,
406
+ hqRoot: lanesRoot(),
407
+ includeSpool: false,
408
+ });
409
+ },
410
+ renameAccount: (name, from, to) => renameCapacityAccount(lanesRoot(), name, from, to),
411
+ takenIds: (name) => registryAccountIds(lanesRoot(), name),
412
+ formatReading: (view, name, label, home) => formatAccountUsage(view, name, label, home, capacityClock().nowMs),
413
+ newAccountId: () => generateAccountId(crypto.randomBytes(4).toString("hex")),
414
+ });
415
+ if (wantsJson(opts, command)) {
416
+ console.log(JSON.stringify(result.json ?? loginJson(rows, clock.nowMs, provider), null, 2));
417
+ return;
418
+ }
419
+ console.log(result.text);
420
+ }
421
+ catch (err) {
422
+ failCapacity(err);
423
+ }
424
+ });
425
+ const accounts = capacity
426
+ .command("accounts")
427
+ .description("List provider accounts from the registry and default homes")
428
+ .option("--json", "Print JSON")
429
+ .action((opts, command) => {
430
+ runCli(() => {
431
+ const clock = capacityClock();
432
+ const rows = readRoster(clock.options);
433
+ if (wantsJson(opts, command)) {
434
+ console.log(JSON.stringify(loginJson(rows, clock.nowMs), null, 2));
435
+ return;
436
+ }
437
+ console.log(rows.length === 0 ? "No accounts." : formatRoster(rows, clock.nowMs));
438
+ });
439
+ });
440
+ accounts
441
+ .command("rename")
442
+ .description("Rename a registry account id. Does not touch the provider credential")
443
+ .argument("<provider>", "claude, codex, or grok")
444
+ .argument("<id>", "Current account id")
445
+ .argument("<new-id>", "New account id")
446
+ .option("--force", "Rename even when a live lane references the account")
447
+ .action((provider, id, newId, opts) => {
448
+ runCli(() => {
449
+ renameCapacityAccount(lanesRoot(), provider, id, newId, { force: opts.force });
450
+ console.log(`Renamed ${provider} account ${id} to ${newId}.`);
451
+ });
452
+ });
453
+ accounts
454
+ .command("remove")
455
+ .description("Forget a registry account. Does not delete its home unless --delete-home is set")
456
+ .argument("<provider>", "claude, codex, or grok")
457
+ .argument("<id>", "Account id")
458
+ .option("--delete-home", "Delete the account home. Refuses the default provider home, the home directory, and any path outside ~/.provider-id")
459
+ .option("--force", "Rename or remove even when a live lane references the account")
460
+ .action((provider, id, opts) => {
461
+ runCli(() => {
462
+ const result = removeCapacityAccount(lanesRoot(), provider, id, {
463
+ deleteHome: opts.deleteHome,
464
+ force: opts.force,
465
+ hostname: os.hostname(),
466
+ homedir: os.homedir,
467
+ });
468
+ console.log(result.removedHome
469
+ ? `Removed ${provider} account ${id} and deleted its home.`
470
+ : `Removed ${provider} account ${id}.`);
471
+ });
472
+ });
473
+ return capacity;
474
+ }
475
+ function readRoster(options) {
476
+ const presented = presentLedger(options, { missingOk: true });
477
+ return rosterFrom(presented.accounts, presented.view, os.homedir());
478
+ }
479
+ function widgetStdout(options, nowMs) {
480
+ const timeZone = capacityWidgetTimeZone(options.env ?? process.env);
481
+ try {
482
+ const presented = presentLedger(options);
483
+ const freshness = capacityFreshness(presented.view.generatedAt, nowMs);
484
+ const mark = readCollectorMark(options);
485
+ return renderCapacityWidget({
486
+ view: presented.view,
487
+ accounts: presented.accounts,
488
+ nowMs,
489
+ timeZone,
490
+ stale: freshness.stale,
491
+ collectorEnabled: mark ? mark.enabled : null,
492
+ });
493
+ }
494
+ catch (err) {
495
+ if (!(err instanceof LedgerMissing))
496
+ throw err;
497
+ return renderCapacityWidget({ nowMs, timeZone, ledgerMissing: true });
498
+ }
499
+ }
500
+ function presentLedger(options, mode = {}) {
501
+ const accounts = listCapacityAccounts({
502
+ env: options.env,
503
+ hqRoot: options.env?.HQ_ROOT,
504
+ home: options.home ?? options.env?.HOME,
505
+ });
506
+ let stored;
507
+ try {
508
+ stored = loadNow(options);
509
+ }
510
+ catch (err) {
511
+ if (!(err instanceof LedgerMissing) || !mode.missingOk)
512
+ throw err;
513
+ const now = options.now ? options.now() : new Date();
514
+ stored = {
515
+ machineId: "local",
516
+ hqCliVersion: "unknown",
517
+ generatedAt: now.toISOString(),
518
+ providers: [],
519
+ };
520
+ }
521
+ const root = options.root ?? capacityRoot(options.env ?? process.env, options.home);
522
+ const identityKeys = claudeIdentityKeys(root);
523
+ return {
524
+ accounts,
525
+ view: projectCapacityView(stored, {
526
+ accounts,
527
+ identityKeyForAccount: (accountId) => identityKeys.get(accountId) ?? null,
528
+ }),
529
+ };
530
+ }
531
+ function formatAccountUsage(view, provider, label, home, nowMs) {
532
+ const group = view.providers.find((item) => item.provider === provider);
533
+ const resolved = path.resolve(home);
534
+ const account = group?.accounts?.find((item) => item.homes.some((itemHome) => path.resolve(itemHome) === resolved));
535
+ if (!group || !account)
536
+ return formatLoginText([], nowMs);
537
+ const titled = {
538
+ ...account,
539
+ labels: [label, ...account.labels.filter((item) => item !== label)],
540
+ };
541
+ return formatCapacityTable({
542
+ ...view,
543
+ providers: [{ ...group, accounts: [titled] }],
544
+ }, nowMs);
545
+ }
546
+ function parseProvider(value) {
547
+ if (value === "claude" || value === "codex" || value === "grok")
548
+ return value;
549
+ throw new SignInUsageError(`Unknown provider ${value}. Expected claude, codex, or grok.`);
550
+ }
551
+ function lanesRoot() {
552
+ return resolveLanesHqRoot({ env: process.env });
553
+ }
554
+ async function loginThroughLanes(provider, accountId) {
555
+ const { loginLaneAccount } = await import("../lib/capacity/lane-login.js");
556
+ return loginLaneAccount({
557
+ provider,
558
+ accountId,
559
+ hqRoot: lanesRoot(),
560
+ env: process.env,
561
+ });
562
+ }
563
+ function confirmSignIn(question) {
564
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
565
+ return new Promise((resolve) => {
566
+ let settled = false;
567
+ const finish = (value) => {
568
+ if (settled)
569
+ return;
570
+ settled = true;
571
+ rl.close();
572
+ resolve(value);
573
+ };
574
+ rl.once("close", () => finish(false));
575
+ rl.question(`${question} [y/N] `, (answer) => {
576
+ finish(/^y(es)?$/i.test(answer.trim()));
577
+ });
578
+ });
579
+ }
580
+ function failCapacity(err) {
581
+ console.error(err instanceof Error ? err.message : String(err));
582
+ process.exitCode = err instanceof SignInUsageError ? 2 : 1;
583
+ }
584
+ function wantsJson(opts, command) {
585
+ // The root program parses tokens before the subcommand name, so a shared
586
+ // --json flag is stored on `capacity` when it appears before another flag.
587
+ if (opts.json)
588
+ return true;
589
+ let parent = command.parent;
590
+ while (parent) {
591
+ if (parent.opts().json)
592
+ return true;
593
+ parent = parent.parent;
594
+ }
595
+ return false;
596
+ }
597
+ function runCli(action) {
598
+ try {
599
+ process.exitCode = 0;
600
+ action();
601
+ }
602
+ catch (err) {
603
+ console.error(err instanceof Error ? err.message : String(err));
604
+ process.exitCode = err instanceof CapacityUsageError ? 2 : 1;
605
+ }
606
+ }
607
+ function loadNow(options) {
608
+ try {
609
+ return readNow(options);
610
+ }
611
+ catch (err) {
612
+ if (!(err instanceof LedgerCorrupt))
613
+ throw err;
614
+ console.error(err.message);
615
+ return rebuildNow(options);
616
+ }
617
+ }
618
+ function capacityClock(options) {
619
+ const resolved = options ?? capacityOptionsFromEnv();
620
+ const nowMs = resolved.now ? resolved.now().getTime() : Date.now();
621
+ return { options: resolved, nowMs };
622
+ }
623
+ function isReading(event) {
624
+ return !("event" in event);
625
+ }
626
+ function isParserFailure(event) {
627
+ return "event" in event && event.event === "parser_failure";
628
+ }
629
+ function cell(value) {
630
+ if (value === null || value === "")
631
+ return "—";
632
+ return String(value);
633
+ }
634
+ function formatSpan(seconds) {
635
+ if (seconds === null)
636
+ return "—";
637
+ const span = Math.max(0, Math.floor(seconds));
638
+ if (span < 60)
639
+ return `${span}s`;
640
+ const minutes = Math.floor(span / 60);
641
+ if (minutes < 60)
642
+ return `${minutes}m`;
643
+ const hours = Math.floor(minutes / 60);
644
+ const remainMinutes = minutes % 60;
645
+ if (hours < 48)
646
+ return remainMinutes === 0 ? `${hours}h` : `${hours}h ${remainMinutes}m`;
647
+ const days = Math.floor(hours / 24);
648
+ const remainHours = hours % 24;
649
+ return remainHours === 0 ? `${days}d` : `${days}d ${remainHours}h`;
650
+ }
651
+ function formatAge(observedAt, nowMs) {
652
+ const at = Date.parse(observedAt);
653
+ if (Number.isNaN(at))
654
+ return "—";
655
+ return `${formatSpan(Math.floor((nowMs - at) / 1000))} ago`;
656
+ }
657
+ function formatTable(headers, rows) {
658
+ const widths = headers.map((header, index) => Math.max(header.length, ...rows.map((row) => row[index]?.length ?? 0)));
659
+ const render = (row) => row.map((entry, index) => entry.padEnd(widths[index] ?? 0)).join(" ");
660
+ const lines = [render([...headers])];
661
+ if (rows.length > 0)
662
+ lines.push(render(widths.map((width) => "-".repeat(width))));
663
+ lines.push(...rows.map(render));
664
+ return lines.join("\n");
665
+ }
666
+ //# sourceMappingURL=capacity.js.map