@indigoai-us/hq-cli 5.345.107 → 5.345.108

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 CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.345.108] — 2026-10-13
6
+ - On macOS, `hq` reads your sign-in from the Keychain copy kept by the desktop app and uses whichever of the Keychain and `~/.hq/cognito-tokens.json` is newer (hq-cloud 6.18.67). macOS asks once for access; if you deny it, `hq` keeps using the file until you run `hq auth keychain --retry`.
7
+
8
+ - `hq capacity route` and `hq lanes create --account auto` now pick the
9
+ provider account with the most room, across Claude, Codex, and Grok. All
10
+ accounts are treated as one pool across companies, so you can keep working
11
+ on whichever provider still has usage left this cycle.
12
+ - The native checkpoint stop gate now recognizes Windows executable extensions such as `.exe`, matching the shell's hook-state directory creation when `jq` is installed through Git Bash.
13
+
5
14
  ## [5.345.107] — 2026-10-13
6
15
  - Company Outposts: `hq outposts provision --company <slug>`, `hq outposts status --company <slug>` and `hq outposts jobs register` are available (hq-cloud 6.18.66). Only company admins can run them, the price and paying company are shown before anything is created, and nothing is provisioned without `--yes`.
7
16
  - Agent processes on one box share one cached Cognito sign-in again instead of each signing in separately (hq-cloud 6.18.66 fix).
@@ -9630,6 +9630,45 @@ export declare const COMMAND_CATALOG: readonly [{
9630
9630
  readonly hidden: false;
9631
9631
  }];
9632
9632
  readonly subcommands: readonly [];
