@indigoai-us/hq-cli 5.345.202 → 5.345.204

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 (64) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/command-catalog.generated.d.ts +24 -0
  3. package/dist/command-catalog.generated.js +26 -0
  4. package/dist/commands/bot.js +7 -2
  5. package/dist/commands/capacity.js +10 -2
  6. package/dist/commands/cloud.d.ts +14 -2
  7. package/dist/commands/cloud.js +25 -16
  8. package/dist/commands/daemon.d.ts +2 -1
  9. package/dist/commands/daemon.js +225 -4
  10. package/dist/commands/integrations-core.d.ts +3 -0
  11. package/dist/commands/integrations-core.js +11 -0
  12. package/dist/commands/integrations-manage.js +2 -2
  13. package/dist/commands/integrations.js +5 -5
  14. package/dist/commands/sync-core.js +4 -1
  15. package/dist/lib/bot/claude-login.d.ts +7 -1
  16. package/dist/lib/bot/claude-login.js +56 -16
  17. package/dist/lib/bot/executive-settings.d.ts +1 -1
  18. package/dist/lib/bot/executive-settings.js +8 -6
  19. package/dist/lib/bot/jobs/meeting-detect.js +2 -2
  20. package/dist/lib/bot/jobs/meeting-rider.js +2 -2
  21. package/dist/lib/bot/run.js +3 -3
  22. package/dist/lib/capacity/codex-availability.d.ts +6 -0
  23. package/dist/lib/capacity/codex-availability.js +33 -0
  24. package/dist/lib/capacity/collectors/claude-oauth.d.ts +53 -5
  25. package/dist/lib/capacity/collectors/claude-oauth.js +251 -71
  26. package/dist/lib/capacity/ledger.d.ts +0 -11
  27. package/dist/lib/capacity/ledger.js +30 -3
  28. package/dist/lib/capacity/route.js +11 -5
  29. package/dist/lib/daemon/catalog.d.ts +3 -0
  30. package/dist/lib/daemon/catalog.js +17 -4
  31. package/dist/lib/daemon/handoff.d.ts +2 -0
  32. package/dist/lib/daemon/handoff.js +9 -2
  33. package/dist/lib/daemon/install.d.ts +99 -0
  34. package/dist/lib/daemon/install.js +316 -7
  35. package/dist/lib/daemon/migrate.d.ts +3 -1
  36. package/dist/lib/daemon/migrate.js +14 -2
  37. package/dist/lib/daemon/plist-refresh.d.ts +34 -0
  38. package/dist/lib/daemon/plist-refresh.js +62 -0
  39. package/dist/lib/daemon/run.js +27 -2
  40. package/dist/lib/daemon/selfcheck.d.ts +71 -0
  41. package/dist/lib/daemon/selfcheck.js +150 -0
  42. package/dist/lib/daemon/supervisor.d.ts +31 -0
  43. package/dist/lib/daemon/supervisor.js +130 -6
  44. package/dist/lib/daemon/unit-holds.js +2 -1
  45. package/dist/lib/lanes/config.js +13 -0
  46. package/dist/lib/lanes/throttle.d.ts +12 -1
  47. package/dist/lib/lanes/throttle.js +27 -8
  48. package/dist/lib/lanes/types.d.ts +5 -0
  49. package/dist/lib/service-manager/launchd.d.ts +25 -0
  50. package/dist/lib/service-manager/launchd.js +51 -3
  51. package/dist/lib/service-manager/migration.d.ts +14 -0
  52. package/dist/lib/service-manager/migration.js +34 -0
  53. package/dist/lib/service-manager/systemd.d.ts +13 -0
  54. package/dist/lib/service-manager/systemd.js +19 -0
  55. package/dist/lib/service-manager/types.d.ts +8 -0
  56. package/dist/lib/signals/steps/extract.d.ts +110 -4
  57. package/dist/lib/signals/steps/extract.js +520 -39
  58. package/dist/lib/sync-core/engine-home.d.ts +2 -1
  59. package/dist/lib/sync-core/engine-home.js +25 -8
  60. package/dist/lib/sync-core/reconcile.d.ts +7 -2
  61. package/dist/lib/sync-core/reconcile.js +32 -9
  62. package/dist/lib/sync-core/release.d.ts +35 -3
  63. package/dist/lib/sync-core/release.js +95 -12
  64. package/package.json +7 -7
