@indigoai-us/hq-cli 5.345.111 → 5.345.113
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 +20 -0
- package/dist/command-catalog.generated.d.ts +4 -0
- package/dist/command-catalog.generated.js +5 -0
- package/dist/commands/capacity.js +1 -1
- package/dist/commands/daemon.d.ts +2 -0
- package/dist/commands/daemon.js +70 -1
- package/dist/hq-cloud-sync-runner/bin/sync-runner.js +180 -180
- package/dist/hq-cloud-sync-runner/package.json +1 -1
- package/dist/lib/capacity/collectors/claude-oauth.d.ts +16 -0
- package/dist/lib/capacity/collectors/claude-oauth.js +57 -5
- package/dist/lib/capacity/route.js +11 -0
- package/dist/lib/daemon/catalog.d.ts +2 -0
- package/dist/lib/daemon/catalog.js +1 -1
- package/dist/lib/daemon/paths.d.ts +2 -0
- package/dist/lib/daemon/paths.js +1 -0
- package/dist/lib/daemon/run.js +2 -0
- package/dist/lib/daemon/supervisor.d.ts +2 -0
- package/dist/lib/daemon/supervisor.js +30 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.345.113] — 2026-10-13
|
|
6
|
+
- `hq daemon sync now --company <slug>` asks the running sync daemon to sync one company right away, without restarting sync. It needs hq-cloud 6.18.68 or later in the daemon; with an older daemon it falls back to the previous restart. `--json` reports which path it took. The desktop app uses this for Sync on the "Added to" banner.
|
|
7
|
+
|
|
8
|
+
- `hq capacity` no longer ranks a Claude account the ledger marks as stale
|
|
9
|
+
(sign-in expired and no reading since). The router skips it with "no fresh
|
|
10
|
+
reading (sign-in expired)" and prints the account's fix command.
|
|
11
|
+
- A Claude home listed under `accounts` now clears an old revoked marker
|
|
12
|
+
once a new sign-in lands, including when a fresh usage poll would
|
|
13
|
+
otherwise skip reading the credential. A marker that still matches the
|
|
14
|
+
current refresh token keeps the account blocked as revoked.
|
|
15
|
+
- After a token refresh, the account stays blocked as "missing profile
|
|
16
|
+
scope" when the refreshed grant lacks that scope, instead of being
|
|
17
|
+
cleared.
|
|
18
|
+
- The live-lane gate finds the `hq` CLI from the running process instead of
|
|
19
|
+
`PATH`, so the refresh gate works when `PATH` is empty or minimal.
|
|
20
|
+
- `hq capacity doctor` renames the "TOKEN AGE" column to "SINCE REFRESH".
|
|
21
|
+
|
|
22
|
+
## [5.345.112] — 2026-10-13
|
|
23
|
+
- The bundled sync engine now uses hq-cloud 6.18.71. The hq-core update no longer replaces pack-installed worker links (for example `core/workers/public/qa-tester`) with a copy of the release folder.
|
|
24
|
+
|
|
5
25
|
## [5.345.111] — 2026-10-13
|
|
6
26
|
- The local bot runs a Create brief job from the desktop app in a fresh model session of its own instead of resuming the owner's whole DM conversation, and schedules it like a DM rather than behind the job slot. A resumed session that fails twice in a row with the model CLI's "An unknown error occurred (Unexpected)" is cleared so the next turn starts fresh; a single failure never clears it.
|
|
7
27
|
|
|
@@ -15484,6 +15484,10 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
15484
15484
|
readonly helpText: readonly [];
|
|
15485
15485
|
readonly arguments: readonly [];
|
|
15486
15486
|
readonly options: readonly [{
|
|
15487
|
+
readonly flags: "--company <slug>";
|
|
15488
|
+
readonly description: "Sync one company";
|
|
15489
|
+
readonly hidden: false;
|
|
15490
|
+
}, {
|
|
15487
15491
|
readonly flags: "--json";
|
|
15488
15492
|
readonly description: "Print machine-readable JSON";
|
|
15489
15493
|
readonly hidden: false;
|
|
@@ -18848,6 +18848,11 @@ export const COMMAND_CATALOG = [
|
|
|
18848
18848
|
"helpText": [],
|
|
18849
18849
|
"arguments": [],
|
|
18850
18850
|
"options": [
|
|
18851
|
+
{
|
|
18852
|
+
"flags": "--company <slug>",
|
|
18853
|
+
"description": "Sync one company",
|
|
18854
|
+
"hidden": false
|
|
18855
|
+
},
|
|
18851
18856
|
{
|
|
18852
18857
|
"flags": "--json",
|
|
18853
18858
|
"description": "Print machine-readable JSON",
|
|
@@ -260,7 +260,7 @@ export function formatDoctor(report) {
|
|
|
260
260
|
: report.providers.map((provider) => `${provider.provider} ${provider.state}`).join("\n");
|
|
261
261
|
const claudeAccounts = report.claudeAccounts.length === 0
|
|
262
262
|
? "none"
|
|
263
|
-
: formatTable(["ACCOUNT", "LAST ATTEMPT", "OUTCOME", "
|
|
263
|
+
: formatTable(["ACCOUNT", "LAST ATTEMPT", "OUTCOME", "SINCE REFRESH", "EXPIRES IN", "BACKOFF UNTIL"], report.claudeAccounts.map((account) => [
|
|
264
264
|
account.label,
|
|
265
265
|
account.lastRefreshAttempt ?? "—",
|
|
266
266
|
account.lastRefreshOutcome ?? "—",
|
|
@@ -19,6 +19,8 @@ import type { HostApp } from "../lib/daemon/supervisor.js";
|
|
|
19
19
|
import { type SyncModeVaultClient } from "./sync-mode.js";
|
|
20
20
|
import type { RunCommand } from "../lib/service-manager/launchd.js";
|
|
21
21
|
type DaemonRunnerModule = typeof import("../lib/daemon/run.js");
|
|
22
|
+
/** First hq-cloud release whose watch runner accepts pass-request files. */
|
|
23
|
+
export declare const PASS_REQUEST_MIN_HQ_CLOUD = "6.18.68";
|
|
22
24
|
/** Load every lazy daemon runtime module before releasing its package-use lease. */
|
|
23
25
|
export declare function loadDaemonRunner(release?: () => void, load?: () => Promise<DaemonRunnerModule>, preload?: () => Promise<void>, reportPreloadFailure?: (error: unknown) => void): Promise<DaemonRunnerModule["runDaemon"]>;
|
|
24
26
|
export interface DaemonCommandDeps {
|
package/dist/commands/daemon.js
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
import * as fs from "node:fs";
|
|
17
17
|
import * as os from "node:os";
|
|
18
18
|
import * as path from "node:path";
|
|
19
|
+
import { randomBytes } from "node:crypto";
|
|
19
20
|
import { spawn, spawnSync } from "node:child_process";
|
|
20
21
|
import { once } from "node:events";
|
|
21
22
|
import { compare as semverCompare, major as semverMajor, valid as validSemver } from "semver";
|
|
@@ -38,6 +39,7 @@ import { acknowledgeConflictNotice, conflictNoticeStateDir } from "../lib/daemon
|
|
|
38
39
|
import { isSyncRunnerCommandLine, readWatchRunnerOwnerStatus, syncRunnerPathFromCommandLine, watchRunnerOwnerStatusPath } from "../lib/daemon/sync-runner.js";
|
|
39
40
|
import { stagedSyncRunnerVersion, syncRunnerBundleStageRoot } from "../lib/daemon/sync-runner-bundle.js";
|
|
40
41
|
import { getSyncModeForCompany, setSyncMode } from "./sync-mode.js";
|
|
42
|
+
import { assertSafeCompanySlug } from "../utils/people.js";
|
|
41
43
|
import { anywhereRuntimeAllowed } from "../lib/anywhere-person-setting.js";
|
|
42
44
|
import { hqdRequest, hqdSocketPath, HqdClientError } from "../lib/daemon/protocol.js";
|
|
43
45
|
import { parseStdinMachineCredentials } from "../lib/daemon/machine-login.js";
|
|
@@ -45,6 +47,8 @@ import { resolveDefaultHqRoot } from "../utils/cognito-session.js";
|
|
|
45
47
|
import { releaseCurrentPackageUseLease, releasePackageUseLeaseAfterResidentPreload } from "../utils/package-use-lease.js";
|
|
46
48
|
import { preloadResidentPackageModules, reportResidentPackagePreloadFailure, runResidentCommandWithPackageLease, } from "../utils/resident-package-preload.js";
|
|
47
49
|
import { followLogFile } from "../lib/log-follow.js";
|
|
50
|
+
/** First hq-cloud release whose watch runner accepts pass-request files. */
|
|
51
|
+
export const PASS_REQUEST_MIN_HQ_CLOUD = "6.18.68";
|
|
48
52
|
/** Load every lazy daemon runtime module before releasing its package-use lease. */
|
|
49
53
|
export async function loadDaemonRunner(release = releaseCurrentPackageUseLease, load = () => import("../lib/daemon/run.js"), preload = () => preloadResidentPackageModules("daemon"), reportPreloadFailure = reportResidentPackagePreloadFailure) {
|
|
50
54
|
const { runDaemon } = await load();
|
|
@@ -545,6 +549,11 @@ function daemonSyncStatus(d) {
|
|
|
545
549
|
syncOwner: effectiveSyncOwner(config, host.detected.type),
|
|
546
550
|
owner: ownerStatus?.owner ?? (syncUnit?.status === "running" ? "hq-daemon" : null),
|
|
547
551
|
runnerPid: ownerStatus?.pid ?? syncUnit?.pid ?? null,
|
|
552
|
+
passRequestDir: syncUnit?.status === "running"
|
|
553
|
+
&& syncUnit.pid !== undefined
|
|
554
|
+
&& (ownerStatus === null || ownerStatus.pid === syncUnit.pid)
|
|
555
|
+
? syncUnit.passRequestDir ?? null
|
|
556
|
+
: null,
|
|
548
557
|
hqCloudVersion,
|
|
549
558
|
hqCloudVersionSource,
|
|
550
559
|
runnerMatchesPin,
|
|
@@ -592,6 +601,53 @@ function queueDaemonSyncControl(d, request) {
|
|
|
592
601
|
handOver(d, host);
|
|
593
602
|
return runningDaemonPid(host.paths.lockPath, d.daemonAlive) !== null;
|
|
594
603
|
}
|
|
604
|
+
function writeSyncPassRequest(paths, company, source) {
|
|
605
|
+
ensurePrivateDir(paths.syncPassRequestsDir);
|
|
606
|
+
const request = {
|
|
607
|
+
version: 1,
|
|
608
|
+
company,
|
|
609
|
+
requestedAt: new Date().toISOString(),
|
|
610
|
+
source,
|
|
611
|
+
};
|
|
612
|
+
const encoded = JSON.stringify(request);
|
|
613
|
+
for (let attempt = 0; attempt < 10; attempt += 1) {
|
|
614
|
+
const basename = `${Date.now()}-${randomBytes(4).toString("hex")}`;
|
|
615
|
+
const temporary = path.join(paths.syncPassRequestsDir, `${basename}.tmp`);
|
|
616
|
+
const destination = path.join(paths.syncPassRequestsDir, `${basename}.json`);
|
|
617
|
+
try {
|
|
618
|
+
fs.writeFileSync(temporary, encoded, { encoding: "utf8", mode: 0o600, flag: "wx" });
|
|
619
|
+
fs.renameSync(temporary, destination);
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
catch (error) {
|
|
623
|
+
try {
|
|
624
|
+
fs.unlinkSync(temporary);
|
|
625
|
+
}
|
|
626
|
+
catch (cleanupError) {
|
|
627
|
+
if (cleanupError.code !== "ENOENT")
|
|
628
|
+
throw cleanupError;
|
|
629
|
+
}
|
|
630
|
+
if (error.code === "EEXIST")
|
|
631
|
+
continue;
|
|
632
|
+
throw error;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
throw new DaemonCommandError("could not allocate a unique sync pass request filename");
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* The version alone does not prove the runner reads requests: a runner adopted
|
|
639
|
+
* across a CLI update keeps the env it was started with. The daemon records the
|
|
640
|
+
* request folder for a child it started with one.
|
|
641
|
+
*/
|
|
642
|
+
function supportsPassRequests(status, paths) {
|
|
643
|
+
const version = status.hqCloudVersion;
|
|
644
|
+
return status.running
|
|
645
|
+
&& status.passRequestDir !== null
|
|
646
|
+
&& path.resolve(status.passRequestDir) === path.resolve(paths.syncPassRequestsDir)
|
|
647
|
+
&& version !== null
|
|
648
|
+
&& validSemver(version) !== null
|
|
649
|
+
&& semverCompare(version, PASS_REQUEST_MIN_HQ_CLOUD) >= 0;
|
|
650
|
+
}
|
|
595
651
|
async function runDaemonSyncNow(d, opts) {
|
|
596
652
|
const host = currentHost(d);
|
|
597
653
|
const config = readDaemonConfig(host.paths.configPath);
|
|
@@ -600,8 +656,20 @@ async function runDaemonSyncNow(d, opts) {
|
|
|
600
656
|
if (!enabledServices(host.detected.type, config).includes("sync")) {
|
|
601
657
|
throw new DaemonCommandError("sync is disabled for this machine; run `hq daemon enable sync` first");
|
|
602
658
|
}
|
|
659
|
+
const company = opts.company ?? null;
|
|
660
|
+
if (company !== null)
|
|
661
|
+
assertSafeCompanySlug(company);
|
|
662
|
+
const status = daemonSyncStatus(d);
|
|
663
|
+
if (supportsPassRequests(status, host.paths)) {
|
|
664
|
+
const source = d.env.HQ_SYNC_PASS_REQUEST_SOURCE === "desktop" ? "desktop" : "cli";
|
|
665
|
+
writeSyncPassRequest(host.paths, company, source);
|
|
666
|
+
handOver(d, host);
|
|
667
|
+
const result = { accepted: true, action: "pass-request", company, daemonRunning: true };
|
|
668
|
+
d.out(opts.json ? JSON.stringify(result, null, 2) : "Asked the daemon to run a fresh sync pass.");
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
603
671
|
const running = queueDaemonSyncControl(d, { action: "restart", unit: "sync" });
|
|
604
|
-
const result = { accepted: true, action: "restart-sync-runner", daemonRunning: running };
|
|
672
|
+
const result = { accepted: true, action: "restart-sync-runner", company, daemonRunning: running };
|
|
605
673
|
d.out(opts.json ? JSON.stringify(result, null, 2) : running ? "Asked the daemon to start a fresh sync pass." : "Queued a fresh sync pass for the next daemon start.");
|
|
606
674
|
}
|
|
607
675
|
async function runDaemonSyncInstantSyncSet(d, enabled, opts) {
|
|
@@ -1336,6 +1404,7 @@ export function registerDaemonCommand(program, over = {}) {
|
|
|
1336
1404
|
sync
|
|
1337
1405
|
.command("now")
|
|
1338
1406
|
.description("Start a fresh sync pass immediately")
|
|
1407
|
+
.option("--company <slug>", "Sync one company")
|
|
1339
1408
|
.option("--json", "Print machine-readable JSON")
|
|
1340
1409
|
.action(wrap((d, opts) => runDaemonSyncNow(d, opts)));
|
|
1341
1410
|
const instantSync = sync.command("instant-sync").description("Control daemon Instant Sync");
|