9633
+ }, {
9634
+ readonly name: "route";
9635
+ readonly description: "Pick the account with the most capacity for a new lane (read-only; port of boss-route)";
9636
+ readonly aliases: readonly [];
9637
+ readonly hidden: false;
9638
+ readonly helpOption: true;
9639
+ readonly helpCommand: false;
9640
+ readonly usage: "[options]";
9641
+ readonly helpText: readonly [];
9642
+ readonly arguments: readonly [];
9643
+ readonly options: readonly [{
9644
+ readonly flags: "--provider <list>";
9645
+ readonly description: "Comma-separated list: any, claude, codex, grok";
9646
+ readonly hidden: false;
9647
+ readonly defaultValue: "any";
9648
+ }, {
9649
+ readonly flags: "--model <id>";
9650
+ readonly description: "Model id. Narrows --provider any to the mapped provider";
9651
+ readonly hidden: false;
9652
+ }, {
9653
+ readonly flags: "--prefer <list>";
9654
+ readonly description: "Comma-separated preferred provider order";
9655
+ readonly hidden: false;
9656
+ readonly defaultValue: "claude,codex,grok";
9657
+ }, {
9658
+ readonly flags: "--floor <percent>";
9659
+ readonly description: "Minimum headroom percent the pick must clear (0-100)";
9660
+ readonly hidden: false;
9661
+ readonly defaultValue: "15";
9662
+ }, {
9663
+ readonly flags: "--json";
9664
+ readonly description: "Print the route result as JSON (default)";
9665
+ readonly hidden: false;
9666
+ }, {
9667
+ readonly flags: "--text";
9668
+ readonly description: "Print a single-line text summary instead of JSON";
9669
+ readonly hidden: false;
9670
+ }];
9671
+ readonly subcommands: readonly [];
9633
9672
  }, {
9634
9673
  readonly name: "enable";
9635
9674
  readonly description: "Create the ledger directory and mark the collector on";
@@ -13082,7 +13121,7 @@ export declare const COMMAND_CATALOG: readonly [{
13082
13121
  readonly hidden: false;
13083
13122
  }, {
13084
13123
  readonly flags: "--account <id>";
13085
- readonly description: "Provider account id";
13124
+ readonly description: "Provider account id, or 'auto' to pick the account with the most room (all accounts are one pool across companies)";
13086
13125
  readonly hidden: false;
13087
13126
  }, {
13088
13127
  readonly flags: "--model <id>";
@@ -11675,6 +11675,53 @@ export const COMMAND_CATALOG = [
11675
11675
  ],
11676
11676
  "subcommands": []
11677
11677
  },
11678
+ {
11679
+ "name": "route",
11680
+ "description": "Pick the account with the most capacity for a new lane (read-only; port of boss-route)",
11681
+ "aliases": [],
11682
+ "hidden": false,
11683
+ "helpOption": true,
11684
+ "helpCommand": false,
11685
+ "usage": "[options]",
11686
+ "helpText": [],
11687
+ "arguments": [],
11688
+ "options": [
11689
+ {
11690
+ "flags": "--provider <list>",
11691
+ "description": "Comma-separated list: any, claude, codex, grok",
11692
+ "hidden": false,
11693
+ "defaultValue": "any"
11694
+ },
11695
+ {
11696
+ "flags": "--model <id>",
11697
+ "description": "Model id. Narrows --provider any to the mapped provider",
11698
+ "hidden": false
11699
+ },
11700
+ {
11701
+ "flags": "--prefer <list>",
11702
+ "description": "Comma-separated preferred provider order",
11703
+ "hidden": false,
11704
+ "defaultValue": "claude,codex,grok"
11705
+ },
11706
+ {
11707
+ "flags": "--floor <percent>",
11708
+ "description": "Minimum headroom percent the pick must clear (0-100)",
11709
+ "hidden": false,
11710
+ "defaultValue": "15"
11711
+ },
11712
+ {
11713
+ "flags": "--json",
11714
+ "description": "Print the route result as JSON (default)",
11715
+ "hidden": false
11716
+ },
11717
+ {
11718
+ "flags": "--text",
11719
+ "description": "Print a single-line text summary instead of JSON",
11720
+ "hidden": false
11721
+ }
11722
+ ],
11723
+ "subcommands": []
11724
+ },
11678
11725
  {
11679
11726
  "name": "enable",
11680
11727
  "description": "Create the ledger directory and mark the collector on",
@@ -15907,7 +15954,7 @@ export const COMMAND_CATALOG = [
15907
15954
  },
15908
15955
  {
15909
15956
  "flags": "--account <id>",
15910
- "description": "Provider account id",
15957
+ "description": "Provider account id, or 'auto' to pick the account with the most room (all accounts are one pool across companies)",
15911
15958
  "hidden": false
15912
15959
  },
15913
15960
  {
@@ -54,7 +54,7 @@ export interface DoctorReport {
54
54
  stale: boolean;
55
55
  ageMs: number;
56
56
  }
57
- export declare function capacityOptionsFromEnv(env?: NodeJS.ProcessEnv): LedgerOptions;
57
+ export { capacityOptionsFromEnv } from "../lib/capacity/ledger.js";
58
58
  export declare function withSchemaVersion(view: NowView): Omit<CapacityJson, "stale" | "ageMs" | "collectorEnabled">;
59
59
  /** Stale when generatedAt is missing or older than five minutes. */
60
60
  export declare function capacityFreshness(generatedAt: string, nowMs: number): CapacityFreshness;
@@ -3,12 +3,15 @@ import * as fs from "node:fs";
3
3
  import * as os from "node:os";
4
4
  import * as path from "node:path";
5
5
  import * as readline from "node:readline";
6
- import { LedgerCorrupt, LedgerMissing, READING_SOURCES, capacityRoot, nowViewJsonSchema, readCollectorMark, readLedgerEvents, readNow, rebuildNow, setCollectorEnabled, } from "../lib/capacity/ledger.js";
6
+ import { LedgerCorrupt, LedgerMissing, READING_SOURCES, capacityOptionsFromEnv, capacityRoot, nowViewJsonSchema, readCollectorMark, readLedgerEvents, readNow, rebuildNow, setCollectorEnabled, } from "../lib/capacity/ledger.js";
7
7
  import { listCapacityAccounts, registryAccountIds } from "../lib/capacity/accounts.js";
8
8
  import { claudeIdentityKeys } from "../lib/capacity/collectors/claude-oauth.js";
9
9
  import { projectCapacityView } from "../lib/capacity/project.js";
10
10
  import { capacityWidgetTimeZone, renderCapacityWidget } from "../lib/capacity/widget.js";
11
11
  import { resolveLanesHqRoot } from "../lib/lanes/hq-root.js";
12
+ import { loadLanesConfig } from "../lib/lanes/config.js";
13
+ import { readAllLanes } from "../lib/lanes/store.js";
14
+ import { RouteUsageError, formatRouteText, pickAccount, resolveFloor, } from "../lib/capacity/route.js";
12
15
  import { SignInUsageError, executeCapacityLogin, formatCredits, formatLoginText, formatRefreshLine, formatRoster, generateAccountId, loginJson, presentAccount, promptsAllowed, removeCapacityAccount, renameCapacityAccount, rosterFrom, } from "../lib/capacity/sign-in.js";
13
16
  import { ingestClaudeStatuslinePayload, installStatuslineSpoolScript } from "../lib/capacity/collectors/claude-statusline.js";
14
17
  import { CapacityUsageError, REFUSAL_HELP, ingestRefusal, } from "../lib/capacity/refusal.js";
@@ -45,16 +48,7 @@ const HISTORY_HEADERS = [
45
48
  "SOURCE",
46
49
  "CLI VERSION",
47
50
  ];
48
- export function capacityOptionsFromEnv(env = process.env) {
49
- const raw = env.HQ_CAPACITY_NOW?.trim();
50
- if (!raw)
51
- return { env };
52
- const parsed = Date.parse(raw);
53
- if (Number.isNaN(parsed)) {
54
- throw new Error(`HQ_CAPACITY_NOW must be a UTC timestamp, got '${raw}'`);
55
- }
56
- return { env, now: () => new Date(raw) };
57
- }
51
+ export { capacityOptionsFromEnv } from "../lib/capacity/ledger.js";
58
52
  export function withSchemaVersion(view) {
59
53
  return { ...view, schemaVersion: CAPACITY_SCHEMA_VERSION };
60
54
  }
@@ -297,6 +291,69 @@ export function registerCapacityCommand(program) {
297
291
  process.exitCode = 1;
298
292
  });
299
293
  });
294
+ capacity
295
+ .command("route")
296
+ .description("Pick the account with the most capacity for a new lane (read-only; port of boss-route)")
297
+ .option("--provider <list>", "Comma-separated list: any, claude, codex, grok", "any")
298
+ .option("--model <id>", "Model id. Narrows --provider any to the mapped provider")
299
+ .option("--prefer <list>", "Comma-separated preferred provider order", "claude,codex,grok")
300
+ .option("--floor <percent>", "Minimum headroom percent the pick must clear (0-100)", "15")
301
+ .option("--json", "Print the route result as JSON (default)")
302
+ .option("--text", "Print a single-line text summary instead of JSON")
303
+ .action((_opts, command) => {
304
+ runCli(() => {
305
+ const opts = command.optsWithGlobals();
306
+ if (opts.json && opts.text) {
307
+ throw new RouteUsageError("--text cannot be combined with --json");
308
+ }
309
+ const clock = capacityClock();
310
+ const view = loadNow(clock.options);
311
+ const freshness = capacityFreshness(view.generatedAt, clock.nowMs);
312
+ const mark = readCollectorMark(clock.options);
313
+ const hqRoot = resolveLanesHqRoot({ env: process.env });
314
+ const lanesConfig = loadLanesConfig(hqRoot);
315
+ let lanes = [];
316
+ let lanesError = null;
317
+ try {
318
+ lanes = readAllLanes(hqRoot);
319
+ }
320
+ catch (err) {
321
+ lanesError = err instanceof Error ? err.message : String(err);
322
+ }
323
+ let result;
324
+ try {
325
+ result = pickAccount({
326
+ view,
327
+ lanes,
328
+ lanesConfig,
329
+ providers: splitList(opts.provider),
330
+ model: opts.model ?? null,
331
+ prefer: splitList(opts.prefer),
332
+ floor: resolveFloor(opts.floor),
333
+ now: new Date(clock.nowMs),
334
+ collectorEnabled: mark ? mark.enabled : null,
335
+ capacityStale: freshness.stale,
336
+ lanesError,
337
+ });
338
+ }
339
+ catch (err) {
340
+ if (err instanceof RouteUsageError) {
341
+ console.error(err.message);
342
+ process.exitCode = 1;
343
+ return;
344
+ }
345
+ throw err;
346
+ }
347
+ if (opts.text) {
348
+ console.log(formatRouteText(result));
349
+ }
350
+ else {
351
+ console.log(JSON.stringify(result, null, 2));
352
+ }
353
+ if (!result.ok)
354
+ process.exitCode = 2;
355
+ });
356
+ });
300
357
  capacity
301
358
  .command("enable")
302
359
  .description("Create the ledger directory and mark the collector on")
@@ -594,6 +651,12 @@ function wantsJson(opts, command) {
594
651
  }
595
652
  return false;
596
653
  }