@@ -53,7 +53,7 @@
53
53
  import { randomUUID } from "node:crypto";
54
54
  import { readFile } from "node:fs/promises";
55
55
  import chalk from "chalk";
56
- import { ensureCognitoIdToken } from "../utils/cognito-session.js";
56
+ import { ensureIntegrationIdToken } from "./integrations-core.js";
57
57
  import { getCompanyUid, vaultApiFetch } from "../utils/vault-api.js";
58
58
  import { redactSensitiveUrl, redactSensitiveUrls, redactUrlsInText } from "../utils/redact-url.js";
59
59
  import { IntegrationsCliError, bareProvider, callGateway, parseCallTimeoutMs, fetchConnections, fetchUsableApps, isBuiltInGatewayProvider, isClientError, printHealthNotice, printJson, printIntegrationCallResultJson, connectionAccountLabel, connectionOwnerLabel, selectAccountConnections, raiseIfUnauthorized, raiseIfUpstreamUnavailable, gatewayResultIsError, selectConnection, revokedConnectionDetails, toolPrefixForProvider, unwrapGatewayResult, queuedOutcome, accountCandidateFor, accountSelectionMessage, ACCOUNT_REQUIRED_CODE, } from "./integrations-core.js";
@@ -361,7 +361,7 @@ export function registerIntegrationsCommand(program) {
361
361
  .option("--usable", "Only apps you can use right now (connected and shared with you), with the --provider value to call them")
362
362
  .option("--no-login", "Fail instead of opening a browser sign-in when there is no HQ session")
363
363
  .action(async (opts) => {
364
- const token = await ensureCognitoIdToken(opts.login === false ? { interactive: false } : {});
364
+ const token = await ensureIntegrationIdToken(opts.login === false ? { interactive: false } : {});
365
365
  const companyUid = await getCompanyUid(token, opts.company);
366
366
  if (opts.usable) {
367
367
  const result = await fetchUsableApps(token, companyUid);
@@ -424,7 +424,7 @@ export function registerIntegrationsCommand(program) {
424
424
  .option("--describe <tool>", "Print the full input schema for one tool")
425
425
  .option("--json", "Machine-readable output")
426
426
  .action(async (opts) => {
427
- const token = await ensureCognitoIdToken();
427
+ const token = await ensureIntegrationIdToken();
428
428
  const companyUid = await getCompanyUid(token, opts.company);
429
429
  const connections = await fetchConnections(token, companyUid);
430
430
  const { connection, accountMatches } = selectToolConnection(connections, opts);
@@ -516,7 +516,7 @@ export function registerIntegrationsCommand(program) {
516
516
  .action(async (tool, opts) => {
517
517
  const waitMs = parseCallTimeoutMs(opts.timeout);
518
518
  const parsedArgs = await resolveCallArgs(opts);
519
- const token = await ensureCognitoIdToken();
519
+ const token = await ensureIntegrationIdToken();
520
520
  const companyUid = await getCompanyUid(token, opts.company);
521
521
  const { connection, accountMatches } = selectToolConnection(await fetchConnections(token, companyUid), opts);
522
522
  if (reportRevokedConnection(connection, { ...opts, fail: true }))
@@ -605,7 +605,7 @@ export function registerIntegrationsCommand(program) {
605
605
  .option("--company <slug>", "Company slug, e.g. indigo")
606
606
  .option("--json", "Machine-readable output")
607
607
  .action(async (queueId, opts) => {
608
- const token = await ensureCognitoIdToken();
608
+ const token = await ensureIntegrationIdToken();
609
609
  const companyUid = await getCompanyUid(token, opts.company);
610
610
  const connection = selectConnection(await fetchConnections(token, companyUid), opts);
611
611
  const res = await vaultApiFetch({
@@ -1,7 +1,7 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as os from "node:os";
3
3
  import * as path from "node:path";
4
- import { ensureSyncCoreBinary } from "../lib/sync-core/release.js";
4
+ import { ensureSyncCoreBinary, readSyncCorePin } from "../lib/sync-core/release.js";
5
5
  import { readCompanyRolloutFlag } from "../lib/sync-core/flag.js";
6
6
  import { reconcileSyncCore } from "../lib/sync-core/reconcile.js";
7
7
  import { engineOwnedManifestScopes, manifestScopeArg } from "../lib/sync-core/manifest-scopes.js";
@@ -59,6 +59,9 @@ export async function runSyncCoreReconcile(opts, d = defaultDeps()) {
59
59
  }),
60
60
  readRolloutFlag: (companyUid) => readCompanyRolloutFlag(companyUid, token, { env: d.env }),
61
61
  ensureBinary: (release) => ensureSyncCoreBinary({ home: d.home, platform: d.platform, arch: d.arch, release, env: d.env }),
62
+ platform: d.platform,
63
+ env: d.env,
64
+ readPin: () => readSyncCorePin({ env: d.env }),
62
65
  log: opts.json ? () => { } : d.out,
63
66
  });
64
67
  if (opts.json)
@@ -21,7 +21,13 @@ export interface ClaudeLoginDeps {
21
21
  env?: NodeJS.ProcessEnv;
22
22
  home?: string;
23
23
  platform?: NodeJS.Platform;
24
- readKeychain?: () => KeychainRead;
24
+ /**
25
+ * Reads the Keychain item; `account` scopes the read to one account name.
26
+ * Omitted account means "any item for the service" (the first one found).
27
+ */
28
+ readKeychain?: (account?: string) => KeychainRead;
29
+ /** The login user's account name; null when unknown. */
30
+ username?: string | null;
25
31
  now?: () => number;
26
32
  }
27
33
  export declare function checkClaudeCodeLogin(deps?: ClaudeLoginDeps): ClaudeLoginCheck;
@@ -17,9 +17,20 @@ import * as path from "node:path";
17
17
  import { fleetHome } from "./fleet-local.js";
18
18
  export const CLAUDE_KEYCHAIN_SERVICE = "Claude Code-credentials";
19
19
  export const CLAUDE_LOGIN_ENV_VARS = ["ANTHROPIC_TOKEN", "CLAUDE_CODE_OAUTH_TOKEN", "ANTHROPIC_API_KEY"];
20
- function readKeychainWithSecurity() {
20
+ function safeUsername() {
21
21
  try {
22
- const stdout = execFileSync("security", ["find-generic-password", "-s", CLAUDE_KEYCHAIN_SERVICE, "-w"], {
22
+ return os.userInfo().username || null;
23
+ }
24
+ catch {
25
+ return null;
26
+ }
27
+ }
28
+ function readKeychainWithSecurity(account) {
29
+ const args = account
30
+ ? ["find-generic-password", "-s", CLAUDE_KEYCHAIN_SERVICE, "-a", account, "-w"]
31
+ : ["find-generic-password", "-s", CLAUDE_KEYCHAIN_SERVICE, "-w"];
32
+ try {
33
+ const stdout = execFileSync("security", args, {
23
34
  encoding: "utf8",
24
35
  stdio: ["ignore", "pipe", "pipe"],
25
36
  timeout: 5000,
@@ -113,30 +124,59 @@ export function checkClaudeCodeLogin(deps = {}) {
113
124
  sources.push({ source: "macos_keychain", status: "skipped", reason: "not macOS" });
114
125
  }
115
126
  else {
116
- const read = (deps.readKeychain ?? readKeychainWithSecurity)();
117
- if (read.code !== 0) {
118
- const detail = read.stderr.replace(/\s+/g, " ").trim().slice(0, 200) || `exit ${read.code}`;
119
- sources.push({ source: "macos_keychain", status: "missing", reason: `no readable '${CLAUDE_KEYCHAIN_SERVICE}' item (${detail})` });
120
- }
121
- else {
127
+ // Claude Code (and the HQ desktop sign-in, which runs `claude auth login`)
128
+ // writes its login under the login user's account name. Another item with
129
+ // the same service can exist under a different account (an old one written
130
+ // as "unknown", or one holding only MCP OAuth), and an unscoped read
131
+ // returns whichever the Keychain finds first. Ask for the user's item
132
+ // first, then fall back to any item for the service.
133
+ const readKeychain = deps.readKeychain ?? readKeychainWithSecurity;
134
+ const username = deps.username === undefined ? safeUsername() : deps.username;
135
+ const recordOf = (result) => {
136
+ if (result.code !== 0)
137
+ return null;
122
138
  let payload;
123
139
  try {
124
- payload = JSON.parse(read.stdout.trim());
140
+ payload = JSON.parse(result.stdout.trim());
125
141
  }
126
142
  catch {
127
143
  payload = null;
128
144
  }
129
- const record = payload && typeof payload === "object" ? oauthRecord(payload.claudeAiOauth) : null;
130
- if (record) {
131
- const described = describeRecord(record, now);
132
- sources.push({ source: "macos_keychain", status: described.status, reason: described.reason });
133
- if (described.usable)
134
- found("macos_keychain");
145
+ return payload && typeof payload === "object" ? oauthRecord(payload.claudeAiOauth) : null;
146
+ };
147
+ const scoped = username ? readKeychain(username) : null;
148
+ const scopedRecord = scoped ? recordOf(scoped) : null;
149
+ let read;
150
+ let record;
151
+ if (scoped && scopedRecord) {
152
+ read = scoped;
153
+ record = scopedRecord;
154
+ }
155
+ else {
156
+ const any = readKeychain();
157
+ const anyRecord = recordOf(any);
158
+ if (anyRecord || !scoped || scoped.code !== 0) {
159
+ read = any;
160
+ record = anyRecord;
135
161
  }
136
162
  else {
137
- sources.push({ source: "macos_keychain", status: "missing", reason: "keychain item has no claudeAiOauth access token" });
163
+ read = scoped;
164
+ record = null;
138
165
  }
139
166
  }
167
+ if (read.code !== 0) {
168
+ const detail = read.stderr.replace(/\s+/g, " ").trim().slice(0, 200) || `exit ${read.code}`;
169
+ sources.push({ source: "macos_keychain", status: "missing", reason: `no readable '${CLAUDE_KEYCHAIN_SERVICE}' item (${detail})` });
170
+ }
171
+ else if (record) {
172
+ const described = describeRecord(record, now);
173
+ sources.push({ source: "macos_keychain", status: described.status, reason: described.reason });
174
+ if (described.usable)
175
+ found("macos_keychain");
176
+ }
177
+ else {
178
+ sources.push({ source: "macos_keychain", status: "missing", reason: "keychain item has no claudeAiOauth access token" });
179
+ }
140
180
  }
141
181
  return { signedIn: via !== null, via, sources };
142
182
  }
@@ -1,5 +1,5 @@
1
1
  import { type BotConfig } from "./config.js";
2
- export declare function executiveRiderEnabled(home?: string): boolean;
2
+ export declare function executiveEnabled(home?: string): boolean;
3
3
  export declare function isExecutiveBot(config: Pick<BotConfig, "name" | "kind" | "workerId">, home?: string): boolean;
4
4
  export declare function executiveStatus(config: Pick<BotConfig, "name" | "kind" | "workerId">, dir: string, home?: string): {
5
5
  is_executive: boolean;
@@ -2,18 +2,20 @@
2
2
  * Shared Executive identity and its opt-in switch.
3
3
  *
4
4
  * The Executive (meeting rider, signal-to-lane, source report DMs) is off
5
- * unless ~/.hq/menubar.json sets executiveMeetingRider.enabled to true. It
6
- * stays internal until the live end-to-end proof passes.
5
+ * unless ~/.hq/menubar.json sets executive.enabled to true. It
6
+ * stays internal until the live end-to-end proof passes. The older
7
+ * executiveMeetingRider key is ignored whatever its value: the desktop beta
8
+ * saved executiveMeetingRider.enabled: true for users who never chose it.
7
9
  */
8
10
  import * as fs from "node:fs";
9
11
  import * as path from "node:path";
10
12
  import * as os from "node:os";
11
13
  import { effectiveBotKind } from "./config.js";
12
14
  import { tailBotEvents } from "./events.js";
13
- export function executiveRiderEnabled(home = os.homedir()) {
15
+ export function executiveEnabled(home = os.homedir()) {
14
16
  try {
15
17
  const settings = JSON.parse(fs.readFileSync(path.join(home, ".hq/menubar.json"), "utf8"));
16
- return settings?.executiveMeetingRider?.enabled === true;
18
+ return settings?.executive?.enabled === true;
17
19
  }
18
20
  catch {
19
21
  return false;
@@ -22,7 +24,7 @@ export function executiveRiderEnabled(home = os.homedir()) {
22
24
  export function isExecutiveBot(config, home = os.homedir()) {
23
25
  if (effectiveBotKind(config) !== "personal")
24
26
  return false;
25
- if (!executiveRiderEnabled(home))
27
+ if (!executiveEnabled(home))
26
28
  return false;
27
29
  let name = "setup";
28
30
  try {
@@ -42,7 +44,7 @@ export function isExecutiveBot(config, home = os.homedir()) {
42
44
  export function executiveStatus(config, dir, home = os.homedir()) {
43
45
  const last = tailBotEvents(dir, (event) => event.event === "meeting.detected").at(-1);
44
46
  return {
45
- is_executive: isExecutiveBot(config, home), rider_enabled: executiveRiderEnabled(home),
47
+ is_executive: isExecutiveBot(config, home), rider_enabled: executiveEnabled(home),
46
48
  // The first desktop producer did not set source.
47
49
  detect_source_last: last ? (last.payload.source === "poll" ? "poll" : "desktop") : null,
48
50
  last_detected_at: last?.at ?? null,
@@ -9,7 +9,7 @@ import { getCompanyUid } from "../../../utils/vault-api.js";
9
9
  import { assertSafeSegment } from "../../hot-channels/landing.js";
10
10
  import { readManifestCompanies } from "../company-bind.js";
11
11
  import { emitBotEvent } from "../events.js";
12
- import { executiveRiderEnabled, isExecutiveBot } from "../executive-settings.js";
12
+ import { executiveEnabled, isExecutiveBot } from "../executive-settings.js";
13
13
  const RETENTION_MS = 24 * 60 * 60 * 1000;
14
14
  const ACTIVE = new Set(["joining", "recording", "in_call"]);
15
15
  export function detectIntervalSeconds(hqRoot) {
@@ -161,7 +161,7 @@ async function untilAborted(signal, work) {
161
161
  export function defaultMeetingDetectDeps(config, dir, log) {
162
162
  return {
163
163
  hqRoot: config.hqRoot, dir, log,
164
- enabled: () => isExecutiveBot(config) && executiveRiderEnabled(),
164
+ enabled: () => isExecutiveBot(config) && executiveEnabled(),
165
165
  companies: () => config.companySlug ? [config.companySlug] : readManifestCompanies(config.hqRoot),
166
166
  intervalSeconds: () => detectIntervalSeconds(config.hqRoot),
167
167
  list: async (company, signal) => {
@@ -12,7 +12,7 @@ import { meetingSignalSourceRef, cursorPath } from "../../signals/incremental.js
12
12
  import { readAllLanes } from "../../lanes/store.js";
13
13
  import { runSignals } from "../../signals/engine.js";
14
14
  import { emitBotEvent, EVENT_PAYLOAD_BYTES } from "../events.js";
15
- import { executiveRiderEnabled } from "../executive-settings.js";
15
+ import { executiveEnabled } from "../executive-settings.js";
16
16
  export { isExecutiveBot } from "../executive-settings.js";
17
17
  import { readLocalJobsStrict, withJobsLock, writeLocalJobs } from "../local-jobs.js";
18
18
  import { readBuiltinJobsMarker, writeBuiltinJobsMarker } from "./hot-signal-sync.js";
@@ -56,7 +56,7 @@ export function ensureMeetingRiderJob(dir) {
56
56
  });
57
57
  }
58
58
  export function meetingRiderSettings(hqRoot, home = os.homedir()) {
59
- const enabled = executiveRiderEnabled(home);
59
+ const enabled = executiveEnabled(home);
60
60
  let interval = 20;
61
61
  try {
62
62
  const settings = yaml.load(fs.readFileSync(path.join(hqRoot, "core/settings/signals.yaml"), "utf8"));
@@ -50,7 +50,7 @@ import { ensureSignalToLaneJobs, runSignalToLane, SIGNAL_TO_LANE_JOB_KIND } from
50
50
  * Everything with a side effect is injectable so the loop is unit-testable.
51
51
  */
52
52
  import { createMeetingRider, defaultMeetingRiderDeps, ensureMeetingRiderJob, MEETING_RIDER_JOB_KIND } from "./jobs/meeting-rider.js";
53
- import { executiveRiderEnabled, isExecutiveBot } from "./executive-settings.js";
53
+ import { executiveEnabled, isExecutiveBot } from "./executive-settings.js";
54
54
  import { createMeetingDetect, defaultMeetingDetectDeps, resolveDetectedMeeting } from "./jobs/meeting-detect.js";
55
55
  import { botAuthEnv, describeBotAuth, formatBotAuth } from "./runtime-auth.js";
56
56
  import { ownerContextBlock } from "./owner-context.js";
@@ -435,7 +435,7 @@ export async function runBot(deps) {
435
435
  eventHandlers[MEETING_RIDER_JOB_KIND] = async ({ item }) => {
436
436
  if (!isExecutiveBot(config))
437
437
  return { status: "skipped-precondition", output: "Not the Executive" };
438
- if (!executiveRiderEnabled())
438
+ if (!executiveEnabled())
439
439
  return { status: "skipped-precondition", output: "rider disabled" };
440
440
  meetingRider.start(await resolveDetectedMeeting(config.hqRoot, typeof item.context === "object" ? item.context?.payload : undefined, meetingDetectDeps.list));
441
441
  return { status: "succeeded", output: "Meeting rider accepted" };
@@ -2905,7 +2905,7 @@ export async function runBot(deps) {
2905
2905
  }
2906
2906
  if (isExecutiveBot(config)) {
2907
2907
  try {
2908
- if (ensureMeetingRiderJob(dir) && executiveRiderEnabled())
2908
+ if (ensureMeetingRiderJob(dir) && executiveEnabled())
2909
2909
  meetingRider.resume();
2910
2910
  }
2911
2911
  catch {
@@ -0,0 +1,6 @@
1
+ import type { NowLimit } from "./ledger.js";
2
+ /** A newer snapshot supersedes omitted windows within the same account. */
3
+ export declare function codexSnapshotStale(limit: NowLimit, limits: readonly NowLimit[]): boolean;
4
+ /** Credits can keep Codex serving after a plan window runs out. */
5
+ export declare function hasCodexCreditRoom(limits: readonly NowLimit[], nowMs: number): boolean;
6
+ //# sourceMappingURL=codex-availability.d.ts.map
@@ -0,0 +1,33 @@
1
+ const MAX_CREDIT_AGE_MS = 24 * 36e5;
2
+ /** A newer snapshot supersedes omitted windows within the same account. */
3
+ export function codexSnapshotStale(limit, limits) {
4
+ if (limit.source !== "codex-jsonl" && limit.source !== "codex-app-server" && limit.source !== "refusal")
5
+ return false;
6
+ const at = Date.parse(limit.observedAt);
7
+ return limits.some((other) => other.accountId === limit.accountId
8
+ && (other.source === "codex-jsonl" || other.source === "codex-app-server")
9
+ && Date.parse(other.observedAt) > at);
10
+ }
11
+ /** Credits can keep Codex serving after a plan window runs out. */
12
+ export function hasCodexCreditRoom(limits, nowMs) {
13
+ return limits.some((credit) => {
14
+ if (credit.kind !== "credits" || !credit.limitId.endsWith(":credits"))
15
+ return false;
16
+ const at = Date.parse(credit.observedAt);
17
+ if (!Number.isFinite(at) || at > nowMs || nowMs - at > MAX_CREDIT_AGE_MS)
18
+ return false;
19
+ if (codexSnapshotStale(credit, limits) || credit.state !== "ok")
20
+ return false;
21
+ if (credit.resetsAt && Date.parse(credit.resetsAt) <= nowMs)
22
+ return false;
23
+ if (credit.hasCredits !== true || credit.spendControlReached === true)
24
+ return false;
25
+ const sameAccount = limits.filter((limit) => limit.accountId === credit.accountId);
26
+ if (sameAccount.some((limit) => !codexSnapshotStale(limit, limits)
27
+ && limit.spendControlReached === true))
28
+ return false;
29
+ const balance = credit.balance === null ? NaN : Number(credit.balance);
30
+ return credit.unlimited === true || (Number.isFinite(balance) && balance > 0);
31
+ });
32
+ }
33
+ //# sourceMappingURL=codex-availability.js.map
@@ -140,8 +140,10 @@ export type WriteClaudeCredentials = (request: WriteClaudeCredentialsRequest) =>
140
140
  * way). The gate treats this as proof the account is already signed out and
141
141
  * records revoked without hitting the token endpoint.
142
142
  * - `home` is the resolved per-host provider home for the account; the gate
143
- * compares it to the home being refreshed with `path.resolve` so symlinks
144
- * do not slip through. `null` is accepted only when the row is not live.
143
+ * compares it to the home being refreshed with `canonicalHomePath` (realpath,
144
+ * lower case on darwin) so symlinks and case differences do not slip through;
145
+ * a home that cannot be resolved blocks. `null` is accepted only when the row
146
+ * is not live.
145
147
  */
146
148
  export interface LiveLane {
147
149
  home: string | null;
@@ -164,8 +166,13 @@ export type ListLiveLanes = () => Promise<readonly LiveLane[]>;
164
166
  */
165
167
  export interface ClaudeProcess {
166
168
  pid: number;
169
+ /**
170
+ * `HOME` is set when the probe read it. Claude Code resolves its default config dir as
171
+ * HOME/.claude, so a process with HOME set and no CLAUDE_CONFIG_DIR holds that dir.
172
+ */
167
173
  env: {
168
174
  CLAUDE_CONFIG_DIR?: string;
175
+ HOME?: string;
169
176
  };
170
177
  probe: "env" | "command-only";
171
178
  }
@@ -245,7 +252,7 @@ export declare function mapClaudeOAuthUsage(body: unknown, options: MapOptions):
245
252
  readings: ReadingInput[];
246
253
  failures: ParserFailureEvent[];
247
254
  };
248
- export declare function collectClaudeOAuth(options?: CollectClaudeOAuthOptions): Promise<ClaudeOAuthCollectResult>;
255
+ export declare function collectClaudeOAuth(inputOptions?: CollectClaudeOAuthOptions): Promise<ClaudeOAuthCollectResult>;
249
256
  /**
250
257
  * Ledger account id → hashed profile account id. Rows with no captured
251
258
  * identity are omitted. The raw uuid is never returned.
@@ -349,11 +356,16 @@ export declare function resolveHqInvocation(argv?: readonly string[], execPath?:
349
356
  command: string;
350
357
  args: string[];
351
358
  };
352
- /** Exported for tests; `invocation` defaults to {@link resolveHqInvocation}. */
359
+ /**
360
+ * Run `hq ... --json` and parse stdout. Async on purpose: the collector runs inside the mesh
361
+ * daemon, and a synchronous read that takes 15-30 s on a loaded machine froze the daemon's
362
+ * event loop (MQTT keepalive, owner heartbeat) for the whole read.
363
+ * Exported for tests; `invocation` defaults to {@link resolveHqInvocation}.
364
+ */
353
365
  export declare function execHqJson(args: readonly string[], invocation?: {
354
366
  command: string;
355
367
  args: string[];
356
- }, env?: NodeJS.ProcessEnv): unknown;
368
+ }, env?: NodeJS.ProcessEnv): Promise<unknown>;
357
369
  /**
358
370
  * Parse `hq lanes list --json` payload into typed rows.
359
371
  * Exported for tests; fail-closed semantics live in defaultListLiveLanes.
@@ -369,12 +381,48 @@ export declare function parseAccountsListPayload(payload: unknown): readonly Raw
369
381
  * Exported for tests.
370
382
  */
371
383
  export declare function buildLiveLanesFromPayloads(lanes: readonly RawLaneRow[], accounts: readonly RawAccountRow[]): readonly LiveLane[];
384
+ /**
385
+ * Share one lane read across every home in a collect. Without this each expired home ran its
386
+ * own pair of `hq lanes ... --json` reads, so one slow machine turned into N timeouts per
387
+ * collect. A failure is shared too: the next home fails closed without waiting again.
388
+ * Exported for tests.
389
+ */
390
+ export declare function memoizeListLiveLanes(list: ListLiveLanes): ListLiveLanes;
372
391
  /**
373
392
  * Parse `ps -axo pid=,command=` into rows that mention the `claude` executable.
374
393
  * Exported for tests. Returned rows carry `probe: "command-only"` so the gate
375
394
  * knows it cannot read per-process CLAUDE_CONFIG_DIR on darwin.
376
395
  */
377
396
  export declare function parsePsClaudeOutput(stdout: string): readonly ClaudeProcess[];
397
+ /** Pid to full command line, from `ps -ww -o pid=,command= -p <pids>`. Exported for tests. */
398
+ export declare function parsePsCommandLines(stdout: string): ReadonlyMap<number, string>;
399
+ export interface PsEnvProbeOptions {
400
+ isDirectory?: (dir: string) => boolean;
401
+ /** False only when the pid is known to be gone (ESRCH). Unknown counts as alive. */
402
+ isAlive?: (pid: number) => boolean;
403
+ }
404
+ /**
405
+ * Upgrade darwin command-only rows with each process's environment. `ps -wwE` prints a
406
+ * same-user process's command line followed by its environment, unquoted and space separated.
407
+ * Only the text after the exact command line (read separately with `ps -ww`) is treated as the
408
+ * environment, so a prompt argument that mentions CLAUDE_CONFIG_DIR= or HOME= is never read as
409
+ * env. A row becomes `probe: "env"` only when:
410
+ * - the `-E` line starts with the command line plus a space, and the rest starts with NAME=;
411
+ * - CLAUDE_CONFIG_DIR= appears at most once in that rest, and its value is absolute and
412
+ * names an existing directory (the value runs to the next ` NAME=` or the end of line).
413
+ * Anything else stays command-only, which blocks every home. A pid missing from the output
414
+ * is dropped only when it is known to have exited; otherwise it stays command-only.
415
+ * Exported for tests.
416
+ */
417
+ export declare function parsePsEnvOutput(envStdout: string, commandLines: ReadonlyMap<number, string>, rows: readonly ClaudeProcess[], options?: PsEnvProbeOptions): readonly ClaudeProcess[];
418
+ /** Default config dir of an env-probed process with CLAUDE_CONFIG_DIR unset. Exported for tests. */
419
+ export declare function processDefaultConfigDir(proc: ClaudeProcess): string;
420
+ /**
421
+ * Canonical form of a home directory for comparison: symlinks resolved by the OS, and lower
422
+ * case on darwin, where APFS is case-insensitive by default. Null when the path cannot be
423
+ * resolved. Exported for tests.
424
+ */
425
+ export declare function canonicalHomePath(dir: string, platform?: NodeJS.Platform): string | null;
378
426
  export interface ClaudeTokenRefreshResult {
379
427
  outcome: "refreshed" | "revoked" | "error" | "skipped-race" | "skipped-live" | "skipped-live-check-failed" | "skipped-process" | "skipped-process-check-failed" | "skipped-backoff" | "skipped-revoked";
380
428
  newAccessToken?: string;