@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.
- package/CHANGELOG.md +18 -0
- package/dist/command-catalog.generated.d.ts +24 -0
- package/dist/command-catalog.generated.js +26 -0
- package/dist/commands/bot.js +7 -2
- package/dist/commands/capacity.js +10 -2
- package/dist/commands/cloud.d.ts +14 -2
- package/dist/commands/cloud.js +25 -16
- package/dist/commands/daemon.d.ts +2 -1
- package/dist/commands/daemon.js +225 -4
- package/dist/commands/integrations-core.d.ts +3 -0
- package/dist/commands/integrations-core.js +11 -0
- package/dist/commands/integrations-manage.js +2 -2
- package/dist/commands/integrations.js +5 -5
- package/dist/commands/sync-core.js +4 -1
- package/dist/lib/bot/claude-login.d.ts +7 -1
- package/dist/lib/bot/claude-login.js +56 -16
- package/dist/lib/bot/executive-settings.d.ts +1 -1
- package/dist/lib/bot/executive-settings.js +8 -6
- package/dist/lib/bot/jobs/meeting-detect.js +2 -2
- package/dist/lib/bot/jobs/meeting-rider.js +2 -2
- package/dist/lib/bot/run.js +3 -3
- package/dist/lib/capacity/codex-availability.d.ts +6 -0
- package/dist/lib/capacity/codex-availability.js +33 -0
- package/dist/lib/capacity/collectors/claude-oauth.d.ts +53 -5
- package/dist/lib/capacity/collectors/claude-oauth.js +251 -71
- package/dist/lib/capacity/ledger.d.ts +0 -11
- package/dist/lib/capacity/ledger.js +30 -3
- package/dist/lib/capacity/route.js +11 -5
- package/dist/lib/daemon/catalog.d.ts +3 -0
- package/dist/lib/daemon/catalog.js +17 -4
- package/dist/lib/daemon/handoff.d.ts +2 -0
- package/dist/lib/daemon/handoff.js +9 -2
- package/dist/lib/daemon/install.d.ts +99 -0
- package/dist/lib/daemon/install.js +316 -7
- package/dist/lib/daemon/migrate.d.ts +3 -1
- package/dist/lib/daemon/migrate.js +14 -2
- package/dist/lib/daemon/plist-refresh.d.ts +34 -0
- package/dist/lib/daemon/plist-refresh.js +62 -0
- package/dist/lib/daemon/run.js +27 -2
- package/dist/lib/daemon/selfcheck.d.ts +71 -0
- package/dist/lib/daemon/selfcheck.js +150 -0
- package/dist/lib/daemon/supervisor.d.ts +31 -0
- package/dist/lib/daemon/supervisor.js +130 -6
- package/dist/lib/daemon/unit-holds.js +2 -1
- package/dist/lib/lanes/config.js +13 -0
- package/dist/lib/lanes/throttle.d.ts +12 -1
- package/dist/lib/lanes/throttle.js +27 -8
- package/dist/lib/lanes/types.d.ts +5 -0
- package/dist/lib/service-manager/launchd.d.ts +25 -0
- package/dist/lib/service-manager/launchd.js +51 -3
- package/dist/lib/service-manager/migration.d.ts +14 -0
- package/dist/lib/service-manager/migration.js +34 -0
- package/dist/lib/service-manager/systemd.d.ts +13 -0
- package/dist/lib/service-manager/systemd.js +19 -0
- package/dist/lib/service-manager/types.d.ts +8 -0
- package/dist/lib/signals/steps/extract.d.ts +110 -4
- package/dist/lib/signals/steps/extract.js +520 -39
- package/dist/lib/sync-core/engine-home.d.ts +2 -1
- package/dist/lib/sync-core/engine-home.js +25 -8
- package/dist/lib/sync-core/reconcile.d.ts +7 -2
- package/dist/lib/sync-core/reconcile.js +32 -9
- package/dist/lib/sync-core/release.d.ts +35 -3
- package/dist/lib/sync-core/release.js +95 -12
- 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 {
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
20
|
+
function safeUsername() {
|
|
21
21
|
try {
|
|
22
|
-
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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(
|
|
140
|
+
payload = JSON.parse(result.stdout.trim());
|
|
125
141
|
}
|
|
126
142
|
catch {
|
|
127
143
|
payload = null;
|
|
128
144
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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?.
|
|
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 (!
|
|
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:
|
|
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 {
|
|
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) &&
|
|
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 {
|
|
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 =
|
|
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"));
|
package/dist/lib/bot/run.js
CHANGED
|
@@ -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 {
|
|
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 (!
|
|
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) &&
|
|
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 `
|
|
144
|
-
*
|
|
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(
|
|
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
|
-
/**
|
|
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;
|