654
+ function splitList(raw) {
655
+ if (raw === undefined)
656
+ return undefined;
657
+ const parts = raw.split(",").map((v) => v.trim()).filter(Boolean);
658
+ return parts.length === 0 ? undefined : parts;
659
+ }
597
660
  function runCli(action) {
598
661
  try {
599
662
  process.exitCode = 0;
@@ -73,15 +73,27 @@ const defaults = () => ({
73
73
  },
74
74
  makeTempPath: () => path.join(os.tmpdir(), `tmp.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}`),
75
75
  commandExists: (name, env) => {
76
+ const windowsExtensions = process.platform === "win32"
77
+ ? (env?.PATHEXT ?? process.env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean)
78
+ : [];
76
79
  for (const entry of (env?.PATH ?? process.env.PATH ?? "").split(path.delimiter)) {
77
80
  if (!entry)
78
81
  continue;
79
- const candidate = path.join(entry, name);
80
- try {
81
- fs.accessSync(candidate, fs.constants.X_OK);
82
- return true;
82
+ const candidates = [name, ...windowsExtensions.map((extension) => `${name}${extension}`)];
83
+ for (const command of candidates) {
84
+ const candidate = path.join(entry, command);
85
+ try {
86
+ if (process.platform === "win32") {
87
+ if (fs.statSync(candidate).isFile())
88
+ return true;
89
+ }
90
+ else {
91
+ fs.accessSync(candidate, fs.constants.X_OK);
92
+ return true;
93
+ }
94
+ }
95
+ catch { /* try the next candidate in PATH */ }
83
96
  }
84
- catch { /* try the next PATH entry */ }
85
97
  }
86
98
  return false;
87
99
  },
@@ -85,6 +85,8 @@ import { writeQuestionBody } from "../lib/lanes/questions.js";
85
85
  import { compactionAfterUsage, } from "../lib/lanes/compaction.js";
86
86
  import { parseCliEffort, resolveLaneEffort, } from "../lib/lanes/effort.js";
87
87
  import { LanesError } from "../lib/lanes/errors.js";
88
+ import { LedgerCorrupt, capacityOptionsFromEnv, readNow, rebuildNow, } from "../lib/capacity/ledger.js";
89
+ import { pickAccount } from "../lib/capacity/route.js";
88
90
  import { readLatestEnvelopeRecord } from "../lib/lanes/watcher.js";
89
91
  import { loadQuotaState, quotaBlockFor } from "../lib/lanes/quota.js";
90
92
  import { SESSION_MOVE_REASON } from "../lib/lanes/move-account.js";
@@ -3262,9 +3264,61 @@ async function runCreate(ctx, opts) {
3262
3264
  if (!briefFile) {
3263
3265
  throw new LanesError("missing_brief_file", "--brief-file is required", {});
3264
3266
  }
3265
- const explicitProvider = opts.provider
3267
+ let explicitProvider = opts.provider
3266
3268
  ? parseLaneProvider(opts.provider)
3267
3269
  : undefined;
3270
+ let autoAccountPick;
3271
+ let autoChosenProvider;
3272
+ let autoChosenAccount;
3273
+ if (opts.account !== undefined && opts.account.trim() === "auto") {
3274
+ const ledgerOptions = capacityOptionsFromEnv(ctx.env);
3275
+ let view;
3276
+ try {
3277
+ view = readNow(ledgerOptions);
3278
+ }
3279
+ catch (err) {
3280
+ if (err instanceof LedgerCorrupt) {
3281
+ view = rebuildNow(ledgerOptions);
3282
+ }
3283
+ else {
3284
+ throw new LanesError("capacity_unavailable", `Cannot route --account auto: ${err instanceof Error ? err.message : String(err)}`, {});
3285
+ }
3286
+ }
3287
+ const routeConfig = loadLanesConfig(hqRoot);
3288
+ let routeLanes = [];
3289
+ let routeLanesError = null;
3290
+ try {
3291
+ routeLanes = readAllLanes(hqRoot);
3292
+ }
3293
+ catch (err) {
3294
+ routeLanesError = err instanceof Error ? err.message : String(err);
3295
+ }
3296
+ const result = pickAccount({
3297
+ view,
3298
+ lanes: routeLanes,
3299
+ lanesConfig: routeConfig,
3300
+ ...(explicitProvider ? { providers: [explicitProvider] } : {}),
3301
+ model: opts.model ?? null,
3302
+ now: ctx.now(),
3303
+ lanesError: routeLanesError,
3304
+ });
3305
+ if (!result.ok || !result.pick) {
3306
+ const resetSuffix = result.next_reset ? `; next reset ${result.next_reset}` : "";
3307
+ throw new LanesError("no_account_with_room", `No account has room${resetSuffix}`, {
3308
+ next_reset: result.next_reset,
3309
+ request: result.request,
3310
+ });
3311
+ }
3312
+ autoChosenProvider = parseLaneProvider(result.pick.provider);
3313
+ autoChosenAccount = result.pick.account;
3314
+ autoAccountPick = {
3315
+ why: result.pick.why,
3316
+ headroom: result.pick.headroom,
3317
+ score: result.pick.score,
3318
+ };
3319
+ opts = { ...opts, account: autoChosenAccount, provider: autoChosenProvider };
3320
+ explicitProvider = autoChosenProvider;
3321
+ }
3268
3322
  if (opts.outpost !== undefined && opts.outpost !== "") {
3269
3323
  const requested = opts.outpost.trim();
3270
3324
  let known;
@@ -3975,18 +4029,23 @@ async function runCreate(ctx, opts) {
3975
4029
  },
3976
4030
  }
3977
4031
  : {}),
3978
- provider_source: explicitProvider !== undefined
3979
- ? "cli"
3980
- : profile?.meta.provider !== undefined
3981
- ? "profile"
3982
- : "default",
4032
+ provider_source: autoAccountPick !== undefined
4033
+ ? "auto"
4034
+ : explicitProvider !== undefined
4035
+ ? "cli"
4036
+ : profile?.meta.provider !== undefined
4037
+ ? "profile"
4038
+ : "default",
3983
4039
  account: pinnedAccount,
3984
- account_source: accountWasExplicit
3985
- ? "cli"
3986
- : profileAccount !== undefined
3987
- ? "profile"
3988
- : "balanced",
4040
+ account_source: autoAccountPick !== undefined
4041
+ ? "auto"
4042
+ : accountWasExplicit
4043
+ ? "cli"
4044
+ : profileAccount !== undefined
4045
+ ? "profile"
4046
+ : "balanced",
3989
4047
  ...(accountSelection !== undefined ? { account_selection: accountSelection } : {}),
4048
+ ...(autoAccountPick !== undefined ? { account_pick: autoAccountPick } : {}),
3990
4049
  ...(defaultPlacement !== undefined
3991
4050
  ? { placement_default: defaultPlacement }
3992
4051
  : {}),
@@ -4220,6 +4279,7 @@ async function runCreate(ctx, opts) {
4220
4279
  priority: started.priority,
4221
4280
  provider: started.provider,
4222
4281
  account: started.account ?? null,
4282
+ ...(started.account_pick !== undefined ? { account_pick: started.account_pick } : {}),
4223
4283
  model: started.model ?? null,
4224
4284
  effort: started.effort ?? null,
4225
4285
  effort_source: started.effort_source ?? null,
@@ -9671,7 +9731,7 @@ export function registerLanesCommand(parent, deps = {}) {
9671
9731
  .option("--loop", "Keep one worker lane registered to process queued phase envelopes")
9672
9732
  .option("--provider <name>", "claude | codex | grok")
9673
9733
  .option("--remote-control", "Open a Claude lane in Remote Control using account a for login and the selected account for inference")
9674
- .option("--account <id>", "Provider account id")
9734
+ .option("--account <id>", "Provider account id, or 'auto' to pick the account with the most room (all accounts are one pool across companies)")
9675
9735
  .option("--model <id>", "Model id for the provider CLI")
9676
9736
  .option("--effort <level>", "Effort level; claude: low|medium|high|xhigh|max, codex: minimal|low|medium|high|xhigh|max|ultra, grok: low|medium|high|xhigh")
9677
9737
  .option("--senior <ref>", "session:<id> | lane:<id> | user")