@indigoai-us/hq-cli 5.345.147 → 5.345.149
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 +65 -0
- package/README.md +2 -0
- package/assets/bot-workers/setup/worker.yaml +11 -0
- package/dist/command-catalog.generated.d.ts +341 -11
- package/dist/command-catalog.generated.js +421 -11
- package/dist/command-help.js +2 -1
- package/dist/commands/access.d.ts +45 -2
- package/dist/commands/access.js +537 -223
- package/dist/commands/agent-inbox.js +2 -1
- package/dist/commands/billing-usage.d.ts +33 -2
- package/dist/commands/billing-usage.js +75 -4
- package/dist/commands/bot.js +2 -2
- package/dist/commands/channels.d.ts +27 -0
- package/dist/commands/channels.js +108 -4
- package/dist/commands/cloud.js +31 -1
- package/dist/commands/core-refresh-vault-access-native.d.ts +33 -0
- package/dist/commands/core-refresh-vault-access-native.js +356 -28
- package/dist/commands/core-work-mesh-bind-trusted-native.d.ts +9 -0
- package/dist/commands/core-work-mesh-bind-trusted-native.js +26 -19
- package/dist/commands/core.js +9 -0
- package/dist/commands/daemon.d.ts +7 -0
- package/dist/commands/daemon.js +37 -3
- package/dist/commands/dm.js +18 -3
- package/dist/commands/doctor.d.ts +11 -1
- package/dist/commands/doctor.js +56 -1
- package/dist/commands/install-global.d.ts +8 -8
- package/dist/commands/install-global.js +139 -61
- package/dist/commands/lanes-shared.d.ts +3 -1
- package/dist/commands/lanes.d.ts +4 -4
- package/dist/commands/lanes.js +380 -25
- package/dist/commands/library-add.d.ts +31 -1
- package/dist/commands/library-add.js +69 -2
- package/dist/commands/library.js +37 -3
- package/dist/commands/pack-install.d.ts +2 -2
- package/dist/commands/pack-install.js +133 -35
- package/dist/commands/reindex.js +19 -0
- package/dist/commands/sync-manifest.d.ts +2 -9
- package/dist/commands/sync-manifest.js +4 -15
- package/dist/hook-engine-path.d.ts +4 -0
- package/dist/hook-engine-path.js +43 -0
- package/dist/hq-cloud-sync-runner/bin/sync-runner.js +2783 -2354
- package/dist/hq-cloud-sync-runner/package.json +1 -1
- package/dist/index.d.ts +10 -0
- package/dist/index.js +76 -5
- package/dist/lazy-commands.js +2 -0
- package/dist/lib/agent-kit/inbox-redact.d.ts +44 -0
- package/dist/lib/agent-kit/inbox-redact.js +361 -0
- package/dist/lib/agent-kit/inbox-state.js +11 -2
- package/dist/lib/agent-kit/mcp/tools.js +2 -1
- package/dist/lib/agent-kit/run/inbox.js +36 -7
- package/dist/lib/anywhere-runtime-flag-key.d.ts +3 -0
- package/dist/lib/anywhere-runtime-flag-key.js +9 -0
- package/dist/lib/billing/usage-cost.d.ts +31 -1
- package/dist/lib/billing/usage-cost.js +46 -0
- package/dist/lib/company-skill-wrapper.js +6 -4
- package/dist/lib/daemon/install.d.ts +47 -1
- package/dist/lib/daemon/install.js +181 -15
- package/dist/lib/daemon/lock.d.ts +32 -0
- package/dist/lib/daemon/lock.js +55 -2
- package/dist/lib/db/local.d.ts +2 -2
- package/dist/lib/db/local.js +2 -2
- package/dist/lib/db/sql.js +2 -2
- package/dist/lib/db/sqlite-binding.d.ts +22 -0
- package/dist/lib/db/sqlite-binding.js +168 -0
- package/dist/lib/doctor/__testing__/run-doctor.js +4 -0
- package/dist/lib/doctor/checks/anywhere.d.ts +15 -0
- package/dist/lib/doctor/checks/anywhere.js +77 -11
- package/dist/lib/doctor/checks/cli-path.d.ts +12 -0
- package/dist/lib/doctor/checks/cli-path.js +46 -0
- package/dist/lib/doctor/checks/hook-engine.d.ts +4 -0
- package/dist/lib/doctor/checks/hook-engine.js +17 -0
- package/dist/lib/doctor/checks/sync-health.d.ts +7 -0
- package/dist/lib/doctor/checks/sync-health.js +38 -2
- package/dist/lib/doctor/checks/temp-homes.d.ts +19 -0
- package/dist/lib/doctor/checks/temp-homes.js +54 -0
- package/dist/lib/doctor/fix/apply.d.ts +90 -1
- package/dist/lib/doctor/fix/apply.js +176 -10
- package/dist/lib/doctor/fix/json-output.d.ts +4 -1
- package/dist/lib/doctor/fix/json-output.js +6 -0
- package/dist/lib/doctor/fix/remediation.d.ts +7 -1
- package/dist/lib/doctor/fix/remediation.js +23 -1
- package/dist/lib/doctor/registry.js +10 -0
- package/dist/lib/doctor/temp-home-sweeper.d.ts +149 -0
- package/dist/lib/doctor/temp-home-sweeper.js +418 -0
- package/dist/lib/flag-registry-worker-source.generated.d.ts +1 -1
- package/dist/lib/flag-registry-worker-source.generated.js +1 -1
- package/dist/lib/flag-registry.js +9 -1
- package/dist/lib/flag-snapshot-file.js +8 -3
- package/dist/lib/hook-engine/reconcile.d.ts +9 -0
- package/dist/lib/hook-engine/reconcile.js +134 -0
- package/dist/lib/hook-engine/registration.d.ts +17 -0
- package/dist/lib/hook-engine/registration.js +50 -0
- package/dist/lib/hook-engine/resolve.d.ts +31 -0
- package/dist/lib/hook-engine/resolve.js +134 -0
- package/dist/lib/install/claude-targets.d.ts +2 -1
- package/dist/lib/install/claude-targets.js +8 -3
- package/dist/lib/install/codex-targets.d.ts +3 -0
- package/dist/lib/install/codex-targets.js +15 -4
- package/dist/lib/install/hook-target.d.ts +9 -0
- package/dist/lib/install/hook-target.js +39 -0
- package/dist/lib/lanes/admission-host.d.ts +1 -0
- package/dist/lib/lanes/admission-host.js +8 -1
- package/dist/lib/lanes/admission.d.ts +4 -0
- package/dist/lib/lanes/admission.js +6 -2
- package/dist/lib/lanes/envelope-repair-dispatch.d.ts +5 -0
- package/dist/lib/lanes/envelope-repair-dispatch.js +12 -1
- package/dist/lib/lanes/index.d.ts +2 -2
- package/dist/lib/lanes/index.js +1 -1
- package/dist/lib/lanes/keep-alive.js +4 -1
- package/dist/lib/lanes/local-host-identity.d.ts +40 -0
- package/dist/lib/lanes/local-host-identity.js +314 -0
- package/dist/lib/lanes/reaper.d.ts +1 -1
- package/dist/lib/lanes/reaper.js +7 -0
- package/dist/lib/lanes/reconcile.d.ts +4 -0
- package/dist/lib/lanes/reconcile.js +16 -8
- package/dist/lib/lanes/spawn.d.ts +20 -0
- package/dist/lib/lanes/spawn.js +48 -2
- package/dist/lib/lanes/status-fields.js +6 -1
- package/dist/lib/lanes/store.js +8 -1
- package/dist/lib/lanes/throttle.d.ts +49 -6
- package/dist/lib/lanes/throttle.js +154 -17
- package/dist/lib/lanes/types.d.ts +22 -1
- package/dist/lib/lanes/types.js +27 -3
- package/dist/lib/lanes/watcher.js +27 -2
- package/dist/lib/lanes/worker-continuity.d.ts +2 -0
- package/dist/lib/lanes/worker-continuity.js +18 -11
- package/dist/lib/manifest-disabled.d.ts +13 -0
- package/dist/lib/manifest-disabled.js +19 -0
- package/dist/lib/service-manager/launchd.d.ts +1 -0
- package/dist/lib/service-manager/launchd.js +1 -1
- package/dist/lib/windows/console-encoding.d.ts +24 -0
- package/dist/lib/windows/console-encoding.js +83 -0
- package/dist/lib/windows/install-followup.d.ts +35 -0
- package/dist/lib/windows/install-followup.js +142 -0
- package/dist/lib/windows/user-path.d.ts +125 -0
- package/dist/lib/windows/user-path.js +275 -0
- package/dist/lib/work-context/auto-bind.d.ts +1 -1
- package/dist/lib/work-context/auto-bind.js +22 -3
- package/dist/lib/work-context/lane-bind.d.ts +31 -0
- package/dist/lib/work-context/lane-bind.js +101 -0
- package/dist/lib/work-context/organize.js +1 -0
- package/dist/lib/work-context/prd-sync.js +7 -0
- package/dist/lib/work-context/project.d.ts +36 -10
- package/dist/lib/work-context/project.js +130 -41
- package/dist/lib/work-context/reconcile.d.ts +6 -0
- package/dist/lib/work-context/reconcile.js +25 -5
- package/dist/lib/work-context/state.d.ts +6 -0
- package/dist/lib/work-context/state.js +29 -1
- package/dist/main.js +2 -1
- package/dist/unknown-command.d.ts +18 -0
- package/dist/unknown-command.js +88 -0
- package/dist/utils/cognito-session.d.ts +8 -0
- package/dist/utils/cognito-session.js +14 -2
- package/dist/utils/copy-tree.d.ts +61 -0
- package/dist/utils/copy-tree.js +249 -0
- package/dist/utils/environmental-error.js +7 -3
- package/dist/utils/hook-trust.d.ts +9 -0
- package/dist/utils/hook-trust.js +30 -2
- package/dist/utils/npm-invocation.d.ts +27 -0
- package/dist/utils/npm-invocation.js +54 -0
- package/dist/utils/qmd-native-binding-error.js +3 -1
- package/dist/utils/resolve-executable.d.ts +2 -0
- package/dist/utils/resolve-executable.js +6 -2
- package/dist/utils/safe-link.d.ts +50 -0
- package/dist/utils/safe-link.js +300 -0
- package/package.json +17 -5
- package/scripts/hook-engine-manifest.mjs +22 -0
- package/scripts/install-hook-engine.mjs +10 -0
package/dist/lib/daemon/lock.js
CHANGED
|
@@ -53,6 +53,11 @@ export function processCommandLine(pid, deps = defaultCommandLineDeps()) {
|
|
|
53
53
|
* The pid is alive and is an `hq daemon run`. After a reboot the pid in an old
|
|
54
54
|
* lock file often belongs to an unrelated program; that must not keep the
|
|
55
55
|
* daemon from starting.
|
|
56
|
+
*
|
|
57
|
+
* An unreadable command line counts as the daemon here, so a second daemon is
|
|
58
|
+
* never started next to one HQ cannot inspect. Never use this to decide a
|
|
59
|
+
* kill: use `verifyDaemonForKill`, which treats an unreadable command line as
|
|
60
|
+
* "not the daemon".
|
|
56
61
|
*/
|
|
57
62
|
export function isDaemonProcess(pid, deps = { alive: (p) => isProcessAlive(p), commandLine: (p) => processCommandLine(p) }) {
|
|
58
63
|
if (!deps.alive(pid))
|
|
@@ -60,6 +65,50 @@ export function isDaemonProcess(pid, deps = { alive: (p) => isProcessAlive(p), c
|
|
|
60
65
|
const cmd = deps.commandLine(pid);
|
|
61
66
|
return cmd === null || /\bdaemon\s+run\b/.test(cmd);
|
|
62
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* PowerShell that prints a Windows process's creation time (UTC, ISO 8601) on
|
|
70
|
+
* the first line and its command line on the rest, or nothing when no process
|
|
71
|
+
* has that id. It uses only a cmdlet and DateTime methods, so it also runs in
|
|
72
|
+
* Constrained Language Mode. `-ErrorAction Stop` makes a WMI failure exit
|
|
73
|
+
* non-zero, so it reads as "could not read", never as "no such process".
|
|
74
|
+
*/
|
|
75
|
+
export function windowsProcessIdentityScript(pid) {
|
|
76
|
+
return (`$p = Get-CimInstance Win32_Process -Filter "ProcessId=${Math.trunc(pid)}" -ErrorAction Stop; ` +
|
|
77
|
+
"if ($p) { $p.CreationDate.ToUniversalTime().ToString('o'); [string]$p.CommandLine }");
|
|
78
|
+
}
|
|
79
|
+
/** Seconds a daemon's creation time may follow its lock's ts (whole-second rounding and clock skew). */
|
|
80
|
+
const LOCK_TS_SLACK_SECONDS = 2;
|
|
81
|
+
/**
|
|
82
|
+
* Decide whether the Windows process in `owner` may be killed as the HQ
|
|
83
|
+
* daemon. Run this immediately before the kill. It is the daemon only when the
|
|
84
|
+
* process exists, its command line is readable and is `hq daemon run`, and it
|
|
85
|
+
* was created no later than the lock was written. A process created after the
|
|
86
|
+
* lock took over a pid the old daemon left behind. Anything that cannot be
|
|
87
|
+
* read is "unverified", and the caller must not kill.
|
|
88
|
+
*/
|
|
89
|
+
export function verifyDaemonForKill(owner, run) {
|
|
90
|
+
const encoded = Buffer.from(windowsProcessIdentityScript(owner.pid), "utf16le").toString("base64");
|
|
91
|
+
const r = run("powershell.exe", ["-NoProfile", "-NonInteractive", "-EncodedCommand", encoded]);
|
|
92
|
+
if (r.status !== 0)
|
|
93
|
+
return { verdict: "unverified", reason: "HQ could not read the process" };
|
|
94
|
+
const lines = r.stdout.split(/\r?\n/).map((l) => l.trim());
|
|
95
|
+
while (lines.length > 0 && lines[lines.length - 1] === "")
|
|
96
|
+
lines.pop();
|
|
97
|
+
if (lines.length === 0)
|
|
98
|
+
return { verdict: "gone" };
|
|
99
|
+
const commandLine = lines.slice(1).join(" ").trim();
|
|
100
|
+
if (!commandLine)
|
|
101
|
+
return { verdict: "unverified", reason: "HQ could not read its command line" };
|
|
102
|
+
if (!/\bdaemon\s+run\b/.test(commandLine))
|
|
103
|
+
return { verdict: "unverified", reason: "it is not hq daemon run" };
|
|
104
|
+
const created = Date.parse(lines[0]);
|
|
105
|
+
if (!Number.isFinite(created))
|
|
106
|
+
return { verdict: "unverified", reason: "HQ could not read its start time" };
|
|
107
|
+
if (Math.floor(created / 1000) > owner.ts + LOCK_TS_SLACK_SECONDS) {
|
|
108
|
+
return { verdict: "unverified", reason: "it started after the daemon lock was written, so it is a different process" };
|
|
109
|
+
}
|
|
110
|
+
return { verdict: "daemon" };
|
|
111
|
+
}
|
|
63
112
|
function claimTakeover(claim, pid, now) {
|
|
64
113
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
65
114
|
try {
|
|
@@ -130,9 +179,13 @@ export function releaseDaemonLock(lockPath, pid = process.pid) {
|
|
|
130
179
|
if (owner?.pid === pid)
|
|
131
180
|
fs.rmSync(lockPath, { force: true });
|
|
132
181
|
}
|
|
182
|
+
/** The live supervisor's lock record (pid and start time), or null when none is running. */
|
|
183
|
+
export function liveDaemonLock(lockPath, alive = isDaemonProcess) {
|
|
184
|
+
const owner = readPidLock(lockPath);
|
|
185
|
+
return owner && alive(owner.pid) ? owner : null;
|
|
186
|
+
}
|
|
133
187
|
/** The live supervisor's pid, or null when none is running. */
|
|
134
188
|
export function runningDaemonPid(lockPath, alive = isDaemonProcess) {
|
|
135
|
-
|
|
136
|
-
return owner && alive(owner.pid) ? owner.pid : null;
|
|
189
|
+
return liveDaemonLock(lockPath, alive)?.pid ?? null;
|
|
137
190
|
}
|
|
138
191
|
//# sourceMappingURL=lock.js.map
|
package/dist/lib/db/local.d.ts
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
* File lives at ~/.hq/db/{company}/vault.db with WAL journal mode.
|
|
5
5
|
* Never returns or logs remote connection strings.
|
|
6
6
|
*/
|
|
7
|
-
import
|
|
7
|
+
import type BetterSqlite3 from "better-sqlite3";
|
|
8
8
|
import { type LocalDbPathEnv } from "./paths.js";
|
|
9
|
-
export type LocalDb =
|
|
9
|
+
export type LocalDb = BetterSqlite3.Database;
|
|
10
10
|
export interface OpenLocalDbOptions extends LocalDbPathEnv {
|
|
11
11
|
/** When true, do not create the file if missing (throws). Default false. */
|
|
12
12
|
readonly?: boolean;
|
package/dist/lib/db/local.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Never returns or logs remote connection strings.
|
|
6
6
|
*/
|
|
7
7
|
import fs from "node:fs";
|
|
8
|
-
import
|
|
8
|
+
import { loadBetterSqlite3 } from "./sqlite-binding.js";
|
|
9
9
|
import { ensureLocalDbDir, resolveLocalDbPath, } from "./paths.js";
|
|
10
10
|
const MIGRATIONS_TABLE = "hq_schema_migrations";
|
|
11
11
|
/**
|
|
@@ -33,7 +33,7 @@ export function openLocalDb(companySlug, opts) {
|
|
|
33
33
|
}
|
|
34
34
|
ensureLocalDbDir(companySlug, opts);
|
|
35
35
|
}
|
|
36
|
-
const db = new
|
|
36
|
+
const db = new (loadBetterSqlite3())(dbPath);
|
|
37
37
|
// WAL for concurrent readers; safe for agent/CLI workloads.
|
|
38
38
|
db.pragma("journal_mode = WAL");
|
|
39
39
|
db.pragma("foreign_keys = ON");
|
package/dist/lib/db/sql.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* resolved company slug from HQ session/flags. Path overrides that open
|
|
6
6
|
* another company's DB are denied unless an explicit dangerous flag is set.
|
|
7
7
|
*/
|
|
8
|
-
import
|
|
8
|
+
import { loadBetterSqlite3 } from "./sqlite-binding.js";
|
|
9
9
|
import { openLocalDb } from "./local.js";
|
|
10
10
|
import { normalizeCompanySlugForLocalDb, resolveLocalDbPath, } from "./paths.js";
|
|
11
11
|
const WRITE_PATTERN = /^\s*(INSERT|UPDATE|DELETE|REPLACE|CREATE|DROP|ALTER|TRUNCATE|REINDEX|VACUUM|BEGIN|COMMIT|ROLLBACK)\b/i;
|
|
@@ -49,7 +49,7 @@ export function resolveSqlDbPath(opts) {
|
|
|
49
49
|
return opts.dbPathOverride;
|
|
50
50
|
}
|
|
51
51
|
function openByPath(dbPath) {
|
|
52
|
-
const db = new
|
|
52
|
+
const db = new (loadBetterSqlite3())(dbPath);
|
|
53
53
|
db.pragma("journal_mode = WAL");
|
|
54
54
|
db.pragma("foreign_keys = ON");
|
|
55
55
|
return db;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type BetterSqlite3 from "better-sqlite3";
|
|
2
|
+
import { type SpawnPlan } from "../../utils/windows-spawn.js";
|
|
3
|
+
export type BetterSqlite3Constructor = typeof BetterSqlite3;
|
|
4
|
+
export declare class SqliteBindingError extends Error {
|
|
5
|
+
constructor(cause?: unknown);
|
|
6
|
+
}
|
|
7
|
+
export interface LoadBetterSqlite3Options {
|
|
8
|
+
importDatabase?: () => BetterSqlite3Constructor;
|
|
9
|
+
/** Return true when the native binary is ready to load again. */
|
|
10
|
+
repair?: () => boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare function resetSqliteBindingForTests(): void;
|
|
13
|
+
export declare function clearBetterSqlite3RequireCache(): void;
|
|
14
|
+
/**
|
|
15
|
+
* A `.js` bin runs under this Node. A `.cmd` shim goes through the escaped
|
|
16
|
+
* cmd.exe plan, so a profile path with spaces or `&` stays one argument.
|
|
17
|
+
*/
|
|
18
|
+
export declare function planRepairToolSpawn(cmd: string, args: readonly string[], env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform): SpawnPlan;
|
|
19
|
+
/** Download or build better-sqlite3's native binary inside its own package directory. */
|
|
20
|
+
export declare function repairBetterSqlite3(): boolean;
|
|
21
|
+
export declare function loadBetterSqlite3(options?: LoadBetterSqlite3Options): BetterSqlite3Constructor;
|
|
22
|
+
//# sourceMappingURL=sqlite-binding.d.ts.map
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Load better-sqlite3 without requiring the user to approve npm scripts.
|
|
3
|
+
* npm 11 skips dependency install scripts, so the prebuilt binary is often
|
|
4
|
+
* missing after `npm install -g`. Try a one-shot prebuild download from the
|
|
5
|
+
* installed package, then surface the exact allow-scripts command.
|
|
6
|
+
*/
|
|
7
|
+
import { spawnSync } from "node:child_process";
|
|
8
|
+
import fs from "node:fs";
|
|
9
|
+
import { createRequire } from "node:module";
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
import { planResolvedCommandSpawn } from "../../utils/windows-spawn.js";
|
|
12
|
+
const require = createRequire(import.meta.url);
|
|
13
|
+
export class SqliteBindingError extends Error {
|
|
14
|
+
constructor(cause) {
|
|
15
|
+
super("better-sqlite3 did not load. npm 11.16 and later skip its install script unless scripts are allowed, so the prebuilt binary was not downloaded. " +
|
|
16
|
+
"HQ tried to download the prebuild and it still failed. " +
|
|
17
|
+
"Fix: npm install -g @indigoai-us/hq-cli --allow-scripts=better-sqlite3");
|
|
18
|
+
this.name = "SqliteBindingError";
|
|
19
|
+
if (cause !== undefined)
|
|
20
|
+
this.cause = cause;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
let cached;
|
|
24
|
+
let failed;
|
|
25
|
+
let defaultRepairAttempted = false;
|
|
26
|
+
export function resetSqliteBindingForTests() {
|
|
27
|
+
cached = undefined;
|
|
28
|
+
failed = undefined;
|
|
29
|
+
defaultRepairAttempted = false;
|
|
30
|
+
}
|
|
31
|
+
function isNativeBindingError(error) {
|
|
32
|
+
const record = error;
|
|
33
|
+
const text = error instanceof Error ? `${record?.code ?? ""} ${error.message}` : String(error);
|
|
34
|
+
return /better_sqlite3\.node|could not locate the bindings file|NODE_MODULE_VERSION|was compiled against a different node|not a valid win32 application|dlopen|ERR_DLOPEN_FAILED|MODULE_NOT_FOUND/i.test(text);
|
|
35
|
+
}
|
|
36
|
+
export function clearBetterSqlite3RequireCache() {
|
|
37
|
+
for (const id of Object.keys(require.cache)) {
|
|
38
|
+
if (id.includes(`${path.sep}better-sqlite3${path.sep}`))
|
|
39
|
+
delete require.cache[id];
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function defaultImport() {
|
|
43
|
+
return require("better-sqlite3");
|
|
44
|
+
}
|
|
45
|
+
function resolveTool(dir, tool) {
|
|
46
|
+
const names = process.platform === "win32" ? [`${tool}.cmd`, `${tool}.exe`, tool] : [tool];
|
|
47
|
+
const bins = [path.join(path.dirname(dir), ".bin"), path.join(dir, "node_modules", ".bin")];
|
|
48
|
+
for (const bin of bins) {
|
|
49
|
+
for (const name of names) {
|
|
50
|
+
const candidate = path.join(bin, name);
|
|
51
|
+
if (fs.existsSync(candidate))
|
|
52
|
+
return candidate;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
const localRequire = createRequire(path.join(dir, "package.json"));
|
|
57
|
+
const pkgJson = localRequire.resolve(`${tool}/package.json`);
|
|
58
|
+
const pkg = JSON.parse(fs.readFileSync(pkgJson, "utf8"));
|
|
59
|
+
const rel = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.[tool];
|
|
60
|
+
if (typeof rel === "string")
|
|
61
|
+
return path.join(path.dirname(pkgJson), rel);
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
// The tool is not installed next to better-sqlite3.
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* A `.js` bin runs under this Node. A `.cmd` shim goes through the escaped
|
|
70
|
+
* cmd.exe plan, so a profile path with spaces or `&` stays one argument.
|
|
71
|
+
*/
|
|
72
|
+
export function planRepairToolSpawn(cmd, args, env = process.env, platform = process.platform) {
|
|
73
|
+
if (cmd.endsWith(".js"))
|
|
74
|
+
return { cmd: process.execPath, args: [cmd, ...args], shell: false };
|
|
75
|
+
return planResolvedCommandSpawn(cmd, args, env, platform);
|
|
76
|
+
}
|
|
77
|
+
function runTool(cmd, args, cwd) {
|
|
78
|
+
const plan = planRepairToolSpawn(cmd, args);
|
|
79
|
+
spawnSync(plan.cmd, plan.args, {
|
|
80
|
+
cwd,
|
|
81
|
+
timeout: 60_000,
|
|
82
|
+
windowsHide: true,
|
|
83
|
+
stdio: "ignore",
|
|
84
|
+
shell: plan.shell,
|
|
85
|
+
...(plan.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {}),
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
/** Download or build better-sqlite3's native binary inside its own package directory. */
|
|
89
|
+
export function repairBetterSqlite3() {
|
|
90
|
+
let dir;
|
|
91
|
+
try {
|
|
92
|
+
dir = path.dirname(require.resolve("better-sqlite3/package.json"));
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
const artifact = path.join(dir, "build", "Release", "better_sqlite3.node");
|
|
98
|
+
if (fs.existsSync(artifact))
|
|
99
|
+
return true;
|
|
100
|
+
const prebuild = resolveTool(dir, "prebuild-install");
|
|
101
|
+
if (prebuild)
|
|
102
|
+
runTool(prebuild, [], dir);
|
|
103
|
+
if (fs.existsSync(artifact))
|
|
104
|
+
return true;
|
|
105
|
+
const nodeGyp = resolveTool(dir, "node-gyp");
|
|
106
|
+
if (nodeGyp)
|
|
107
|
+
runTool(nodeGyp, ["rebuild", "--release"], dir);
|
|
108
|
+
return fs.existsSync(artifact);
|
|
109
|
+
}
|
|
110
|
+
function attemptRepair(options) {
|
|
111
|
+
if (options.repair) {
|
|
112
|
+
try {
|
|
113
|
+
return options.repair();
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (defaultRepairAttempted)
|
|
120
|
+
return false;
|
|
121
|
+
defaultRepairAttempted = true;
|
|
122
|
+
try {
|
|
123
|
+
return repairBetterSqlite3();
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
export function loadBetterSqlite3(options = {}) {
|
|
130
|
+
const injected = options.importDatabase !== undefined;
|
|
131
|
+
if (!injected && cached)
|
|
132
|
+
return cached;
|
|
133
|
+
if (!injected && failed)
|
|
134
|
+
throw failed;
|
|
135
|
+
const importDatabase = options.importDatabase ?? defaultImport;
|
|
136
|
+
const load = () => {
|
|
137
|
+
const ctor = importDatabase();
|
|
138
|
+
if (!injected)
|
|
139
|
+
cached = ctor;
|
|
140
|
+
return ctor;
|
|
141
|
+
};
|
|
142
|
+
try {
|
|
143
|
+
return load();
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
if (!isNativeBindingError(error))
|
|
147
|
+
throw error;
|
|
148
|
+
const repaired = attemptRepair(options);
|
|
149
|
+
if (!repaired) {
|
|
150
|
+
const wrapped = new SqliteBindingError(error);
|
|
151
|
+
if (!injected)
|
|
152
|
+
failed = wrapped;
|
|
153
|
+
throw wrapped;
|
|
154
|
+
}
|
|
155
|
+
if (!injected)
|
|
156
|
+
clearBetterSqlite3RequireCache();
|
|
157
|
+
try {
|
|
158
|
+
return load();
|
|
159
|
+
}
|
|
160
|
+
catch (retryError) {
|
|
161
|
+
const wrapped = new SqliteBindingError(retryError);
|
|
162
|
+
if (!injected)
|
|
163
|
+
failed = wrapped;
|
|
164
|
+
throw wrapped;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
//# sourceMappingURL=sqlite-binding.js.map
|
|
@@ -3,6 +3,7 @@ import * as os from "node:os";
|
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import { runDoctor as runDoctorCommand, } from "../../../commands/doctor.js";
|
|
5
5
|
import { createAnywhereFamily } from "../checks/anywhere.js";
|
|
6
|
+
import { createTempHomesFamily } from "../checks/temp-homes.js";
|
|
6
7
|
import { createDefaultRegistry } from "../registry.js";
|
|
7
8
|
/**
|
|
8
9
|
* Run doctor tests against synthetic state. The production registry normally
|
|
@@ -45,6 +46,9 @@ export async function runDoctorForTest(options = {}) {
|
|
|
45
46
|
// Command tests exercise doctor rendering and exit behavior offline. The
|
|
46
47
|
// real person preference is covered in the anywhere-family tests.
|
|
47
48
|
registry.register(createAnywhereFamily({ personSettingReader: async () => false }));
|
|
49
|
+
// The temp-homes family walks the machine's real temp folders; scan
|
|
50
|
+
// none here. Its behaviour is covered with fake trees in its own tests.
|
|
51
|
+
registry.register(createTempHomesFamily({ roots: [] }));
|
|
48
52
|
}
|
|
49
53
|
return await runDoctorCommand({ ...options, registry, integrations: false });
|
|
50
54
|
}
|
|
@@ -17,6 +17,7 @@ export declare const REMEDIATION: {
|
|
|
17
17
|
readonly repairCodexSkills: "hq uninstall --global --runtime codex && hq install --global --runtime codex";
|
|
18
18
|
readonly daemonInstall: "hq daemon install";
|
|
19
19
|
readonly daemonRestart: "hq daemon restart --daemon";
|
|
20
|
+
readonly daemonStatus: "hq daemon status";
|
|
20
21
|
readonly link: "hq link <repo-dir> <company>";
|
|
21
22
|
};
|
|
22
23
|
interface DaemonServiceState {
|
|
@@ -45,10 +46,24 @@ export interface AnywhereDeps {
|
|
|
45
46
|
/** The hq-cli version installed on this machine. Default: this CLI's version. */
|
|
46
47
|
cliVersion?: string;
|
|
47
48
|
serviceState?: () => DaemonServiceState;
|
|
49
|
+
/** Is an hqd process alive? Default: the daemon lock names a live hqd pid. */
|
|
50
|
+
daemonRunning?: () => boolean;
|
|
51
|
+
/** Transport to report when health throws. Production uses the platform default. */
|
|
52
|
+
unreachableTransport?: "windows_named_pipe" | "unix_socket";
|
|
48
53
|
now?: () => Date;
|
|
49
54
|
}
|
|
55
|
+
export interface PipeConnectFailure {
|
|
56
|
+
code?: string;
|
|
57
|
+
detail?: string;
|
|
58
|
+
}
|
|
50
59
|
export declare function createAnywhereFamily(deps?: AnywhereDeps): CheckFamily;
|
|
51
60
|
export declare const anywhereFamily: CheckFamily;
|
|
61
|
+
/**
|
|
62
|
+
* What to say when the Windows named pipe cannot be used. The old wording
|
|
63
|
+
* guessed an ACL refusal whenever any process looked like a daemon. This
|
|
64
|
+
* reports the connect error that was actually observed.
|
|
65
|
+
*/
|
|
66
|
+
export declare function windowsNamedPipeUnreachableMessage(socketPath: string, daemonRunning: boolean, failure?: PipeConnectFailure): string;
|
|
52
67
|
export declare function defaultServiceState(home: string, options?: DaemonServiceProbeOptions): DaemonServiceState;
|
|
53
68
|
export {};
|
|
54
69
|
//# sourceMappingURL=anywhere.d.ts.map
|
|
@@ -23,8 +23,10 @@ import { daemonPresenceLabel, heartbeatFile, readHeartbeat } from "../../daemon/
|
|
|
23
23
|
import { DAEMON_LAUNCHD_LABEL, DAEMON_SYSTEMD_UNIT, DAEMON_WINDOWS_TASK } from "../../daemon/install.js";
|
|
24
24
|
import { hostedDaemonRunning } from "../../daemon/handoff.js";
|
|
25
25
|
import { daemonPaths } from "../../daemon/paths.js";
|
|
26
|
+
import { runningDaemonPid } from "../../daemon/lock.js";
|
|
26
27
|
import { hqdSocketPath } from "../../daemon/protocol.js";
|
|
27
28
|
import { readRegistry, registryDir, registryFile } from "../../registry/registry.js";
|
|
29
|
+
import { skillCopySource } from "../../../utils/safe-link.js";
|
|
28
30
|
import { anywhereRuntimeEnabled } from "../../../commands/anywhere-runtime-flag.js";
|
|
29
31
|
import { readHqAnywherePersonSetting } from "../../anywhere-person-setting.js";
|
|
30
32
|
const ANYWHERE_FAMILY_ID = "anywhere";
|
|
@@ -35,6 +37,7 @@ export const REMEDIATION = {
|
|
|
35
37
|
repairCodexSkills: "hq uninstall --global --runtime codex && hq install --global --runtime codex",
|
|
36
38
|
daemonInstall: "hq daemon install",
|
|
37
39
|
daemonRestart: "hq daemon restart --daemon",
|
|
40
|
+
daemonStatus: "hq daemon status",
|
|
38
41
|
link: "hq link <repo-dir> <company>",
|
|
39
42
|
};
|
|
40
43
|
export function createAnywhereFamily(deps = {}) {
|
|
@@ -60,6 +63,7 @@ async function runAnywhereChecks(context, deps = {}) {
|
|
|
60
63
|
: result(personSetting ? "PASS" : "WARN", "person-setting", `HQ Anywhere person setting is ${personSetting ? "on" : "off"}`);
|
|
61
64
|
let health;
|
|
62
65
|
let healthUnsupported = false;
|
|
66
|
+
let connectFailure;
|
|
63
67
|
try {
|
|
64
68
|
if (deps.health) {
|
|
65
69
|
health = await deps.health(cwd);
|
|
@@ -104,12 +108,14 @@ async function runAnywhereChecks(context, deps = {}) {
|
|
|
104
108
|
};
|
|
105
109
|
healthUnsupported = true;
|
|
106
110
|
}
|
|
107
|
-
catch {
|
|
108
|
-
health = unreachableHealth(socketPath);
|
|
111
|
+
catch (inner) {
|
|
112
|
+
health = unreachableHealth(socketPath, deps.unreachableTransport);
|
|
113
|
+
connectFailure = connectFailureFrom(inner);
|
|
109
114
|
}
|
|
110
115
|
}
|
|
111
116
|
else {
|
|
112
|
-
health = unreachableHealth(socketPath);
|
|
117
|
+
health = unreachableHealth(socketPath, deps.unreachableTransport);
|
|
118
|
+
connectFailure = connectFailureFrom(error);
|
|
113
119
|
}
|
|
114
120
|
}
|
|
115
121
|
const claude = claudeTargets(home);
|
|
@@ -118,7 +124,12 @@ async function runAnywhereChecks(context, deps = {}) {
|
|
|
118
124
|
const serviceState = runtimeEnabled && installed
|
|
119
125
|
? (deps.serviceState ?? (() => defaultServiceState(home)))()
|
|
120
126
|
: undefined;
|
|
121
|
-
|
|
127
|
+
// A missing Windows pipe usually means hqd never started; only blame the
|
|
128
|
+
// pipe ACL when a daemon process is actually alive.
|
|
129
|
+
const daemonRunning = health.reachable
|
|
130
|
+
? true
|
|
131
|
+
: (deps.daemonRunning ?? (() => runningDaemonPid(daemonPaths(home, env).lockPath) !== null))();
|
|
132
|
+
const healthCheck = runtimeHealthCheck(health, healthUnsupported, serviceState, daemonRunning, connectFailure);
|
|
122
133
|
if (!runtimeEnabled)
|
|
123
134
|
return [healthCheck, settingCheck];
|
|
124
135
|
const remoteModes = [
|
|
@@ -169,17 +180,67 @@ async function runAnywhereChecks(context, deps = {}) {
|
|
|
169
180
|
out.push(checkSkillLinks("codex", codex.skillsDir, REMEDIATION.installCodex, REMEDIATION.repairCodexSkills));
|
|
170
181
|
return out;
|
|
171
182
|
}
|
|
172
|
-
function unreachableHealth(socketPath) {
|
|
183
|
+
function unreachableHealth(socketPath, transport) {
|
|
173
184
|
return {
|
|
174
185
|
reachable: false,
|
|
175
186
|
version: null,
|
|
176
187
|
uptimeMs: null,
|
|
177
188
|
flag: { enabled: null, lastSuccessfulReadAgeMs: null, heldThroughReadError: false },
|
|
178
189
|
resolution: { scope: "unknown", company: null, fallbackReason: null },
|
|
179
|
-
socket: {
|
|
190
|
+
socket: {
|
|
191
|
+
path: socketPath,
|
|
192
|
+
transport: transport ?? (process.platform === "win32" ? "windows_named_pipe" : "unix_socket"),
|
|
193
|
+
mode: null,
|
|
194
|
+
ownerMatchesUser: null,
|
|
195
|
+
userOnly: null,
|
|
196
|
+
},
|
|
180
197
|
};
|
|
181
198
|
}
|
|
182
|
-
function
|
|
199
|
+
function connectFailureFrom(error) {
|
|
200
|
+
if (error instanceof HqdClientError)
|
|
201
|
+
return { code: error.code, detail: error.message };
|
|
202
|
+
if (error instanceof Error) {
|
|
203
|
+
const code = error.code;
|
|
204
|
+
return { code: typeof code === "string" ? code : undefined, detail: error.message };
|
|
205
|
+
}
|
|
206
|
+
return { detail: String(error) };
|
|
207
|
+
}
|
|
208
|
+
function failureSuffix(failure) {
|
|
209
|
+
if (!failure)
|
|
210
|
+
return "";
|
|
211
|
+
const parts = [failure.code, failure.detail].filter((part) => Boolean(part && part.trim()));
|
|
212
|
+
return parts.length === 0 ? "" : ` (${parts.join(": ")})`;
|
|
213
|
+
}
|
|
214
|
+
function accessDenied(failure) {
|
|
215
|
+
const code = (failure?.code ?? "").toUpperCase();
|
|
216
|
+
if (code === "EACCES" || code === "EPERM")
|
|
217
|
+
return true;
|
|
218
|
+
return /access is denied|unsafe acl/i.test(failure?.detail ?? "");
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* What to say when the Windows named pipe cannot be used. The old wording
|
|
222
|
+
* guessed an ACL refusal whenever any process looked like a daemon. This
|
|
223
|
+
* reports the connect error that was actually observed.
|
|
224
|
+
*/
|
|
225
|
+
export function windowsNamedPipeUnreachableMessage(socketPath, daemonRunning, failure) {
|
|
226
|
+
const suffix = failureSuffix(failure);
|
|
227
|
+
if (!daemonRunning) {
|
|
228
|
+
return `hqd is not running, so its named pipe ${socketPath} does not exist${suffix}. Run ${REMEDIATION.daemonStatus} to see why it has not started.`;
|
|
229
|
+
}
|
|
230
|
+
if (accessDenied(failure)) {
|
|
231
|
+
return `hqd is running but access to named pipe ${socketPath} was denied${suffix}. The pipe ACL does not allow this user. Run ${REMEDIATION.daemonRestart} to recreate the pipe.`;
|
|
232
|
+
}
|
|
233
|
+
const code = (failure?.code ?? "").toUpperCase();
|
|
234
|
+
if (code === "ENOENT") {
|
|
235
|
+
return `hqd is running but named pipe ${socketPath} does not exist${suffix}. Run ${REMEDIATION.daemonRestart} to recreate the pipe.`;
|
|
236
|
+
}
|
|
237
|
+
if (code === "ETIMEDOUT") {
|
|
238
|
+
return `hqd is running but did not answer on named pipe ${socketPath}${suffix}. Run ${REMEDIATION.daemonStatus}, then ${REMEDIATION.daemonRestart}.`;
|
|
239
|
+
}
|
|
240
|
+
const observed = suffix || " (observed)";
|
|
241
|
+
return `hqd is not reachable on ${socketPath}${observed}. HQ did not see an access-denied error, so this is not a named-pipe ACL refusal. Run ${REMEDIATION.daemonStatus} to see why, then ${REMEDIATION.daemonRestart}.`;
|
|
242
|
+
}
|
|
243
|
+
function runtimeHealthCheck(health, healthUnsupported = false, serviceState, daemonRunning = true, failure) {
|
|
183
244
|
const { flag, resolution, socket } = health;
|
|
184
245
|
const age = flag.lastSuccessfulReadAgeMs === null
|
|
185
246
|
? "unknown"
|
|
@@ -195,8 +256,9 @@ function runtimeHealthCheck(health, healthUnsupported = false, serviceState) {
|
|
|
195
256
|
const socketSecurity = socket.transport === "windows_named_pipe"
|
|
196
257
|
? "Windows named pipe (mode and owner are not reported)"
|
|
197
258
|
: `mode ${socket.mode ?? "unknown"}; owner matches current user: ${socket.ownerMatchesUser === null ? "unknown" : socket.ownerMatchesUser ? "yes" : "no"}; user-only: ${socket.userOnly === null ? "unknown" : socket.userOnly ? "yes" : "no"}`;
|
|
259
|
+
const pipeWithoutDaemon = socket.transport === "windows_named_pipe" && !daemonRunning;
|
|
198
260
|
const unreachableMessage = socket.transport === "windows_named_pipe"
|
|
199
|
-
?
|
|
261
|
+
? windowsNamedPipeUnreachableMessage(socket.path, daemonRunning, failure)
|
|
200
262
|
: `hqd is not reachable on ${socket.path}; version, uptime, flag state, and cwd binding are unavailable`;
|
|
201
263
|
const socketNotUserOnly = health.reachable && socket.transport === "unix_socket" && (socket.userOnly !== true || socket.ownerMatchesUser !== true);
|
|
202
264
|
const warningNotes = [
|
|
@@ -207,7 +269,8 @@ function runtimeHealthCheck(health, healthUnsupported = false, serviceState) {
|
|
|
207
269
|
? `hqd reachable; version ${health.version ?? "unknown"}; uptime ${health.uptimeMs === null ? "unknown" : `${new Intl.NumberFormat("en-US").format(health.uptimeMs)} ms`}; runtime flag ${flagLabel}; last successful flag read ${age} ago; held through read error: ${flag.heldThroughReadError ? "yes" : "no"}; cwd binding ${binding}; socket ${socket.path}; ${socketSecurity}${warningNotes.length ? `; ${warningNotes.join("; ")}` : ""}`
|
|
208
270
|
: unreachableMessage;
|
|
209
271
|
const remediation = !health.reachable && serviceState
|
|
210
|
-
? serviceState.installed ? REMEDIATION.
|
|
272
|
+
? !serviceState.installed ? REMEDIATION.daemonInstall
|
|
273
|
+
: pipeWithoutDaemon ? REMEDIATION.daemonStatus : REMEDIATION.daemonRestart
|
|
211
274
|
: healthUnsupported || socketNotUserOnly ? REMEDIATION.daemonRestart : undefined;
|
|
212
275
|
return {
|
|
213
276
|
status: health.reachable && warningNotes.length === 0 ? "PASS" : "WARN",
|
|
@@ -336,12 +399,15 @@ function checkSkillLinks(runtime, dir, fix, repair = fix) {
|
|
|
336
399
|
return result("WARN", id, `${dir} does not exist`, fix, dir);
|
|
337
400
|
}
|
|
338
401
|
const links = names.filter((n) => fs.lstatSync(path.join(dir, n)).isSymbolicLink());
|
|
339
|
-
|
|
402
|
+
// Windows falls back to marked copies when it refuses both link types.
|
|
403
|
+
const copies = names.filter((n) => skillCopySource(path.join(dir, n)) !== null);
|
|
404
|
+
if (links.length === 0 && copies.length === 0)
|
|
340
405
|
return result("WARN", id, `no hq-* skill links in ${dir}`, fix, dir);
|
|
341
406
|
const broken = links.filter((n) => !fs.existsSync(path.join(dir, n)));
|
|
342
407
|
if (broken.length > 0)
|
|
343
408
|
return result("FAIL", id, `${broken.length} broken skill links in ${dir}: ${broken.slice(0, 5).join(", ")}`, repair, dir);
|
|
344
|
-
|
|
409
|
+
const copied = copies.length > 0 ? ` (${copies.length} more are copies that do not follow HQ skill updates)` : "";
|
|
410
|
+
return result("PASS", id, `${links.length} hq-* skill links in ${dir} resolve${copied}`, undefined, dir);
|
|
345
411
|
}
|
|
346
412
|
export function defaultServiceState(home, options = {}) {
|
|
347
413
|
const platform = options.platform ?? process.platform;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type ShimPathCheck } from "../../windows/user-path.js";
|
|
2
|
+
import type { CheckContext, CheckFamily, CheckResult } from "../types.js";
|
|
3
|
+
export declare const CLI_PATH_FAMILY_ID = "cli-path";
|
|
4
|
+
export declare const CLI_PATH_CHECK_ID = "cli-path.npm-shim-on-path";
|
|
5
|
+
/**
|
|
6
|
+
* Windows: is the folder holding the npm `hq.cmd` shim on the PATH a new
|
|
7
|
+
* terminal gets? This check only reads. The CLI adds the folder to HKCU Path
|
|
8
|
+
* on startup; the warning is the fallback when that add did not stick.
|
|
9
|
+
*/
|
|
10
|
+
export declare const cliPathFamily: CheckFamily;
|
|
11
|
+
export declare function checkCliPath(_context: CheckContext, check?: () => ShimPathCheck | null): CheckResult[];
|
|
12
|
+
//# sourceMappingURL=cli-path.d.ts.map
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { checkShimOnPath, pathFixCommand, shimPathWarning } from "../../windows/user-path.js";
|
|
2
|
+
export const CLI_PATH_FAMILY_ID = "cli-path";
|
|
3
|
+
export const CLI_PATH_CHECK_ID = "cli-path.npm-shim-on-path";
|
|
4
|
+
/**
|
|
5
|
+
* Windows: is the folder holding the npm `hq.cmd` shim on the PATH a new
|
|
6
|
+
* terminal gets? This check only reads. The CLI adds the folder to HKCU Path
|
|
7
|
+
* on startup; the warning is the fallback when that add did not stick.
|
|
8
|
+
*/
|
|
9
|
+
export const cliPathFamily = {
|
|
10
|
+
id: CLI_PATH_FAMILY_ID,
|
|
11
|
+
title: "hq command on PATH",
|
|
12
|
+
run: (context) => Promise.resolve(checkCliPath(context)),
|
|
13
|
+
};
|
|
14
|
+
export function checkCliPath(_context, check = () => checkShimOnPath()) {
|
|
15
|
+
const result = check();
|
|
16
|
+
if (!result) {
|
|
17
|
+
return [
|
|
18
|
+
{
|
|
19
|
+
status: "NA",
|
|
20
|
+
checkId: CLI_PATH_CHECK_ID,
|
|
21
|
+
message: "Not a Windows npm global install, so there is no hq.cmd shim folder to check.",
|
|
22
|
+
},
|
|
23
|
+
];
|
|
24
|
+
}
|
|
25
|
+
if (result.onPath) {
|
|
26
|
+
return [
|
|
27
|
+
{
|
|
28
|
+
status: "PASS",
|
|
29
|
+
checkId: CLI_PATH_CHECK_ID,
|
|
30
|
+
target: result.shimDir,
|
|
31
|
+
message: `The hq command folder ${result.shimDir} is on your Windows PATH.`,
|
|
32
|
+
},
|
|
33
|
+
];
|
|
34
|
+
}
|
|
35
|
+
return [
|
|
36
|
+
{
|
|
37
|
+
status: "WARN",
|
|
38
|
+
checkId: CLI_PATH_CHECK_ID,
|
|
39
|
+
target: result.shimDir,
|
|
40
|
+
message: shimPathWarning(result)[0],
|
|
41
|
+
details: { source: result.source },
|
|
42
|
+
remediation: `In PowerShell, then open a new terminal: ${pathFixCommand(result.shimDir)}`,
|
|
43
|
+
},
|
|
44
|
+
];
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=cli-path.js.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { launcherEnabled } from '../../hook-engine/registration.js';
|
|
2
|
+
import { findHookEngine, hookEngineRequired, pinnedHookEngineMatches, HOOK_ENGINE_REPAIR, HOOK_ENGINE_REPAIR_PIN } from '../../hook-engine/resolve.js';
|
|
3
|
+
export function checkHookEngine() {
|
|
4
|
+
const required = hookEngineRequired();
|
|
5
|
+
const fallback = required ? 'Native engine is required; repair before running hooks.' : 'Existing bash hooks remain active.';
|
|
6
|
+
try {
|
|
7
|
+
const engine = findHookEngine();
|
|
8
|
+
if (engine && pinnedHookEngineMatches(engine))
|
|
9
|
+
return { checkId: 'hooks.native-engine', status: 'PASS', target: engine.pinned, message: `Native hook engine ${engine.version} installed at its version-pinned path. Launcher flag: ${launcherEnabled() ? 'on (run hq reindex to reconcile settings)' : 'off (bash registration)'}.` };
|
|
10
|
+
return { checkId: 'hooks.native-engine', status: required ? 'FAIL' : 'WARN', message: `Native hook engine is missing or damaged. ${fallback}`, remediation: engine ? `Close HQ sessions, then run ${HOOK_ENGINE_REPAIR_PIN}` : HOOK_ENGINE_REPAIR };
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
return { checkId: 'hooks.native-engine', status: required ? 'FAIL' : 'WARN', message: `Native hook engine unavailable: ${error.message}. ${fallback}`, remediation: HOOK_ENGINE_REPAIR };
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export const hookEngineFamily = { id: 'hook-engine', title: 'Native hook engine', run: async () => [checkHookEngine()] };
|
|
17
|
+
//# sourceMappingURL=hook-engine.js.map
|
|
@@ -100,6 +100,13 @@ export interface SyncHealthDeps {
|
|
|
100
100
|
* unhealthy-sync signal, and `hq doctor --fix` has nothing local to refresh.
|
|
101
101
|
*/
|
|
102
102
|
localCompanyExists?: (slug: string) => boolean;
|
|
103
|
+
/**
|
|
104
|
+
* Whether HQ_SYNC_MANIFEST_DISABLED turns manifest uploads off on this
|
|
105
|
+
* machine. Defaults to reading the environment the way `hq sync manifest`
|
|
106
|
+
* does. When true a stale row names the switch and is not auto-fixable,
|
|
107
|
+
* since a re-upload would exit 0 without uploading.
|
|
108
|
+
*/
|
|
109
|
+
manifestUploadsDisabled?: () => boolean;
|
|
103
110
|
}
|
|
104
111
|
/**
|
|
105
112
|
* True when `slug` is a filesystem-safe company folder name. Rejects empty
|