@indigoai-us/hq-cli 5.345.198 → 5.345.200
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 +71 -0
- package/assets/scaffold/core/scripts/lib/session-scope-capability.sh +6 -4
- package/dist/command-catalog.generated.d.ts +150 -6
- package/dist/command-catalog.generated.js +182 -6
- package/dist/commands/bot-events.d.ts +19 -0
- package/dist/commands/bot-events.js +46 -0
- package/dist/commands/bot-jobs.d.ts +2 -0
- package/dist/commands/bot-jobs.js +43 -2
- package/dist/commands/bot-runtime.d.ts +35 -0
- package/dist/commands/bot-runtime.js +115 -0
- package/dist/commands/bot.d.ts +11 -0
- package/dist/commands/bot.js +72 -11
- package/dist/commands/creators.js +2 -1
- package/dist/commands/lanes-report.d.ts +2 -1
- package/dist/commands/lanes-report.js +44 -1
- package/dist/commands/lanes.d.ts +9 -0
- package/dist/commands/lanes.js +95 -9
- package/dist/commands/library-remove.js +2 -1
- package/dist/commands/library.js +5 -4
- package/dist/commands/meetings.js +1 -21
- package/dist/commands/mesh.d.ts +0 -7
- package/dist/commands/mesh.js +5 -1
- package/dist/commands/signals-run.d.ts +2 -0
- package/dist/commands/signals-run.js +6 -1
- package/dist/commands/sync-held.js +2 -2
- package/dist/lanes-drain-hook.js +9 -0
- package/dist/lib/bot/api.d.ts +5 -0
- package/dist/lib/bot/claude-login.d.ts +31 -0
- package/dist/lib/bot/claude-login.js +160 -0
- package/dist/lib/bot/events.d.ts +18 -0
- package/dist/lib/bot/events.js +160 -0
- package/dist/lib/bot/executive-settings.d.ts +10 -0
- package/dist/lib/bot/executive-settings.js +43 -0
- package/dist/lib/bot/fleet-cron.js +4 -0
- package/dist/lib/bot/fleet-download.d.ts +27 -0
- package/dist/lib/bot/fleet-download.js +162 -0
- package/dist/lib/bot/fleet-local.d.ts +51 -2
- package/dist/lib/bot/fleet-local.js +179 -76
- package/dist/lib/bot/fleet-python.d.ts +2 -0
- package/dist/lib/bot/fleet-python.js +29 -0
- package/dist/lib/bot/job-run.js +1 -1
- package/dist/lib/bot/jobs/hot-signal-sync.d.ts +8 -0
- package/dist/lib/bot/jobs/hot-signal-sync.js +2 -0
- package/dist/lib/bot/jobs/meeting-detect.d.ts +28 -0
- package/dist/lib/bot/jobs/meeting-detect.js +176 -0
- package/dist/lib/bot/jobs/meeting-rider.d.ts +53 -0
- package/dist/lib/bot/jobs/meeting-rider.js +284 -0
- package/dist/lib/bot/jobs/signal-to-lane.d.ts +32 -0
- package/dist/lib/bot/jobs/signal-to-lane.js +169 -0
- package/dist/lib/bot/local-jobs.d.ts +15 -2
- package/dist/lib/bot/local-jobs.js +24 -7
- package/dist/lib/bot/run.d.ts +7 -49
- package/dist/lib/bot/run.js +100 -4
- package/dist/lib/bot/status.d.ts +21 -0
- package/dist/lib/doctor/checks/bots.d.ts +6 -0
- package/dist/lib/doctor/checks/bots.js +47 -1
- package/dist/lib/hot-channels/adapters/meetings.d.ts +1 -0
- package/dist/lib/hot-channels/adapters/meetings.js +3 -1
- package/dist/lib/hot-channels/meeting-source-ref.d.ts +3 -0
- package/dist/lib/hot-channels/meeting-source-ref.js +7 -0
- package/dist/lib/hot-channels/schedule.js +2 -2
- package/dist/lib/hot-channels/signals-pass.d.ts +9 -32
- package/dist/lib/hot-channels/signals-pass.js +51 -17
- package/dist/lib/lanes/admission.d.ts +2 -0
- package/dist/lib/lanes/admission.js +4 -0
- package/dist/lib/lanes/approval-gate.js +4 -1
- package/dist/lib/lanes/links.d.ts +8 -1
- package/dist/lib/lanes/links.js +84 -3
- package/dist/lib/lanes/mesh-events.js +3 -1
- package/dist/lib/lanes/outcome.d.ts +154 -0
- package/dist/lib/lanes/outcome.js +151 -0
- package/dist/lib/lanes/reconcile.js +1 -0
- package/dist/lib/lanes/signal-brief-template.d.ts +7 -0
- package/dist/lib/lanes/signal-brief-template.js +40 -0
- package/dist/lib/lanes/signal-lane.d.ts +24 -0
- package/dist/lib/lanes/signal-lane.js +99 -0
- package/dist/lib/lanes/source-report.d.ts +37 -0
- package/dist/lib/lanes/source-report.js +66 -0
- package/dist/lib/lanes/spawn.d.ts +6 -0
- package/dist/lib/lanes/spawn.js +21 -0
- package/dist/lib/lanes/store.d.ts +3 -0
- package/dist/lib/lanes/store.js +64 -0
- package/dist/lib/lanes/story-ledger.d.ts +3 -0
- package/dist/lib/lanes/story-ledger.js +1 -0
- package/dist/lib/lanes/types.d.ts +137 -0
- package/dist/lib/lanes/types.js +17 -2
- package/dist/lib/lanes/watcher.js +4 -0
- package/dist/lib/live-meetings.d.ts +19 -0
- package/dist/lib/live-meetings.js +23 -0
- package/dist/lib/mesh/api.js +2 -1
- package/dist/lib/mesh/live/daemon/doctor.js +6 -7
- package/dist/lib/mesh/live/daemon/run.d.ts +0 -10
- package/dist/lib/mesh/live/daemon/run.js +3 -0
- package/dist/lib/mesh/live/emit.d.ts +2 -11
- package/dist/lib/mesh/live/emit.js +20 -2
- package/dist/lib/mesh/live/flush.d.ts +2 -0
- package/dist/lib/mesh/live/flush.js +34 -3
- package/dist/lib/mesh/live/reporting-privacy.d.ts +7 -0
- package/dist/lib/mesh/live/reporting-privacy.js +47 -0
- package/dist/lib/mesh/live/spool.d.ts +8 -0
- package/dist/lib/mesh/live/spool.js +59 -40
- package/dist/lib/plan-limit-nag.d.ts +30 -4
- package/dist/lib/plan-limit-nag.js +68 -17
- package/dist/lib/signals/engine.d.ts +4 -0
- package/dist/lib/signals/engine.js +25 -5
- package/dist/lib/signals/incremental.d.ts +42 -0
- package/dist/lib/signals/incremental.js +191 -0
- package/dist/lib/signals/prior-signals.d.ts +11 -0
- package/dist/lib/signals/prior-signals.js +44 -0
- package/dist/lib/signals/reply.js +6 -1
- package/dist/lib/signals/settings.d.ts +5 -0
- package/dist/lib/signals/settings.js +17 -0
- package/dist/lib/signals/steps/extract.d.ts +23 -2
- package/dist/lib/signals/steps/extract.js +142 -9
- package/dist/lib/signals/steps/types.d.ts +1 -0
- package/dist/lib/signals/write-guard.js +7 -0
- package/dist/lib/work-context/company.d.ts +2 -3
- package/dist/lib/work-context/company.js +4 -22
- package/dist/secrets-env-action.js +5 -2
- package/dist/utils/cognito-callback-port.d.ts +17 -0
- package/dist/utils/cognito-callback-port.js +56 -0
- package/dist/utils/cognito-config.js +7 -3
- package/dist/utils/cognito-session.d.ts +7 -6
- package/dist/utils/cognito-session.js +19 -14
- package/dist/utils/interactive-caller.d.ts +23 -0
- package/dist/utils/interactive-caller.js +80 -0
- package/dist/utils/login-browser.js +5 -2
- package/dist/utils/resident-package-preload.js +4 -0
- package/package.json +7 -7
package/dist/commands/lanes.js
CHANGED
|
@@ -75,6 +75,7 @@ import { resolveActiveMembershipCompany } from "../lib/mesh/api.js";
|
|
|
75
75
|
import { openMeshTransport, requireToken } from "../lib/mesh/client.js";
|
|
76
76
|
import { CompanySelectionError } from "../utils/company-selection-error.js";
|
|
77
77
|
import { assertNoSecretText } from "../lib/lanes/text-scan.js";
|
|
78
|
+
import { createSignalLane } from "../lib/lanes/signal-lane.js";
|
|
78
79
|
import { createLaneMesh, emitLaneDecisionEvents, emitLaneTerminalStateEvent, laneMeshEventIdentity, } from "../lib/lanes/mesh-events.js";
|
|
79
80
|
import { sessionIdFromEnv } from "../lib/mesh/live/session-identity.js";
|
|
80
81
|
import { applyLoopDisposition, loopDispositionSnapshot } from "../lib/lanes/loop-disposition.js";
|
|
@@ -285,7 +286,7 @@ function failJson(err) {
|
|
|
285
286
|
process.stderr.write(`hq lanes: ${commandError.message}\n`);
|
|
286
287
|
}
|
|
287
288
|
printJson({ ...commandError.toJSON(), message: commandError.message });
|
|
288
|
-
process.exit(1);
|
|
289
|
+
process.exit(["signal_not_found", "no_conductor_link"].includes(commandError.error) ? 2 : 1);
|
|
289
290
|
}
|
|
290
291
|
const message = commandError instanceof Error ? commandError.message : String(commandError);
|
|
291
292
|
process.stderr.write(`hq lanes: ${message}${commandError instanceof Error && commandError.stack ? `\n${commandError.stack}` : ""}\n`);
|
|
@@ -1935,6 +1936,7 @@ async function refreshRemoteLaneMirrors(ctx, hqRoot, records, parentLaneIds, opt
|
|
|
1935
1936
|
before.envelope_summary !== envelopeSummary ||
|
|
1936
1937
|
(mirroredReportAt !== undefined && before.last_host_report_at !== mirroredReportAt) ||
|
|
1937
1938
|
(isTerminalLaneState(owner.state) && (JSON.stringify(before.last_envelope) !== JSON.stringify(owner.last_envelope) ||
|
|
1939
|
+
JSON.stringify(before.outcome) !== JSON.stringify(owner.outcome) ||
|
|
1938
1940
|
before.story_complete !== owner.story_complete ||
|
|
1939
1941
|
before.story_complete_reason !== owner.story_complete_reason ||
|
|
1940
1942
|
before.story_status !== owner.story_status ||
|
|
@@ -1964,6 +1966,7 @@ async function refreshRemoteLaneMirrors(ctx, hqRoot, records, parentLaneIds, opt
|
|
|
1964
1966
|
...(mirroredReportAt !== undefined ? { last_host_report_at: mirroredReportAt } : {}),
|
|
1965
1967
|
...(isTerminalLaneState(owner.state) ? {
|
|
1966
1968
|
last_envelope: owner.last_envelope,
|
|
1969
|
+
outcome: owner.outcome,
|
|
1967
1970
|
story_complete: owner.story_complete,
|
|
1968
1971
|
story_complete_reason: owner.story_complete_reason,
|
|
1969
1972
|
story_status: owner.story_status,
|
|
@@ -3465,7 +3468,34 @@ export function cancelPendingQuestionsForLane(hqRoot, laneId, nowIso, operatorRe
|
|
|
3465
3468
|
function mintMessageId() {
|
|
3466
3469
|
return `msg_${Date.now().toString(36)}_${randomBytes(3).toString("hex")}`;
|
|
3467
3470
|
}
|
|
3471
|
+
/** In-process signal entry point sharing CLI admission, dedupe and conductor rules. */
|
|
3472
|
+
export async function createLaneFromSignal(input, deps = {}) {
|
|
3473
|
+
const ctx = resolveDeps({ ...deps, hqRoot: () => input.hqRoot });
|
|
3474
|
+
const company = await ctx.resolveCompany(input.company);
|
|
3475
|
+
const result = await createSignalLane(input.hqRoot, {
|
|
3476
|
+
company: company.slug, signal: input.signal_id, env: ctx.env,
|
|
3477
|
+
}, options => runCreate(ctx, { ...options, company: company.slug, project: "executive-bot",
|
|
3478
|
+
noStory: `signal:${input.signal_id}`, freshThread: true, quiet: true }));
|
|
3479
|
+
return { lane_id: result.lane.lane_id, existing: result.existing };
|
|
3480
|
+
}
|
|
3468
3481
|
async function runCreate(ctx, opts) {
|
|
3482
|
+
if (opts.signal !== undefined) {
|
|
3483
|
+
if (!opts.company?.trim())
|
|
3484
|
+
throw new LanesError("missing_company", "--signal requires --company");
|
|
3485
|
+
if (opts.story || opts.additionalStory?.length || opts.briefFile || (opts.project && opts.project !== "executive-bot")) {
|
|
3486
|
+
throw new LanesError("signal_option_conflict", "Signal lanes use project executive-bot and a generated brief without a story");
|
|
3487
|
+
}
|
|
3488
|
+
const company = await ctx.resolveCompany(opts.company);
|
|
3489
|
+
const result = await createSignalLane(ctx.hqRoot(), {
|
|
3490
|
+
company: company.slug, signal: opts.signal, rulesFile: opts.rulesFile ? resolveCallerPath(ctx.callerCwd(), opts.rulesFile) : undefined,
|
|
3491
|
+
anyType: opts.anyType, senior: opts.senior, link: opts.link, env: ctx.env,
|
|
3492
|
+
}, input => runCreate(ctx, { ...opts, ...input, company: company.slug, signal: undefined, project: "executive-bot", noStory: `signal:${opts.signal}`, freshThread: opts.runner === "subagent" ? undefined : true, quiet: true }));
|
|
3493
|
+
if (!opts.quiet)
|
|
3494
|
+
printJson({ lane_id: result.lane.lane_id, signal_id: result.lane.signal.signal_id,
|
|
3495
|
+
source_ref: result.lane.signal.source_ref, senior: result.lane.senior, link: result.lane.signal_link ?? null,
|
|
3496
|
+
existing: result.existing, brief_path: result.brief_path });
|
|
3497
|
+
return result.lane;
|
|
3498
|
+
}
|
|
3469
3499
|
if (opts.runner !== undefined && parseLaneRunner(opts.runner) === "subagent") {
|
|
3470
3500
|
return runCreateSubagent(ctx, opts);
|
|
3471
3501
|
}
|
|
@@ -4304,6 +4334,7 @@ async function runCreate(ctx, opts) {
|
|
|
4304
4334
|
const admissionReservedAt = admission.action === "start" ? ctx.now().toISOString() : undefined;
|
|
4305
4335
|
const laneInit = {
|
|
4306
4336
|
company,
|
|
4337
|
+
...(opts.signalMetadata ? { signal: opts.signalMetadata, signal_link: opts.signalLink } : {}),
|
|
4307
4338
|
...(company === null && meshCompany
|
|
4308
4339
|
? { mesh_company: meshCompany }
|
|
4309
4340
|
: {}),
|
|
@@ -6182,7 +6213,54 @@ function formatLaneListHuman(lanes) {
|
|
|
6182
6213
|
rows.push("No lanes.");
|
|
6183
6214
|
return rows.join("\n");
|
|
6184
6215
|
}
|
|
6185
|
-
async function
|
|
6216
|
+
async function runLaneStatus(ctx, laneId, opts) {
|
|
6217
|
+
const lane = (await resolveMessageLaneTarget(ctx, laneId)).lane;
|
|
6218
|
+
const status = {
|
|
6219
|
+
schema_version: "hq-lanes-lane-status.v1",
|
|
6220
|
+
lane_id: lane.lane_id,
|
|
6221
|
+
company: lane.company?.slug ?? null,
|
|
6222
|
+
state: lane.state,
|
|
6223
|
+
decision: lane.last_envelope?.decision ?? null,
|
|
6224
|
+
envelope_ref: lane.last_envelope?.ref ?? null,
|
|
6225
|
+
story_status: lane.story_status ?? null,
|
|
6226
|
+
story_complete: lane.story_complete ?? null,
|
|
6227
|
+
outcome: lane.outcome ?? null,
|
|
6228
|
+
};
|
|
6229
|
+
if (opts.json) {
|
|
6230
|
+
printJson(status);
|
|
6231
|
+
return;
|
|
6232
|
+
}
|
|
6233
|
+
const lines = [
|
|
6234
|
+
`lane ${status.lane_id}: ${status.state}` +
|
|
6235
|
+
(status.decision ? ` (decision ${status.decision})` : ""),
|
|
6236
|
+
];
|
|
6237
|
+
if (status.outcome) {
|
|
6238
|
+
lines.push(`outcome ${status.outcome.state}: ${status.outcome.outcome}`);
|
|
6239
|
+
for (const item of status.outcome.gathered) {
|
|
6240
|
+
lines.push(` gathered ${item.label}: ${item.text}${item.ref ? ` (${item.ref})` : ""}`);
|
|
6241
|
+
}
|
|
6242
|
+
for (const step of status.outcome.next_steps)
|
|
6243
|
+
lines.push(` next: ${step}`);
|
|
6244
|
+
if (status.outcome.decision_id)
|
|
6245
|
+
lines.push(` decision_id: ${status.outcome.decision_id}`);
|
|
6246
|
+
}
|
|
6247
|
+
else {
|
|
6248
|
+
lines.push("outcome: none");
|
|
6249
|
+
}
|
|
6250
|
+
console.log(lines.join("\n"));
|
|
6251
|
+
}
|
|
6252
|
+
async function runStatus(ctx, opts, laneId) {
|
|
6253
|
+
if (laneId !== undefined) {
|
|
6254
|
+
if (opts.project !== undefined || opts.company !== undefined) {
|
|
6255
|
+
throw new LanesError("invalid_args", "hq lanes status <lane-id> reports one lane; drop --project and --company", { lane_id: laneId });
|
|
6256
|
+
}
|
|
6257
|
+
await runLaneStatus(ctx, laneId, opts);
|
|
6258
|
+
return;
|
|
6259
|
+
}
|
|
6260
|
+
if (opts.project === undefined) {
|
|
6261
|
+
throw new LanesError("invalid_args", "hq lanes status requires --project <slug>, or a lane id for one lane", {});
|
|
6262
|
+
}
|
|
6263
|
+
const projectId = opts.project;
|
|
6186
6264
|
if (!ctx.storyLedgerEnabled()) {
|
|
6187
6265
|
throw new LanesError("story_ledger_disabled", "hq lanes status requires the lanes.story-ledger flag", {
|
|
6188
6266
|
flag: "lanes.story-ledger",
|
|
@@ -6216,7 +6294,7 @@ async function runStatus(ctx, opts) {
|
|
|
6216
6294
|
}
|
|
6217
6295
|
const snapshot = buildLaneStatusSnapshot({
|
|
6218
6296
|
hqRoot,
|
|
6219
|
-
projectId
|
|
6297
|
+
projectId,
|
|
6220
6298
|
...(company ? { companySlug: company.slug } : {}),
|
|
6221
6299
|
...(company ? { companyUid: company.uid } : {}),
|
|
6222
6300
|
lanes,
|
|
@@ -6500,6 +6578,7 @@ async function runShow(ctx, laneId, hasPendingQuestionForLane, resolveLaneTarget
|
|
|
6500
6578
|
fingerprint: lane.fingerprint ?? null,
|
|
6501
6579
|
envelope_repair_mode: lane.envelope_repair?.mode ?? null,
|
|
6502
6580
|
envelope_validation_errors: lane.envelope_validation_errors ?? [],
|
|
6581
|
+
outcome: lane.outcome ?? null,
|
|
6503
6582
|
last_resume: lane.last_resume ?? null,
|
|
6504
6583
|
// Public JSON key for the bounded start audit; `last_resume` remains unchanged.
|
|
6505
6584
|
resume_history: lane.resume_history ?? [],
|
|
@@ -9162,6 +9241,7 @@ async function runCreateSubagent(ctx, opts) {
|
|
|
9162
9241
|
const createdAt = ctx.now().toISOString();
|
|
9163
9242
|
const lane = createLane(hqRoot, {
|
|
9164
9243
|
company,
|
|
9244
|
+
...(opts.signalMetadata ? { signal: opts.signalMetadata, signal_link: opts.signalLink } : {}),
|
|
9165
9245
|
...(company === null && meshCompany ? { mesh_company: meshCompany } : {}),
|
|
9166
9246
|
project_id: project,
|
|
9167
9247
|
story_id: story ?? "",
|
|
@@ -10402,6 +10482,7 @@ export function registerLanesCommand(parent, deps = {}) {
|
|
|
10402
10482
|
});
|
|
10403
10483
|
registerLaneReportCommands(lanes, {
|
|
10404
10484
|
hqRoot: ctx.hqRoot,
|
|
10485
|
+
laneRunsHere: (lane) => laneRunsHere(lane, reconcileHostFromCtx(ctx)),
|
|
10405
10486
|
resolveCompany: ctx.resolveCompany,
|
|
10406
10487
|
now: ctx.now,
|
|
10407
10488
|
focusDashboardEnabled: (company) => readLanesRegistryFlagFailOff(LANE_FOCUS_DASHBOARD_FLAG, {
|
|
@@ -10446,12 +10527,16 @@ export function registerLanesCommand(parent, deps = {}) {
|
|
|
10446
10527
|
.description("Register a lane, run admission, and return immediately")
|
|
10447
10528
|
.option("--company <slug|uid>", "Company slug or cloud uid; omit for a core/personal lane")
|
|
10448
10529
|
.option("--mesh-company <slug|uid>", "Explicit Work Mesh company for a personal lane; membership is verified")
|
|
10449
|
-
.
|
|
10530
|
+
.option("--project <slug>", "HQ project slug (executive-bot for --signal)")
|
|
10531
|
+
.option("--signal <id>", "Create one lane for a company signal")
|
|
10532
|
+
.option("--rules-file <path>", "Standing rules for a signal lane")
|
|
10533
|
+
.option("--any-type", "Allow signal types other than action_item")
|
|
10534
|
+
.option("--link <id>", "Conductor link for a signal lane")
|
|
10450
10535
|
.option("--story <id>", "Story id from the project's prd.json")
|
|
10451
10536
|
.option("--focus-override <reason>", "Human-approved reason to override the lane focus guard")
|
|
10452
10537
|
.addOption(noStoryOption)
|
|
10453
10538
|
.option("--additional-story <id>", "Additional story carried by this lane; repeatable and discouraged when active", collectRepeatable)
|
|
10454
|
-
.
|
|
10539
|
+
.option("--brief-file <path>", "Path to the lane brief (generated for --signal)")
|
|
10455
10540
|
.option("--worker <name>", "HQ worker or reusable profile id")
|
|
10456
10541
|
.option("--fresh-thread", "Skip worker continuity and start a cold provider session")
|
|
10457
10542
|
.option("--loop", "Keep one worker lane registered to process queued phase envelopes")
|
|
@@ -10697,12 +10782,13 @@ export function registerLanesCommand(parent, deps = {}) {
|
|
|
10697
10782
|
}));
|
|
10698
10783
|
lanes
|
|
10699
10784
|
.command("status")
|
|
10700
|
-
.description("Summarize project stories, lanes, PRs, blockers, and pending questions")
|
|
10701
|
-
.
|
|
10785
|
+
.description("Summarize project stories, lanes, PRs, blockers, and pending questions; with a lane id, report that lane's outcome")
|
|
10786
|
+
.argument("[lane-id]", "Report one lane's state and outcome block instead of a project")
|
|
10787
|
+
.option("--project <slug>", "HQ project slug (required without a lane id)")
|
|
10702
10788
|
.option("--company <slug|uid>", "Company project scope; omit for a personal project")
|
|
10703
10789
|
.option("--json", "Emit JSON on stdout")
|
|
10704
|
-
.action(wrap(async (opts) => {
|
|
10705
|
-
await runStatus(ctx, opts);
|
|
10790
|
+
.action(wrap(async (laneId, opts) => {
|
|
10791
|
+
await runStatus(ctx, opts, laneId);
|
|
10706
10792
|
}));
|
|
10707
10793
|
lanes
|
|
10708
10794
|
.command("capacity")
|
|
@@ -44,6 +44,7 @@ import * as readline from "node:readline";
|
|
|
44
44
|
import chalk from "chalk";
|
|
45
45
|
import { isCloudPlaceholderName, parseCloudPlaceholder } from "@indigoai-us/hq-cloud";
|
|
46
46
|
import { OFFLOAD_PREFIX, PLACEHOLDER_SUFFIX, digestFile, resolvePlaceholder } from "./storage-offload.js";
|
|
47
|
+
import { gatedInteractiveLogin } from "../utils/interactive-caller.js";
|
|
47
48
|
/** A company library blob key: `.hq-offload/blobs/<64 lowercase hex>`. */
|
|
48
49
|
const BLOB_KEY_PATTERN = /^\.hq-offload\/blobs\/([0-9a-f]{64})$/;
|
|
49
50
|
/** hq-pro caps an asset search at 50 hits. */
|
|
@@ -637,7 +638,7 @@ export function registerLibraryRemoveCommand(library) {
|
|
|
637
638
|
import("../utils/manifest.js"),
|
|
638
639
|
]);
|
|
639
640
|
const credential = await resolveVaultCredentialForCapability("files:write", "library rm", {
|
|
640
|
-
interactive:
|
|
641
|
+
interactive: gatedInteractiveLogin(),
|
|
641
642
|
});
|
|
642
643
|
const { token } = credential;
|
|
643
644
|
const apiKey = credential.kind === "api-key";
|
package/dist/commands/library.js
CHANGED
|
@@ -41,6 +41,7 @@ import { defaultLibraryIo, resolveLibrarySyncScope, runLibraryAdd, runLibraryTag
|
|
|
41
41
|
import { captionAddedAsset, extractCaptionFrames, runLibraryDescribe, runLocalClaude } from "./library-caption.js";
|
|
42
42
|
import { LIBRARY_LINK_DEFAULT_MINUTES, LIBRARY_LINK_MAX_MINUTES, runLibraryLink } from "./library-link.js";
|
|
43
43
|
import { registerLibraryRemoveCommand } from "./library-remove.js";
|
|
44
|
+
import { gatedInteractiveLogin } from "../utils/interactive-caller.js";
|
|
44
45
|
/** Mirrors hq-pro's MAX_ASSET_SEARCH_TAGS / tag length bound. */
|
|
45
46
|
export const MAX_LIBRARY_SEARCH_TAGS = 20;
|
|
46
47
|
const MAX_TAG_LENGTH = 100;
|
|
@@ -445,7 +446,7 @@ export function registerLibraryCommand(program) {
|
|
|
445
446
|
};
|
|
446
447
|
const result = await runLibrarySearch(queryParts.join(" "), searchOptions, {
|
|
447
448
|
getToken: async () => (await resolveVaultCredentialForCapability("search:query", "library search", {
|
|
448
|
-
interactive:
|
|
449
|
+
interactive: gatedInteractiveLogin(),
|
|
449
450
|
})).token,
|
|
450
451
|
post: (token, body) => vaultApiFetch({ token, path: "/v1/search", method: "POST", body }),
|
|
451
452
|
searchLocal: (query, opts) => searchLocalAssets(query, opts, defaultLocalEnv()),
|
|
@@ -476,7 +477,7 @@ export function registerLibraryCommand(program) {
|
|
|
476
477
|
.action(async (sources, options) => {
|
|
477
478
|
try {
|
|
478
479
|
const credential = await resolveVaultCredentialForCapability("files:write", "library add", {
|
|
479
|
-
interactive:
|
|
480
|
+
interactive: gatedInteractiveLogin(),
|
|
480
481
|
});
|
|
481
482
|
const { token } = credential;
|
|
482
483
|
const hqRoot = findHqRoot();
|
|
@@ -561,7 +562,7 @@ export function registerLibraryCommand(program) {
|
|
|
561
562
|
.action(async (target, options) => {
|
|
562
563
|
try {
|
|
563
564
|
const credential = await resolveVaultCredentialForCapability("files:read", "library describe", {
|
|
564
|
-
interactive:
|
|
565
|
+
interactive: gatedInteractiveLogin(),
|
|
565
566
|
});
|
|
566
567
|
const { token } = credential;
|
|
567
568
|
const hqRoot = findHqRoot();
|
|
@@ -618,7 +619,7 @@ export function registerLibraryCommand(program) {
|
|
|
618
619
|
.action(async (target, options) => {
|
|
619
620
|
try {
|
|
620
621
|
const credential = await resolveVaultCredentialForCapability("files:read", "library link", {
|
|
621
|
-
interactive:
|
|
622
|
+
interactive: gatedInteractiveLogin(),
|
|
622
623
|
});
|
|
623
624
|
const { token } = credential;
|
|
624
625
|
const result = await runLibraryLink(target, { company: options.company, expiresMinutes: options.expires }, {
|
|
@@ -6,6 +6,7 @@ import { sessionIdFromEnv } from "../lib/core-utils/session-id.js";
|
|
|
6
6
|
import { vaultApiFetch, getCompanyUid, resolveCallerPersonUid, } from "../utils/vault-api.js";
|
|
7
7
|
import { isPlanGateError, planGateErrorFromPayload, } from "../utils/plan-gate-error.js";
|
|
8
8
|
import { mapWithConcurrency } from "./sync-manifest.js";
|
|
9
|
+
import { fetchAttendedLiveBots } from "../lib/live-meetings.js";
|
|
9
10
|
import { isUsageOnly, pricingPlanFromBody, resolveCompanyPricingPlan, usageOnlyCardRequiredLines, usagePauseFromBody, } from "../lib/billing/usage-only.js";
|
|
10
11
|
const LIVE_SNAPSHOT_STATUSES = new Set([
|
|
11
12
|
"joining",
|
|
@@ -110,27 +111,6 @@ function liveBotTime(bot) {
|
|
|
110
111
|
.filter(Number.isFinite);
|
|
111
112
|
return times.length > 0 ? Math.max(...times) : Number.NEGATIVE_INFINITY;
|
|
112
113
|
}
|
|
113
|
-
/**
|
|
114
|
-
* Live meetings in this company the caller may read but did not record.
|
|
115
|
-
*
|
|
116
|
-
* `/v1/bot/list` returns only the caller's own notetakers, so a meeting a
|
|
117
|
-
* coworker recorded never appears there. The meetings API lists the live
|
|
118
|
-
* meetings the caller is an attendee of. An older server ignores `view=live`
|
|
119
|
-
* and answers with the ordinary meeting list, which has no `bots`, and any
|
|
120
|
-
* failure here leaves the caller's own list unchanged.
|
|
121
|
-
*/
|
|
122
|
-
async function fetchAttendedLiveBots(token, companyId) {
|
|
123
|
-
try {
|
|
124
|
-
const res = await vaultApiFetch({ token, path: "/v1/meetings", query: { companyId, view: "live" } });
|
|
125
|
-
if (!res.ok)
|
|
126
|
-
return [];
|
|
127
|
-
const data = (await res.json());
|
|
128
|
-
return Array.isArray(data.bots) ? data.bots : [];
|
|
129
|
-
}
|
|
130
|
-
catch {
|
|
131
|
-
return [];
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
114
|
/** The caller's own bots first; attended meetings they did not record added after. */
|
|
135
115
|
function mergeLiveMeetingBots(own, attended) {
|
|
136
116
|
const seen = new Set(own.map(liveBotId).filter((id) => Boolean(id)));
|
package/dist/commands/mesh.d.ts
CHANGED
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `hq mesh` — work-mesh verbs for agents and local sessions.
|
|
3
|
-
*
|
|
4
|
-
* Native hq-cli: Cognito + hq-pro REST + ~/.hq/work-mesh/cache. Does not
|
|
5
|
-
* require hq-pack-work-mesh on disk and does not start MQTT listen.
|
|
6
|
-
* Distinct from `hq doctor` (hook guardrails).
|
|
7
|
-
*/
|
|
8
1
|
import { Command } from "commander";
|
|
9
2
|
import { type MeshCompany, type MeshProjectRepo, type MeshProjectStoryInput, type MeshThread } from "../lib/mesh/api.js";
|
|
10
3
|
import { UnknownProjectStoryStateError } from "../lib/mesh/story-status.js";
|
package/dist/commands/mesh.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { reportingRuntimeAllowed } from "../lib/mesh/live/reporting-privacy.js";
|
|
1
2
|
/**
|
|
2
3
|
* `hq mesh` — work-mesh verbs for agents and local sessions.
|
|
3
4
|
*
|
|
@@ -1123,6 +1124,7 @@ async function runPrdSync(opts) {
|
|
|
1123
1124
|
}));
|
|
1124
1125
|
const poster = createEmitPoster({ token, baseUrl: resolveVaultApiBase(process.env) });
|
|
1125
1126
|
const emitted = await emitEvents({
|
|
1127
|
+
reportingAllowed: () => reportingRuntimeAllowed(isMachineIdentity()),
|
|
1126
1128
|
workMeshRoot: meshRoot,
|
|
1127
1129
|
poster,
|
|
1128
1130
|
newEvents: events,
|
|
@@ -2534,6 +2536,7 @@ async function runSessionEnqueue(cliKind, opts) {
|
|
|
2534
2536
|
baseUrl: resolveVaultApiBase(process.env),
|
|
2535
2537
|
});
|
|
2536
2538
|
const summary = await emitEvents({
|
|
2539
|
+
reportingAllowed: () => reportingRuntimeAllowed(isMachineIdentity()),
|
|
2537
2540
|
workMeshRoot: meshRoot,
|
|
2538
2541
|
poster,
|
|
2539
2542
|
newEvents: [event],
|
|
@@ -2617,7 +2620,7 @@ async function runMeshEmit(opts) {
|
|
|
2617
2620
|
skipped = backlog.skipped;
|
|
2618
2621
|
newEvents = backlog.events;
|
|
2619
2622
|
}
|
|
2620
|
-
const summary = await emitEvents({ workMeshRoot: meshRoot, poster, newEvents });
|
|
2623
|
+
const summary = await emitEvents({ reportingAllowed: () => reportingRuntimeAllowed(isMachineIdentity()), workMeshRoot: meshRoot, poster, newEvents });
|
|
2621
2624
|
const out = {
|
|
2622
2625
|
ok: true,
|
|
2623
2626
|
action: "emit",
|
|
@@ -2643,6 +2646,7 @@ async function runSessionFlush(opts) {
|
|
|
2643
2646
|
baseUrl: resolveVaultApiBase(process.env),
|
|
2644
2647
|
});
|
|
2645
2648
|
const summary = await flushSessionEvents({
|
|
2649
|
+
reportingAllowed: () => reportingRuntimeAllowed(isMachineIdentity()),
|
|
2646
2650
|
workMeshRoot: workMeshHomeRoot(),
|
|
2647
2651
|
workContextRoot: workContextHomeRoot(),
|
|
2648
2652
|
poster,
|
|
@@ -11,6 +11,8 @@ import { Command } from "commander";
|
|
|
11
11
|
import { type RunSignalsResult, type SignalsStepChoice, type SignalsTrigger } from "../lib/signals/engine.js";
|
|
12
12
|
import type { SignalsRunAlertSenders } from "../lib/signals/failure-alert.js";
|
|
13
13
|
interface RunCliOptions {
|
|
14
|
+
source?: string;
|
|
15
|
+
incremental?: boolean;
|
|
14
16
|
company?: string;
|
|
15
17
|
dryRun?: boolean;
|
|
16
18
|
step: SignalsStepChoice;
|
|
@@ -109,6 +109,8 @@ export async function runSignalsCli(options, overrides = {}) {
|
|
|
109
109
|
const result = await runSignals({
|
|
110
110
|
hqRoot: options.hqRoot,
|
|
111
111
|
company: options.company,
|
|
112
|
+
source: options.source,
|
|
113
|
+
incremental: options.incremental,
|
|
112
114
|
dryRun: options.dryRun === true,
|
|
113
115
|
step: options.step,
|
|
114
116
|
trigger: options.trigger,
|
|
@@ -134,7 +136,8 @@ export async function runSignalsCli(options, overrides = {}) {
|
|
|
134
136
|
process.stdout.write(`${JSON.stringify({ status, startedAt, trigger })}\n`);
|
|
135
137
|
}
|
|
136
138
|
else if (options.json) {
|
|
137
|
-
const
|
|
139
|
+
const record = result.records[0];
|
|
140
|
+
const payload = options.incremental && record ? { ...record, ...record.steps.find((s) => s.name === "extract")?.detail } : options.company && result.records.length === 1 ? record : result;
|
|
138
141
|
process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
|
|
139
142
|
}
|
|
140
143
|
else {
|
|
@@ -150,6 +153,8 @@ export function registerSignalsRunSubcommand(signals) {
|
|
|
150
153
|
run
|
|
151
154
|
.description("Run the local signals engine (garden, adapters, extract, garden, upload) for companies with capture on")
|
|
152
155
|
.option("--company <slug>", "Run one company (default: every company listed in the companies manifest)")
|
|
156
|
+
.option("--source <ref>", "Read a growing source: meeting:<id> or <channel>:<file> (needs --incremental and --company)")
|
|
157
|
+
.option("--incremental", "Extract only new signals from --source, retaining a cursor and prior signal ids")
|
|
153
158
|
.option("--dry-run", "Print what would run; run no mutating step and write nothing")
|
|
154
159
|
.addOption(new Option("--step <step>", "Run one step").choices([...SIGNALS_STEP_CHOICES]).default("all"))
|
|
155
160
|
.addOption(new Option("--trigger <trigger>", "What started the run").choices([...SIGNALS_TRIGGERS]).default("manual"))
|
|
@@ -19,6 +19,7 @@ import { VaultClient, PERSONAL_VAULT_JOURNAL_SLUG, getStateDir } from "@indigoai
|
|
|
19
19
|
import { buildVaultConfig, cognitoSubjectFromToken, ensureCognitoToken } from "../utils/cognito-session.js";
|
|
20
20
|
import { decodeJwtPayload } from "../utils/person-token-cache.js";
|
|
21
21
|
import { resolveCanonicalPersonUid } from "./cloud.js";
|
|
22
|
+
import { gatedInteractiveLogin } from "../utils/interactive-caller.js";
|
|
22
23
|
export const HELD_UNAVAILABLE_MESSAGE = "this HQ build cannot list or release held uploads yet; update HQ and try again";
|
|
23
24
|
export function fenceExports(mod = hqCloud) {
|
|
24
25
|
const list = mod.listPushIdentityFences;
|
|
@@ -157,11 +158,10 @@ export async function resolveHeldIdentity(options, io) {
|
|
|
157
158
|
return { sub, personalUid };
|
|
158
159
|
}
|
|
159
160
|
export function createDefaultSyncHeldDeps() {
|
|
160
|
-
const tty = process.stdin.isTTY === true && process.stdout.isTTY === true;
|
|
161
161
|
return {
|
|
162
162
|
exports: fenceExports(),
|
|
163
163
|
stateDir: getStateDir(),
|
|
164
|
-
identity: (options) => resolveHeldIdentity({ needPersonal: options.needPersonal, interactive: options.interactive
|
|
164
|
+
identity: (options) => resolveHeldIdentity({ needPersonal: options.needPersonal, interactive: gatedInteractiveLogin(options.interactive) }, {
|
|
165
165
|
ensureToken: (interactive) => ensureCognitoToken({ interactive }),
|
|
166
166
|
listPersons: async (token) => (await new VaultClient(buildVaultConfig(token)).entity.listByType("person")),
|
|
167
167
|
}),
|
package/dist/lanes-drain-hook.js
CHANGED
|
@@ -187,6 +187,15 @@ async function main() {
|
|
|
187
187
|
for await (const chunk of process.stdin)
|
|
188
188
|
chunks.push(Buffer.from(chunk));
|
|
189
189
|
const text = Buffer.concat(chunks).toString("utf8");
|
|
190
|
+
if (process.env.HQ_LANE_SIGNAL_LINK && process.env.HQ_ROOT) {
|
|
191
|
+
try {
|
|
192
|
+
const { bindSignalLaneHook } = await import("./lib/lanes/links.js");
|
|
193
|
+
bindSignalLaneHook(process.env.HQ_ROOT, text, process.env);
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
process.stderr.write("hq-lanes-drain: signal_link_binding_failed; conductor link remains unavailable\n");
|
|
197
|
+
}
|
|
198
|
+
}
|
|
190
199
|
let input = {};
|
|
191
200
|
let inputError = "";
|
|
192
201
|
try {
|
package/dist/lib/bot/api.d.ts
CHANGED
|
@@ -62,6 +62,11 @@ export interface JobRunCompletion {
|
|
|
62
62
|
}
|
|
63
63
|
/** A job record as the cloud returns it (hq-pro-agents jobs/schema.ts `JobRecord`). */
|
|
64
64
|
export interface BotJobRecord {
|
|
65
|
+
trigger?: {
|
|
66
|
+
kind: "event";
|
|
67
|
+
event: string;
|
|
68
|
+
filter?: Record<string, string>;
|
|
69
|
+
};
|
|
65
70
|
jobId: string;
|
|
66
71
|
prompt: string;
|
|
67
72
|
status?: string;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export declare const CLAUDE_KEYCHAIN_SERVICE = "Claude Code-credentials";
|
|
2
|
+
export declare const CLAUDE_LOGIN_ENV_VARS: readonly ["ANTHROPIC_TOKEN", "CLAUDE_CODE_OAUTH_TOKEN", "ANTHROPIC_API_KEY"];
|
|
3
|
+
export type ClaudeLoginStatus = "ok" | "missing" | "expired" | "skipped";
|
|
4
|
+
export interface ClaudeLoginSource {
|
|
5
|
+
source: string;
|
|
6
|
+
status: ClaudeLoginStatus;
|
|
7
|
+
reason: string;
|
|
8
|
+
}
|
|
9
|
+
export interface ClaudeLoginCheck {
|
|
10
|
+
signedIn: boolean;
|
|
11
|
+
/** The first source the runtime would use, when signed in. */
|
|
12
|
+
via: string | null;
|
|
13
|
+
sources: ClaudeLoginSource[];
|
|
14
|
+
}
|
|
15
|
+
export interface KeychainRead {
|
|
16
|
+
code: number;
|
|
17
|
+
stdout: string;
|
|
18
|
+
stderr: string;
|
|
19
|
+
}
|
|
20
|
+
export interface ClaudeLoginDeps {
|
|
21
|
+
env?: NodeJS.ProcessEnv;
|
|
22
|
+
home?: string;
|
|
23
|
+
platform?: NodeJS.Platform;
|
|
24
|
+
readKeychain?: () => KeychainRead;
|
|
25
|
+
now?: () => number;
|
|
26
|
+
}
|
|
27
|
+
export declare function checkClaudeCodeLogin(deps?: ClaudeLoginDeps): ClaudeLoginCheck;
|
|
28
|
+
/** One sentence listing every source that did not yield a login. */
|
|
29
|
+
export declare function describeClaudeLoginSources(check: ClaudeLoginCheck): string;
|
|
30
|
+
export declare function claudeLoginFix(botName?: string): string;
|
|
31
|
+
//# sourceMappingURL=claude-login.d.ts.map
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Can a Claude bot on the HQ Fleet Runtime read a Claude Code login here?
|
|
3
|
+
*
|
|
4
|
+
* The runtime never runs the `claude` binary; it calls Anthropic with the
|
|
5
|
+
* owner's Claude Code OAuth token, read in this order (its
|
|
6
|
+
* `resolve_anthropic_token`): ANTHROPIC_TOKEN, CLAUDE_CODE_OAUTH_TOKEN and
|
|
7
|
+
* ANTHROPIC_API_KEY from the environment, the HQ Fleet login file,
|
|
8
|
+
* ~/.claude/.credentials.json, then the macOS Keychain item
|
|
9
|
+
* "Claude Code-credentials". This mirrors that order so `hq doctor` and
|
|
10
|
+
* `hq bot create` can say which sources were checked and why each failed,
|
|
11
|
+
* without printing a token.
|
|
12
|
+
*/
|
|
13
|
+
import { execFileSync } from "node:child_process";
|
|
14
|
+
import * as fs from "node:fs";
|
|
15
|
+
import * as os from "node:os";
|
|
16
|
+
import * as path from "node:path";
|
|
17
|
+
import { fleetHome } from "./fleet-local.js";
|
|
18
|
+
export const CLAUDE_KEYCHAIN_SERVICE = "Claude Code-credentials";
|
|
19
|
+
export const CLAUDE_LOGIN_ENV_VARS = ["ANTHROPIC_TOKEN", "CLAUDE_CODE_OAUTH_TOKEN", "ANTHROPIC_API_KEY"];
|
|
20
|
+
function readKeychainWithSecurity() {
|
|
21
|
+
try {
|
|
22
|
+
const stdout = execFileSync("security", ["find-generic-password", "-s", CLAUDE_KEYCHAIN_SERVICE, "-w"], {
|
|
23
|
+
encoding: "utf8",
|
|
24
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
25
|
+
timeout: 5000,
|
|
26
|
+
});
|
|
27
|
+
return { code: 0, stdout, stderr: "" };
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
const failure = error;
|
|
31
|
+
const stderr = failure.stderr ? String(failure.stderr) : (failure.message ?? "");
|
|
32
|
+
return { code: typeof failure.status === "number" ? failure.status : -1, stdout: "", stderr };
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function oauthRecord(value) {
|
|
36
|
+
if (!value || typeof value !== "object")
|
|
37
|
+
return null;
|
|
38
|
+
const record = value;
|
|
39
|
+
const accessToken = typeof record.accessToken === "string" ? record.accessToken.trim() : "";
|
|
40
|
+
if (!accessToken)
|
|
41
|
+
return null;
|
|
42
|
+
return {
|
|
43
|
+
accessToken,
|
|
44
|
+
refreshToken: typeof record.refreshToken === "string" ? record.refreshToken : "",
|
|
45
|
+
expiresAt: typeof record.expiresAt === "number" ? record.expiresAt : 0,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function describeRecord(record, now) {
|
|
49
|
+
if (!record.expiresAt || now < record.expiresAt - 60_000) {
|
|
50
|
+
return { status: "ok", reason: "access token present and not expired", usable: true };
|
|
51
|
+
}
|
|
52
|
+
if (record.refreshToken) {
|
|
53
|
+
return { status: "expired", reason: "access token expired; the bot refreshes it with the refresh token", usable: true };
|
|
54
|
+
}
|
|
55
|
+
return { status: "expired", reason: "access token expired and no refresh token", usable: false };
|
|
56
|
+
}
|
|
57
|
+
function readJsonFile(file) {
|
|
58
|
+
if (!fs.existsSync(file))
|
|
59
|
+
return { parsed: null, problem: `${file} does not exist` };
|
|
60
|
+
try {
|
|
61
|
+
return { parsed: JSON.parse(fs.readFileSync(file, "utf8")), problem: null };
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
return { parsed: null, problem: `${file} could not be read (${error.message})` };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
export function checkClaudeCodeLogin(deps = {}) {
|
|
68
|
+
const env = deps.env ?? process.env;
|
|
69
|
+
const home = deps.home ?? os.homedir();
|
|
70
|
+
const platform = deps.platform ?? process.platform;
|
|
71
|
+
const now = (deps.now ?? Date.now)();
|
|
72
|
+
const sources = [];
|
|
73
|
+
let via = null;
|
|
74
|
+
const found = (source) => {
|
|
75
|
+
if (via === null)
|
|
76
|
+
via = source;
|
|
77
|
+
};
|
|
78
|
+
for (const name of CLAUDE_LOGIN_ENV_VARS) {
|
|
79
|
+
const set = !!env[name]?.trim();
|
|
80
|
+
sources.push({ source: `env:${name}`, status: set ? "ok" : "missing", reason: set ? "set" : "not set in this environment" });
|
|
81
|
+
if (set)
|
|
82
|
+
found(`env:${name}`);
|
|
83
|
+
}
|
|
84
|
+
const fleetLogin = path.join(fleetHome(home, env), ".anthropic_oauth.json");
|
|
85
|
+
const fleetFile = readJsonFile(fleetLogin);
|
|
86
|
+
const fleetRecord = fleetFile.parsed ? oauthRecord(fleetFile.parsed) : null;
|
|
87
|
+
if (fleetRecord) {
|
|
88
|
+
const described = describeRecord(fleetRecord, now);
|
|
89
|
+
sources.push({ source: "hq_fleet_oauth_file", status: described.status, reason: described.reason });
|
|
90
|
+
if (described.usable)
|
|
91
|
+
found("hq_fleet_oauth_file");
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
sources.push({ source: "hq_fleet_oauth_file", status: "missing", reason: fleetFile.problem ?? `${fleetLogin} holds no access token` });
|
|
95
|
+
}
|
|
96
|
+
const credentialsFile = path.join(home, ".claude", ".credentials.json");
|
|
97
|
+
const claudeFile = readJsonFile(credentialsFile);
|
|
98
|
+
const claudeRecord = claudeFile.parsed ? oauthRecord(claudeFile.parsed.claudeAiOauth) : null;
|
|
99
|
+
if (claudeRecord) {
|
|
100
|
+
const described = describeRecord(claudeRecord, now);
|
|
101
|
+
sources.push({ source: "claude_code_credentials_file", status: described.status, reason: described.reason });
|
|
102
|
+
if (described.usable)
|
|
103
|
+
found("claude_code_credentials_file");
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
sources.push({
|
|
107
|
+
source: "claude_code_credentials_file",
|
|
108
|
+
status: "missing",
|
|
109
|
+
reason: claudeFile.problem ?? `${credentialsFile} holds no claudeAiOauth access token`,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
if (platform !== "darwin") {
|
|
113
|
+
sources.push({ source: "macos_keychain", status: "skipped", reason: "not macOS" });
|
|
114
|
+
}
|
|
115
|
+
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 {
|
|
122
|
+
let payload;
|
|
123
|
+
try {
|
|
124
|
+
payload = JSON.parse(read.stdout.trim());
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
payload = null;
|
|
128
|
+
}
|
|
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");
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
sources.push({ source: "macos_keychain", status: "missing", reason: "keychain item has no claudeAiOauth access token" });
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return { signedIn: via !== null, via, sources };
|
|
142
|
+
}
|
|
143
|
+
const SOURCE_LABELS = {
|
|
144
|
+
hq_fleet_oauth_file: "the HQ Fleet login file",
|
|
145
|
+
claude_code_credentials_file: "~/.claude/.credentials.json",
|
|
146
|
+
macos_keychain: "the macOS Keychain",
|
|
147
|
+
};
|
|
148
|
+
/** One sentence listing every source that did not yield a login. */
|
|
149
|
+
export function describeClaudeLoginSources(check) {
|
|
150
|
+
const parts = check.sources
|
|
151
|
+
.filter((entry) => entry.status !== "ok" && entry.status !== "skipped")
|
|
152
|
+
.map((entry) => `${SOURCE_LABELS[entry.source] ?? entry.source.replace("env:", "")}: ${entry.reason}`);
|
|
153
|
+
return parts.length ? `Checked ${parts.join("; ")}.` : "";
|
|
154
|
+
}
|
|
155
|
+
export function claudeLoginFix(botName) {
|
|
156
|
+
return botName
|
|
157
|
+
? `Run \`claude /login\` in a terminal on this computer, then \`hq bot restart ${botName}\`.`
|
|
158
|
+
: "Run `claude /login` in a terminal on this computer, then retry.";
|
|
159
|
+
}
|
|
160
|
+
//# sourceMappingURL=claude-login.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { InboxItem } from "./api.js";
|
|
2
|
+
import { type LocalJob } from "./local-jobs.js";
|
|
3
|
+
/** Rotate journals at five MiB without discarding unread records. */
|
|
4
|
+
export declare const EVENT_ROTATE_BYTES: number;
|
|
5
|
+
export declare const EVENT_PAYLOAD_BYTES: number;
|
|
6
|
+
export interface BotEvent {
|
|
7
|
+
event: string;
|
|
8
|
+
at: string;
|
|
9
|
+
payload: Record<string, unknown>;
|
|
10
|
+
}
|
|
11
|
+
export declare function validateEventPayload(payload: unknown): asserts payload is Record<string, unknown>;
|
|
12
|
+
export declare function emitBotEvent(dir: string, event: string, payload: unknown, now?: Date, alreadyEmitted?: (record: BotEvent) => boolean): BotEvent;
|
|
13
|
+
export declare function eventMatches(job: LocalJob, record: BotEvent): boolean;
|
|
14
|
+
/** Pending items survive a crash between queueing and the next poll. */
|
|
15
|
+
export declare function drainBotEvents(dir: string): InboxItem[];
|
|
16
|
+
export declare function ackBotEvent(dir: string, messageId: string): void;
|
|
17
|
+
export declare function tailBotEvents(dir: string, predicate?: (event: BotEvent) => boolean): BotEvent[];
|
|
18
|
+
//# sourceMappingURL=events.d.ts.map
|