@indigoai-us/hq-cli 5.345.170 → 5.345.172
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 +84 -0
- package/assets/scaffold/core/scripts/hq-delegate-send.sh +1 -1
- package/dist/command-catalog.generated.d.ts +206 -7
- package/dist/command-catalog.generated.js +252 -8
- package/dist/command-registration-plan.d.ts +7 -0
- package/dist/command-registration-plan.js +1 -0
- package/dist/commands/access.d.ts +32 -1
- package/dist/commands/access.js +112 -3
- package/dist/commands/agent-kit.d.ts +1 -1
- package/dist/commands/agent-kit.js +3 -1
- package/dist/commands/agents.js +2 -1
- package/dist/commands/bot.d.ts +3 -0
- package/dist/commands/bot.js +179 -18
- package/dist/commands/cloud.d.ts +58 -1
- package/dist/commands/cloud.js +228 -10
- package/dist/commands/core-company-index-scope-flag.d.ts +8 -1
- package/dist/commands/core-company-index-scope-flag.js +9 -2
- package/dist/commands/core-delegate-w1b.js +5 -2
- package/dist/commands/core-dm-bind-native.js +19 -4
- package/dist/commands/core-rebuild-index-fast.js +24 -6
- package/dist/commands/core.js +18 -6
- package/dist/commands/daemon.js +16 -1
- package/dist/commands/dm.d.ts +17 -3
- package/dist/commands/dm.js +121 -26
- package/dist/commands/files.d.ts +1 -0
- package/dist/commands/files.js +22 -4
- package/dist/commands/integrations-api.d.ts +4 -0
- package/dist/commands/integrations-api.js +18 -2
- package/dist/commands/integrations-connect.d.ts +10 -3
- package/dist/commands/integrations-connect.js +70 -6
- package/dist/commands/integrations-core.d.ts +23 -0
- package/dist/commands/integrations-core.js +100 -0
- package/dist/commands/integrations-manage.js +8 -1
- package/dist/commands/integrations.js +49 -5
- package/dist/commands/lanes-loop.d.ts +20 -0
- package/dist/commands/lanes-loop.js +69 -11
- package/dist/commands/lanes.js +9 -1
- package/dist/commands/rescue.d.ts +9 -0
- package/dist/commands/rescue.js +31 -0
- package/dist/commands/run.js +115 -40
- package/dist/commands/scan.d.ts +5 -1
- package/dist/commands/scan.js +16 -4
- package/dist/commands/secrets.js +50 -9
- package/dist/commands/sync-core.d.ts +22 -0
- package/dist/commands/sync-core.js +124 -0
- package/dist/hq-cloud-sync-runner/bin/sync-runner.js +2447 -2135
- package/dist/hq-cloud-sync-runner/package.json +1 -1
- package/dist/lib/agent-kit/inbox-state.d.ts +17 -0
- package/dist/lib/agent-kit/inbox-state.js +41 -20
- package/dist/lib/agent-kit/kit-config.d.ts +2 -0
- package/dist/lib/agent-kit/kit-config.js +6 -0
- package/dist/lib/agent-kit/mcp/tools.js +12 -5
- package/dist/lib/agent-kit/run/heartbeat.d.ts +6 -3
- package/dist/lib/agent-kit/run/heartbeat.js +6 -3
- package/dist/lib/agent-kit/run/inbox.d.ts +8 -3
- package/dist/lib/agent-kit/run/inbox.js +42 -10
- package/dist/lib/agent-kit/run/request-timeout.d.ts +26 -0
- package/dist/lib/agent-kit/run/request-timeout.js +51 -0
- package/dist/lib/bot/api.d.ts +1 -1
- package/dist/lib/bot/config.d.ts +10 -0
- package/dist/lib/bot/config.js +17 -1
- package/dist/lib/bot/fleet-local.d.ts +142 -0
- package/dist/lib/bot/fleet-local.js +689 -0
- package/dist/lib/bot/fleet-python.d.ts +47 -0
- package/dist/lib/bot/fleet-python.js +218 -0
- package/dist/lib/bot/index.d.ts +2 -0
- package/dist/lib/bot/index.js +2 -0
- package/dist/lib/bot/local-config.d.ts +3 -2
- package/dist/lib/bot/local-config.js +5 -2
- package/dist/lib/bot/promote.d.ts +10 -0
- package/dist/lib/bot/promote.js +3 -2
- package/dist/lib/bot/prompt.d.ts +6 -1
- package/dist/lib/bot/prompt.js +8 -3
- package/dist/lib/bot/runtime/index.js +8 -4
- package/dist/lib/bot/runtime/scratch-dir.d.ts +24 -0
- package/dist/lib/bot/runtime/scratch-dir.js +41 -0
- package/dist/lib/caller-principal.d.ts +4 -0
- package/dist/lib/caller-principal.js +40 -0
- package/dist/lib/capacity/collectors/grok.js +8 -2
- package/dist/lib/cli-flag-keys.d.ts +2 -0
- package/dist/lib/cli-flag-keys.js +2 -0
- package/dist/lib/daemon/catalog.d.ts +12 -4
- package/dist/lib/daemon/catalog.js +70 -24
- package/dist/lib/daemon/config.d.ts +0 -7
- package/dist/lib/daemon/config.js +0 -32
- package/dist/lib/daemon/run.d.ts +5 -1
- package/dist/lib/daemon/run.js +48 -11
- package/dist/lib/daemon/supervisor.js +3 -1
- package/dist/lib/doctor/checks/release-links.d.ts +62 -1
- package/dist/lib/doctor/checks/release-links.js +188 -2
- package/dist/lib/doctor/fix/apply.js +54 -0
- package/dist/lib/doctor/fix/remediation.d.ts +8 -1
- package/dist/lib/doctor/fix/remediation.js +22 -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/index-render/index.d.ts +9 -0
- package/dist/lib/index-render/index.js +65 -5
- package/dist/lib/index-render/session-scope.d.ts +69 -0
- package/dist/lib/index-render/session-scope.js +279 -0
- package/dist/lib/lanes/admission.js +7 -20
- package/dist/lib/lanes/reconcile.js +13 -15
- package/dist/lib/lanes/stale-lock.d.ts +50 -0
- package/dist/lib/lanes/stale-lock.js +105 -0
- package/dist/lib/lanes/store.js +11 -76
- package/dist/lib/notify-recipient.d.ts +10 -4
- package/dist/lib/notify-recipient.js +16 -4
- package/dist/lib/secret-input-link.d.ts +42 -0
- package/dist/lib/secret-input-link.js +93 -0
- package/dist/lib/secret-scan/range.d.ts +57 -0
- package/dist/lib/secret-scan/range.js +266 -0
- package/dist/lib/secret-scan/repo-config.d.ts +18 -3
- package/dist/lib/secret-scan/repo-config.js +135 -2
- package/dist/lib/sync-core/engine-home.d.ts +44 -0
- package/dist/lib/sync-core/engine-home.js +193 -0
- package/dist/lib/sync-core/flag.d.ts +17 -0
- package/dist/lib/sync-core/flag.js +47 -0
- package/dist/lib/sync-core/reconcile.d.ts +31 -0
- package/dist/lib/sync-core/reconcile.js +196 -0
- package/dist/lib/sync-core/release.d.ts +32 -0
- package/dist/lib/sync-core/release.js +140 -0
- package/dist/lib/sync-core/rollout-state.d.ts +24 -0
- package/dist/lib/sync-core/rollout-state.js +94 -0
- package/dist/lib/vault-sync-switches.d.ts +5 -0
- package/dist/lib/vault-sync-switches.js +43 -0
- package/dist/run/script-bound-schema.d.ts +27 -0
- package/dist/run/script-bound-schema.js +97 -0
- package/dist/secrets-env-action.d.ts +2 -0
- package/dist/secrets-env-action.js +7 -2
- package/dist/utils/cognito-session.d.ts +3 -30
- package/dist/utils/cognito-session.js +2 -100
- package/dist/utils/machine-principal-state.d.ts +38 -0
- package/dist/utils/machine-principal-state.js +110 -0
- package/dist/utils/remote-list-not-converging.d.ts +21 -0
- package/dist/utils/remote-list-not-converging.js +40 -0
- package/dist/utils/safe-link.js +9 -0
- package/dist/utils/script-bound-exec.d.ts +158 -0
- package/dist/utils/script-bound-exec.js +568 -0
- package/dist/utils/sync-watchdog.d.ts +92 -0
- package/dist/utils/sync-watchdog.js +250 -0
- package/package.json +8 -8
- package/dist/lib/daemon/sync-core-canary.d.ts +0 -9
- package/dist/lib/daemon/sync-core-canary.js +0 -56
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type SyncCorePlanFigures, type SyncCoreVaultKind, type SyncCoreVaultPhase } from "./engine-home.js";
|
|
2
|
+
export interface RolloutVault {
|
|
3
|
+
uid: string;
|
|
4
|
+
kind: SyncCoreVaultKind;
|
|
5
|
+
slug?: string;
|
|
6
|
+
phase: SyncCoreVaultPhase;
|
|
7
|
+
engineHome: string;
|
|
8
|
+
legacyOffSince?: string;
|
|
9
|
+
legacyAlreadyOff?: true;
|
|
10
|
+
plan?: SyncCorePlanFigures;
|
|
11
|
+
hold?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface SyncCoreRolloutState {
|
|
14
|
+
version: 1;
|
|
15
|
+
readAt: string;
|
|
16
|
+
release: string;
|
|
17
|
+
binaryPath: string;
|
|
18
|
+
vaults: RolloutVault[];
|
|
19
|
+
error?: string;
|
|
20
|
+
}
|
|
21
|
+
export declare function syncCoreRolloutStatePath(home: string): string;
|
|
22
|
+
export declare function readSyncCoreRolloutState(home: string): SyncCoreRolloutState | null;
|
|
23
|
+
export declare function writeSyncCoreRolloutState(home: string, state: SyncCoreRolloutState): void;
|
|
24
|
+
//# sourceMappingURL=rollout-state.d.ts.map
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { isSyncCoreVaultUid } from "./engine-home.js";
|
|
4
|
+
function planFigures(value) {
|
|
5
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
6
|
+
return null;
|
|
7
|
+
const p = value;
|
|
8
|
+
if (typeof p.at !== "string")
|
|
9
|
+
return null;
|
|
10
|
+
const numbers = ["uploads", "downloads", "deletes", "remoteRows", "localFiles"];
|
|
11
|
+
if (numbers.some((key) => typeof p[key] !== "number" || !Number.isFinite(p[key])))
|
|
12
|
+
return null;
|
|
13
|
+
return {
|
|
14
|
+
at: p.at,
|
|
15
|
+
uploads: p.uploads,
|
|
16
|
+
downloads: p.downloads,
|
|
17
|
+
deletes: p.deletes,
|
|
18
|
+
remoteRows: p.remoteRows,
|
|
19
|
+
localFiles: p.localFiles,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export function syncCoreRolloutStatePath(home) {
|
|
23
|
+
return path.join(home, ".hq", "sync-core", "rollout.json");
|
|
24
|
+
}
|
|
25
|
+
function vault(value) {
|
|
26
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
27
|
+
return null;
|
|
28
|
+
const v = value;
|
|
29
|
+
if (!isSyncCoreVaultUid(v.uid))
|
|
30
|
+
return null;
|
|
31
|
+
if (v.kind !== "personal" && v.kind !== "company")
|
|
32
|
+
return null;
|
|
33
|
+
if (typeof v.engineHome !== "string" || !path.isAbsolute(v.engineHome))
|
|
34
|
+
return null;
|
|
35
|
+
if (v.legacyOffSince !== undefined && typeof v.legacyOffSince !== "string")
|
|
36
|
+
return null;
|
|
37
|
+
if (v.phase === undefined && typeof v.legacyOffSince === "string")
|
|
38
|
+
v.phase = "full";
|
|
39
|
+
if (v.phase !== "warming" && v.phase !== "full")
|
|
40
|
+
return null;
|
|
41
|
+
if (v.kind === "company" && (typeof v.slug !== "string" || !/^[a-z0-9][a-z0-9_-]*$/.test(v.slug)))
|
|
42
|
+
return null;
|
|
43
|
+
const plan = v.plan === undefined ? undefined : planFigures(v.plan);
|
|
44
|
+
if (plan === null)
|
|
45
|
+
return null;
|
|
46
|
+
if (v.hold !== undefined && typeof v.hold !== "string")
|
|
47
|
+
return null;
|
|
48
|
+
return {
|
|
49
|
+
uid: v.uid,
|
|
50
|
+
kind: v.kind,
|
|
51
|
+
...(typeof v.slug === "string" ? { slug: v.slug } : {}),
|
|
52
|
+
phase: v.phase,
|
|
53
|
+
engineHome: v.engineHome,
|
|
54
|
+
...(typeof v.legacyOffSince === "string" ? { legacyOffSince: v.legacyOffSince } : {}),
|
|
55
|
+
...(v.legacyAlreadyOff === true ? { legacyAlreadyOff: true } : {}),
|
|
56
|
+
...(plan ? { plan } : {}),
|
|
57
|
+
...(typeof v.hold === "string" ? { hold: v.hold } : {}),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
export function readSyncCoreRolloutState(home) {
|
|
61
|
+
let raw;
|
|
62
|
+
try {
|
|
63
|
+
raw = JSON.parse(fs.readFileSync(syncCoreRolloutStatePath(home), "utf8"));
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
69
|
+
return null;
|
|
70
|
+
const obj = raw;
|
|
71
|
+
if (obj.version !== 1 || typeof obj.release !== "string" || typeof obj.binaryPath !== "string")
|
|
72
|
+
return null;
|
|
73
|
+
if (typeof obj.readAt !== "string" || !Array.isArray(obj.vaults))
|
|
74
|
+
return null;
|
|
75
|
+
const vaults = obj.vaults.map(vault);
|
|
76
|
+
if (vaults.some((v) => v === null))
|
|
77
|
+
return null;
|
|
78
|
+
return {
|
|
79
|
+
version: 1,
|
|
80
|
+
readAt: obj.readAt,
|
|
81
|
+
release: obj.release,
|
|
82
|
+
binaryPath: obj.binaryPath,
|
|
83
|
+
vaults: vaults,
|
|
84
|
+
...(typeof obj.error === "string" ? { error: obj.error } : {}),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
export function writeSyncCoreRolloutState(home, state) {
|
|
88
|
+
const file = syncCoreRolloutStatePath(home);
|
|
89
|
+
fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
|
|
90
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
91
|
+
fs.writeFileSync(tmp, `${JSON.stringify(state, null, 2)}\n`, { mode: 0o600 });
|
|
92
|
+
fs.renameSync(tmp, file);
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=rollout-state.js.map
|
|
@@ -38,4 +38,9 @@ export declare function unreadableSwitchesWarning(reason: string): string;
|
|
|
38
38
|
* (`HQ_SYNC_SKIP_COMPANIES`, `HQ_SYNC_SKIP_PERSONAL`). Empty when all are on.
|
|
39
39
|
*/
|
|
40
40
|
export declare function vaultSkipEnv(switches: VaultSyncSwitches): Record<string, string>;
|
|
41
|
+
export interface VaultSyncSwitchPatch {
|
|
42
|
+
personal?: boolean;
|
|
43
|
+
companies?: Record<string, boolean>;
|
|
44
|
+
}
|
|
45
|
+
export declare function writeVaultSyncSwitches(home: string, patch: VaultSyncSwitchPatch): boolean;
|
|
41
46
|
//# sourceMappingURL=vault-sync-switches.d.ts.map
|
|
@@ -83,4 +83,47 @@ export function vaultSkipEnv(switches) {
|
|
|
83
83
|
env.HQ_SYNC_SKIP_PERSONAL = "1";
|
|
84
84
|
return env;
|
|
85
85
|
}
|
|
86
|
+
export function writeVaultSyncSwitches(home, patch) {
|
|
87
|
+
const file = vaultSyncSettingsPath(home);
|
|
88
|
+
let prefs = {};
|
|
89
|
+
let text = null;
|
|
90
|
+
try {
|
|
91
|
+
text = fs.readFileSync(file, "utf8");
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
if (err?.code !== "ENOENT")
|
|
95
|
+
throw err;
|
|
96
|
+
}
|
|
97
|
+
if (text !== null) {
|
|
98
|
+
const raw = JSON.parse(text);
|
|
99
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw))
|
|
100
|
+
throw new Error(`${file} is not a JSON object`);
|
|
101
|
+
prefs = { ...raw };
|
|
102
|
+
}
|
|
103
|
+
let changed = false;
|
|
104
|
+
if (patch.personal !== undefined && prefs.personalSyncEnabled !== patch.personal) {
|
|
105
|
+
prefs.personalSyncEnabled = patch.personal;
|
|
106
|
+
changed = true;
|
|
107
|
+
}
|
|
108
|
+
if (patch.companies && Object.keys(patch.companies).length > 0) {
|
|
109
|
+
const existing = prefs.workspaceSyncEnabled;
|
|
110
|
+
const map = typeof existing === "object" && existing !== null && !Array.isArray(existing)
|
|
111
|
+
? { ...existing }
|
|
112
|
+
: {};
|
|
113
|
+
for (const [slug, enabled] of Object.entries(patch.companies)) {
|
|
114
|
+
if (map[slug] !== enabled) {
|
|
115
|
+
map[slug] = enabled;
|
|
116
|
+
changed = true;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
prefs.workspaceSyncEnabled = map;
|
|
120
|
+
}
|
|
121
|
+
if (!changed)
|
|
122
|
+
return false;
|
|
123
|
+
fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
|
|
124
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
125
|
+
fs.writeFileSync(tmp, `${JSON.stringify(prefs, null, 2)}\n`, { mode: 0o600 });
|
|
126
|
+
fs.renameSync(tmp, file);
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
86
129
|
//# sourceMappingURL=vault-sync-switches.js.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strict env-schema reader for `hq run --script` (feedback_0e6abcb8).
|
|
3
|
+
*
|
|
4
|
+
* With --script, `hq run` must not load the caller's schema through varlock:
|
|
5
|
+
* a varlock graph can run `exec()` resolvers, load plugins, or import other
|
|
6
|
+
* files, and any of that runs inside the CLI process while the fetched
|
|
7
|
+
* secrets are in memory. A caller could write `LEAK=exec(...)` next to a
|
|
8
|
+
* script-locked `hq()` item and read the secret before the script binding is
|
|
9
|
+
* checked.
|
|
10
|
+
*
|
|
11
|
+
* So the --script path reads the schema and local files with this parser and
|
|
12
|
+
* never builds a varlock graph. Only these lines are accepted (allowlist):
|
|
13
|
+
* - blank lines and `#` comments. A comment may carry only the decorators
|
|
14
|
+
* listed in ALLOWED_DECORATORS; any other `@decorator` is refused.
|
|
15
|
+
* - `KEY=hq()` or `KEY=hq("NAME")` / `KEY=hq('NAME')`: an HQ secret.
|
|
16
|
+
* - `KEY=literal`, optionally quoted, with no `(`, `)`, `$`, `{`, `}` or
|
|
17
|
+
* backtick: a static value. An empty value is allowed.
|
|
18
|
+
* Everything else is refused with an error before any secret is fetched.
|
|
19
|
+
* Later files override earlier ones, as in the varlock path.
|
|
20
|
+
*/
|
|
21
|
+
export interface StrictSchema {
|
|
22
|
+
statics: Record<string, string>;
|
|
23
|
+
/** Env key -> vault secret name. */
|
|
24
|
+
secrets: Map<string, string>;
|
|
25
|
+
}
|
|
26
|
+
export declare function parseStrictSchemaFiles(paths: string[]): StrictSchema;
|
|
27
|
+
//# sourceMappingURL=script-bound-schema.d.ts.map
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strict env-schema reader for `hq run --script` (feedback_0e6abcb8).
|
|
3
|
+
*
|
|
4
|
+
* With --script, `hq run` must not load the caller's schema through varlock:
|
|
5
|
+
* a varlock graph can run `exec()` resolvers, load plugins, or import other
|
|
6
|
+
* files, and any of that runs inside the CLI process while the fetched
|
|
7
|
+
* secrets are in memory. A caller could write `LEAK=exec(...)` next to a
|
|
8
|
+
* script-locked `hq()` item and read the secret before the script binding is
|
|
9
|
+
* checked.
|
|
10
|
+
*
|
|
11
|
+
* So the --script path reads the schema and local files with this parser and
|
|
12
|
+
* never builds a varlock graph. Only these lines are accepted (allowlist):
|
|
13
|
+
* - blank lines and `#` comments. A comment may carry only the decorators
|
|
14
|
+
* listed in ALLOWED_DECORATORS; any other `@decorator` is refused.
|
|
15
|
+
* - `KEY=hq()` or `KEY=hq("NAME")` / `KEY=hq('NAME')`: an HQ secret.
|
|
16
|
+
* - `KEY=literal`, optionally quoted, with no `(`, `)`, `$`, `{`, `}` or
|
|
17
|
+
* backtick: a static value. An empty value is allowed.
|
|
18
|
+
* Everything else is refused with an error before any secret is fetched.
|
|
19
|
+
* Later files override earlier ones, as in the varlock path.
|
|
20
|
+
*/
|
|
21
|
+
import * as fs from 'node:fs';
|
|
22
|
+
const KEY = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
23
|
+
const SECRET_NAME = /^[A-Z][A-Z0-9_]*(\/[A-Z][A-Z0-9_]+)*$/;
|
|
24
|
+
const HQ_CALL = /^hq\(\s*(?:(["'])([^"']*)\1)?\s*\)$/;
|
|
25
|
+
const UNSAFE_LITERAL = /[()${}`]/;
|
|
26
|
+
const ALLOWED_DECORATORS = new Set([
|
|
27
|
+
'hqCompany',
|
|
28
|
+
'required',
|
|
29
|
+
'optional',
|
|
30
|
+
'sensitive',
|
|
31
|
+
'public',
|
|
32
|
+
'defaultRequired',
|
|
33
|
+
'defaultSensitive',
|
|
34
|
+
]);
|
|
35
|
+
function refuse(file, lineNo, reason) {
|
|
36
|
+
throw new Error(`hq run --script reads ${file} in strict mode and refused line ${lineNo}: ${reason}. ` +
|
|
37
|
+
'With --script only KEY=hq(...), KEY=literal and the decorators ' +
|
|
38
|
+
`${[...ALLOWED_DECORATORS].map((d) => '@' + d).join(', ')} are allowed; resolvers such as exec() and @plugin/@import are not. ` +
|
|
39
|
+
'Remove them, or run without --script.');
|
|
40
|
+
}
|
|
41
|
+
function unquote(value) {
|
|
42
|
+
if (value.length >= 2) {
|
|
43
|
+
const q = value[0];
|
|
44
|
+
if ((q === '"' || q === "'") && value[value.length - 1] === q) {
|
|
45
|
+
const inner = value.slice(1, -1);
|
|
46
|
+
if (!inner.includes(q))
|
|
47
|
+
return inner;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return value;
|
|
51
|
+
}
|
|
52
|
+
export function parseStrictSchemaFiles(paths) {
|
|
53
|
+
const statics = {};
|
|
54
|
+
const secrets = new Map();
|
|
55
|
+
for (const file of paths) {
|
|
56
|
+
const lines = fs.readFileSync(file, 'utf8').split(/\r?\n/);
|
|
57
|
+
lines.forEach((raw, index) => {
|
|
58
|
+
const lineNo = index + 1;
|
|
59
|
+
const line = raw.trim();
|
|
60
|
+
if (line === '')
|
|
61
|
+
return;
|
|
62
|
+
if (line.startsWith('#')) {
|
|
63
|
+
for (const match of line.matchAll(/@([A-Za-z_][A-Za-z0-9_]*)/g)) {
|
|
64
|
+
if (!ALLOWED_DECORATORS.has(match[1]))
|
|
65
|
+
refuse(file, lineNo, `decorator @${match[1]} is not allowed`);
|
|
66
|
+
}
|
|
67
|
+
// An allowed decorator may take only a quoted literal argument.
|
|
68
|
+
const stripped = line.replace(/@hqCompany\(\s*"[^"()$`{}]*"\s*\)/g, '');
|
|
69
|
+
if (/[()]/.test(stripped) && /@/.test(stripped))
|
|
70
|
+
refuse(file, lineNo, 'decorator arguments are not allowed');
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const eq = line.indexOf('=');
|
|
74
|
+
if (eq <= 0)
|
|
75
|
+
refuse(file, lineNo, 'expected KEY=value');
|
|
76
|
+
const key = line.slice(0, eq).trim().replace(/^export\s+/, '');
|
|
77
|
+
if (!KEY.test(key))
|
|
78
|
+
refuse(file, lineNo, 'invalid key');
|
|
79
|
+
const value = line.slice(eq + 1).trim();
|
|
80
|
+
const hq = HQ_CALL.exec(value);
|
|
81
|
+
if (hq) {
|
|
82
|
+
const name = hq[2] || key;
|
|
83
|
+
if (!SECRET_NAME.test(name))
|
|
84
|
+
refuse(file, lineNo, `invalid secret name for ${key}`);
|
|
85
|
+
secrets.set(key, name);
|
|
86
|
+
delete statics[key];
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (UNSAFE_LITERAL.test(value))
|
|
90
|
+
refuse(file, lineNo, `${key} is not a plain literal or hq() call`);
|
|
91
|
+
statics[key] = unquote(value);
|
|
92
|
+
secrets.delete(key);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
return { statics, secrets };
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=script-bound-schema.js.map
|
|
@@ -76,6 +76,8 @@ export declare function requestedApiKeyCompany(flagCompany: string | undefined):
|
|
|
76
76
|
export declare function assertApiKeyCompany(token: string, requested: string, commandLabel: string): Promise<string>;
|
|
77
77
|
export declare function loadRevealedSecrets(token: string, companyUid: string, keys: string[], usage?: SecretUsage, options?: {
|
|
78
78
|
allowOfflineFallback?: boolean;
|
|
79
|
+
/** Filled with each returned row's scriptLock.mode (undefined when absent). */
|
|
80
|
+
lockModes?: Map<string, string | undefined>;
|
|
79
81
|
}): Promise<Map<string, string>>;
|
|
80
82
|
/**
|
|
81
83
|
* Emit `hq secrets env` output and terminate deterministically.
|
|
@@ -129,7 +129,7 @@ export function parseSecretNameList(input) {
|
|
|
129
129
|
// refuse a key bound to a different company. Without it the fetch route reads
|
|
130
130
|
// from the key's own company, which made `--company <other>` silently return
|
|
131
131
|
// the key company's same-named secret (P0 triage, 2026-10-11).
|
|
132
|
-
async function loadRevealedSecretsViaApiKey(token, keys, companyUid) {
|
|
132
|
+
async function loadRevealedSecretsViaApiKey(token, keys, companyUid, lockModes) {
|
|
133
133
|
const resolved = new Map();
|
|
134
134
|
const requested = [...new Set(keys)];
|
|
135
135
|
const cacheScope = "__api_key__";
|
|
@@ -170,6 +170,10 @@ async function loadRevealedSecretsViaApiKey(token, keys, companyUid) {
|
|
|
170
170
|
throw new Error(`Failed to fetch secret '${name}': malformed fetch response`);
|
|
171
171
|
}
|
|
172
172
|
removeCacheEntry(cacheScope, name);
|
|
173
|
+
// The API-key fetch route returns the secret's scriptLock (the vault denies
|
|
174
|
+
// an enforced secret on this route, since it carries no script usage).
|
|
175
|
+
const mode = secret.scriptLock?.mode;
|
|
176
|
+
lockModes?.set(name, typeof mode === "string" ? mode : undefined);
|
|
173
177
|
resolved.set(name, secret.value);
|
|
174
178
|
}
|
|
175
179
|
return resolved;
|
|
@@ -219,7 +223,7 @@ export async function assertApiKeyCompany(token, requested, commandLabel) {
|
|
|
219
223
|
}
|
|
220
224
|
export async function loadRevealedSecrets(token, companyUid, keys, usage, options = {}) {
|
|
221
225
|
if (token.startsWith(HQ_API_KEY_PREFIX)) {
|
|
222
|
-
return loadRevealedSecretsViaApiKey(token, keys, companyUid);
|
|
226
|
+
return loadRevealedSecretsViaApiKey(token, keys, companyUid, options.lockModes);
|
|
223
227
|
}
|
|
224
228
|
const resolved = new Map();
|
|
225
229
|
const requested = [...new Set(keys)];
|
|
@@ -284,6 +288,7 @@ export async function loadRevealedSecrets(token, companyUid, keys, usage, option
|
|
|
284
288
|
throw new Error("Invalid secret load response from vault");
|
|
285
289
|
}
|
|
286
290
|
seenNames.add(s.name);
|
|
291
|
+
options.lockModes?.set(s.name, s.scriptLock?.mode);
|
|
287
292
|
if (s.revokedAt != null) {
|
|
288
293
|
errorsByName.set(s.name, {
|
|
289
294
|
code: "revoked",
|
|
@@ -19,11 +19,12 @@
|
|
|
19
19
|
* HQ_VAULT_API_URL — vault-service API Gateway URL
|
|
20
20
|
*/
|
|
21
21
|
import { isExpiring, isMachineIdentity, type CognitoAuthConfig, type CognitoTokens, type ClientInfo, type VaultServiceConfig } from "@indigoai-us/hq-cloud";
|
|
22
|
+
import { hasPersonCachedSession, isMachineCredsFileEnvSet, wantsMachineCognitoTokens, type CognitoTokenSource } from "./machine-principal-state.js";
|
|
22
23
|
import { loadCachedTokens, personTokenCacheFile, readPersonTokenFile } from "./person-token-cache.js";
|
|
23
24
|
export { loadCachedTokens, personTokenCacheFile, readPersonTokenFile };
|
|
24
25
|
export { isExpiring, isMachineIdentity };
|
|
25
|
-
|
|
26
|
-
export type CognitoTokenSource
|
|
26
|
+
export { hasPersonCachedSession, isMachineCredsFileEnvSet, wantsMachineCognitoTokens };
|
|
27
|
+
export type { CognitoTokenSource };
|
|
27
28
|
/** Actor kind derived from the active token / machine-creds signal. */
|
|
28
29
|
export type CognitoActorKind = "human" | "agent" | "unknown";
|
|
29
30
|
/**
|
|
@@ -97,13 +98,6 @@ export declare const DEFAULT_HQ_ROOT: string;
|
|
|
97
98
|
export declare function machineTokenStateDir(home?: string, env?: NodeJS.ProcessEnv): string;
|
|
98
99
|
/** Absolute path of the machine-token cache file (daemon state dir). */
|
|
99
100
|
export declare function machineTokenCacheFile(home?: string, env?: NodeJS.ProcessEnv): string;
|
|
100
|
-
/**
|
|
101
|
-
* True when `HQ_MACHINE_CREDS_FILE` is explicitly set (non-empty) in `env`.
|
|
102
|
-
* Explicit env remains the primary opt-in for daemon units. Fresh agent boxes
|
|
103
|
-
* without the env still mint via {@link wantsMachineCognitoTokens}'s
|
|
104
|
-
* default-file fallback when no usable person session exists.
|
|
105
|
-
*/
|
|
106
|
-
export declare function isMachineCredsFileEnvSet(env?: NodeJS.ProcessEnv): boolean;
|
|
107
101
|
/** Default machine-creds path hq-cloud also checks when the env is unset. */
|
|
108
102
|
export declare function defaultMachineCredsFile(home?: string): string;
|
|
109
103
|
export type EnsureCognitoTokenOptions = {
|
|
@@ -127,33 +121,12 @@ export type EnsureCognitoTokenOptions = {
|
|
|
127
121
|
export declare const HQ_NO_BROWSER_LOGIN_ENV = "HQ_NO_BROWSER_LOGIN";
|
|
128
122
|
/** An explicit `interactive` option wins; otherwise interactive unless the env switch is "1". */
|
|
129
123
|
export declare function resolveInteractiveLogin(options: Pick<EnsureCognitoTokenOptions, "interactive">, env?: NodeJS.ProcessEnv): boolean;
|
|
130
|
-
/**
|
|
131
|
-
* True when a usable person Cognito cache file exists under the effective
|
|
132
|
-
* `HQ_STATE_DIR` (or `~/.hq`). Presence alone keeps human commands on the
|
|
133
|
-
* person path even when the default machine-creds file is also on disk
|
|
134
|
-
* (no-flip guarantee).
|
|
135
|
-
*/
|
|
136
|
-
export declare function hasPersonCachedSession(env?: NodeJS.ProcessEnv): boolean;
|
|
137
124
|
/**
|
|
138
125
|
* Fresh-box / Outpost bootstrap: env unset, not forced to person, no person
|
|
139
126
|
* cache, and the default machine-creds file is readable. Distinct from
|
|
140
127
|
* explicit `HQ_MACHINE_CREDS_FILE` / `{ tokenSource: "machine" }` opt-in.
|
|
141
128
|
*/
|
|
142
129
|
export declare function isDefaultMachineCredsFallback(options?: Pick<EnsureCognitoTokenOptions, "tokenSource">, env?: NodeJS.ProcessEnv): boolean;
|
|
143
|
-
/**
|
|
144
|
-
* Whether this call should mint/cache via machine creds (USER_PASSWORD_AUTH).
|
|
145
|
-
*
|
|
146
|
-
* Machine path when:
|
|
147
|
-
* - explicit `{ tokenSource: "machine" }` and creds are readable, or
|
|
148
|
-
* - `HQ_MACHINE_CREDS_FILE` is set and readable, or
|
|
149
|
-
* - fresh-box fallback: env unset, no person cache, default
|
|
150
|
-
* `~/.hq-agent/machine-creds.json` readable.
|
|
151
|
-
*
|
|
152
|
-
* Explicit `{ tokenSource: "person" }` never uses machine creds. A present
|
|
153
|
-
* person cache keeps the person path even when the default creds file exists
|
|
154
|
-
* (do not flip a human's commands onto machine tokens).
|
|
155
|
-
*/
|
|
156
|
-
export declare function wantsMachineCognitoTokens(options?: Pick<EnsureCognitoTokenOptions, "tokenSource">, env?: NodeJS.ProcessEnv): boolean;
|
|
157
130
|
/**
|
|
158
131
|
* Which Cognito token source this process will use for doctor / status / whoami.
|
|
159
132
|
* Matches {@link wantsMachineCognitoTokens}: env opt-in, or default-file
|
|
@@ -26,6 +26,7 @@ import * as path from "path";
|
|
|
26
26
|
import * as yaml from "js-yaml";
|
|
27
27
|
import chalk from "chalk";
|
|
28
28
|
import { isExpiring, refreshTokens, saveCachedTokens, browserLogin, detectHqCoreVersion, isMachineIdentity, getValidMachineTokens, CognitoRefreshError, accessTokenFingerprint, invalidateCachedTokensByFingerprint, } from "@indigoai-us/hq-cloud";
|
|
29
|
+
import { hasPersonCachedSession, isMachineCredsFileEnvSet, probeMachineIdentity, wantsMachineCognitoTokens, } from "./machine-principal-state.js";
|
|
29
30
|
import { CLI_NAME, CLI_VERSION } from "../cli-version.js";
|
|
30
31
|
import { daemonDir } from "../lib/mesh/live/daemon/paths.js";
|
|
31
32
|
import { workMeshRoot } from "../lib/mesh/live/paths.js";
|
|
@@ -36,6 +37,7 @@ export { loadCachedTokens, personTokenCacheFile, readPersonTokenFile };
|
|
|
36
37
|
// Re-export the cached-session primitives for callers that must deliberately
|
|
37
38
|
// avoid the refresh/login behavior in the higher-level helpers below.
|
|
38
39
|
export { isExpiring, isMachineIdentity };
|
|
40
|
+
export { hasPersonCachedSession, isMachineCredsFileEnvSet, wantsMachineCognitoTokens };
|
|
39
41
|
/**
|
|
40
42
|
* Override for where machine-minted Cognito tokens are cached.
|
|
41
43
|
* Default: the work-mesh daemon state dir (`~/.hq/work-mesh/daemon`), so a
|
|
@@ -253,16 +255,6 @@ export function machineTokenStateDir(home = os.homedir(), env = process.env) {
|
|
|
253
255
|
export function machineTokenCacheFile(home = os.homedir(), env = process.env) {
|
|
254
256
|
return path.join(machineTokenStateDir(home, env), "cognito-tokens.json");
|
|
255
257
|
}
|
|
256
|
-
/**
|
|
257
|
-
* True when `HQ_MACHINE_CREDS_FILE` is explicitly set (non-empty) in `env`.
|
|
258
|
-
* Explicit env remains the primary opt-in for daemon units. Fresh agent boxes
|
|
259
|
-
* without the env still mint via {@link wantsMachineCognitoTokens}'s
|
|
260
|
-
* default-file fallback when no usable person session exists.
|
|
261
|
-
*/
|
|
262
|
-
export function isMachineCredsFileEnvSet(env = process.env) {
|
|
263
|
-
const raw = env.HQ_MACHINE_CREDS_FILE;
|
|
264
|
-
return typeof raw === "string" && raw.trim().length > 0;
|
|
265
|
-
}
|
|
266
258
|
/** Default machine-creds path hq-cloud also checks when the env is unset. */
|
|
267
259
|
export function defaultMachineCredsFile(home = os.homedir()) {
|
|
268
260
|
return path.join(home, ".hq-agent", "machine-creds.json");
|
|
@@ -279,69 +271,6 @@ export function resolveInteractiveLogin(options, env = process.env) {
|
|
|
279
271
|
return options.interactive;
|
|
280
272
|
return env[HQ_NO_BROWSER_LOGIN_ENV]?.trim() !== "1";
|
|
281
273
|
}
|
|
282
|
-
/**
|
|
283
|
-
* Align `process.env.HQ_MACHINE_CREDS_FILE` with a (possibly synthetic) `env`
|
|
284
|
-
* for the duration of `fn`, so hq-cloud's `isMachineIdentity()` sees the same
|
|
285
|
-
* value doctor / tests intended.
|
|
286
|
-
*/
|
|
287
|
-
function withAlignedMachineCredsEnv(env, fn) {
|
|
288
|
-
const prevCreds = process.env.HQ_MACHINE_CREDS_FILE;
|
|
289
|
-
const nextCreds = env.HQ_MACHINE_CREDS_FILE;
|
|
290
|
-
if (nextCreds !== prevCreds) {
|
|
291
|
-
if (nextCreds === undefined || String(nextCreds).trim() === "") {
|
|
292
|
-
delete process.env.HQ_MACHINE_CREDS_FILE;
|
|
293
|
-
}
|
|
294
|
-
else {
|
|
295
|
-
process.env.HQ_MACHINE_CREDS_FILE = nextCreds;
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
try {
|
|
299
|
-
return fn();
|
|
300
|
-
}
|
|
301
|
-
finally {
|
|
302
|
-
if (nextCreds !== prevCreds) {
|
|
303
|
-
if (prevCreds === undefined)
|
|
304
|
-
delete process.env.HQ_MACHINE_CREDS_FILE;
|
|
305
|
-
else
|
|
306
|
-
process.env.HQ_MACHINE_CREDS_FILE = prevCreds;
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
/**
|
|
311
|
-
* True when a usable person Cognito cache file exists under the effective
|
|
312
|
-
* `HQ_STATE_DIR` (or `~/.hq`). Presence alone keeps human commands on the
|
|
313
|
-
* person path even when the default machine-creds file is also on disk
|
|
314
|
-
* (no-flip guarantee).
|
|
315
|
-
*/
|
|
316
|
-
export function hasPersonCachedSession(env = process.env) {
|
|
317
|
-
try {
|
|
318
|
-
const file = personTokenCacheFile(os.homedir(), env);
|
|
319
|
-
const raw = readPersonTokenFile(os.homedir(), env);
|
|
320
|
-
if (!raw ||
|
|
321
|
-
typeof raw.accessToken !== "string" ||
|
|
322
|
-
raw.accessToken.length === 0 ||
|
|
323
|
-
typeof raw.idToken !== "string" ||
|
|
324
|
-
raw.idToken.length === 0) {
|
|
325
|
-
return false;
|
|
326
|
-
}
|
|
327
|
-
// Definitive refresh rejection writes `${file}.invalid.<fingerprint>`;
|
|
328
|
-
// treat that as no usable person session so status/whoami report machine.
|
|
329
|
-
const marker = `${file}.invalid.${accessTokenFingerprint(raw.accessToken)}`;
|
|
330
|
-
if (fs.existsSync(marker))
|
|
331
|
-
return false;
|
|
332
|
-
return true;
|
|
333
|
-
}
|
|
334
|
-
catch {
|
|
335
|
-
return false;
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
/**
|
|
339
|
-
* Whether hq-cloud currently sees readable machine creds (env override or
|
|
340
|
-
* default `~/.hq-agent/machine-creds.json`), honoring a synthetic `env`.
|
|
341
|
-
*/
|
|
342
|
-
function probeMachineIdentity(env) {
|
|
343
|
-
return withAlignedMachineCredsEnv(env, () => isMachineIdentity());
|
|
344
|
-
}
|
|
345
274
|
/**
|
|
346
275
|
* Fresh-box / Outpost bootstrap: env unset, not forced to person, no person
|
|
347
276
|
* cache, and the default machine-creds file is readable. Distinct from
|
|
@@ -361,33 +290,6 @@ export function isDefaultMachineCredsFallback(options = {}, env = process.env) {
|
|
|
361
290
|
function logDefaultMachineCredsFallback() {
|
|
362
291
|
console.error(chalk.dim("machine identity from default creds file"));
|
|
363
292
|
}
|
|
364
|
-
/**
|
|
365
|
-
* Whether this call should mint/cache via machine creds (USER_PASSWORD_AUTH).
|
|
366
|
-
*
|
|
367
|
-
* Machine path when:
|
|
368
|
-
* - explicit `{ tokenSource: "machine" }` and creds are readable, or
|
|
369
|
-
* - `HQ_MACHINE_CREDS_FILE` is set and readable, or
|
|
370
|
-
* - fresh-box fallback: env unset, no person cache, default
|
|
371
|
-
* `~/.hq-agent/machine-creds.json` readable.
|
|
372
|
-
*
|
|
373
|
-
* Explicit `{ tokenSource: "person" }` never uses machine creds. A present
|
|
374
|
-
* person cache keeps the person path even when the default creds file exists
|
|
375
|
-
* (do not flip a human's commands onto machine tokens).
|
|
376
|
-
*/
|
|
377
|
-
export function wantsMachineCognitoTokens(options = {}, env = process.env) {
|
|
378
|
-
if (options.tokenSource === "person")
|
|
379
|
-
return false;
|
|
380
|
-
if (options.tokenSource === "machine") {
|
|
381
|
-
return probeMachineIdentity(env);
|
|
382
|
-
}
|
|
383
|
-
if (isMachineCredsFileEnvSet(env)) {
|
|
384
|
-
return probeMachineIdentity(env);
|
|
385
|
-
}
|
|
386
|
-
// Fresh-box fallback — default creds file only when no person session.
|
|
387
|
-
if (hasPersonCachedSession(env))
|
|
388
|
-
return false;
|
|
389
|
-
return probeMachineIdentity(env);
|
|
390
|
-
}
|
|
391
293
|
/**
|
|
392
294
|
* Which Cognito token source this process will use for doctor / status / whoami.
|
|
393
295
|
* Matches {@link wantsMachineCognitoTokens}: env opt-in, or default-file
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/** Person browser/login cache vs machine-creds USER_PASSWORD_AUTH mint. */
|
|
2
|
+
export type CognitoTokenSource = "person" | "machine";
|
|
3
|
+
/**
|
|
4
|
+
* True when `HQ_MACHINE_CREDS_FILE` is explicitly set (non-empty) in `env`.
|
|
5
|
+
* Explicit env remains the primary opt-in for daemon units. Fresh agent boxes
|
|
6
|
+
* without the env still mint via {@link wantsMachineCognitoTokens}'s
|
|
7
|
+
* default-file fallback when no usable person session exists.
|
|
8
|
+
*/
|
|
9
|
+
export declare function isMachineCredsFileEnvSet(env?: NodeJS.ProcessEnv): boolean;
|
|
10
|
+
/**
|
|
11
|
+
* True when a usable person Cognito cache file exists under the effective
|
|
12
|
+
* `HQ_STATE_DIR` (or `~/.hq`). Presence alone keeps human commands on the
|
|
13
|
+
* person path even when the default machine-creds file is also on disk
|
|
14
|
+
* (no-flip guarantee).
|
|
15
|
+
*/
|
|
16
|
+
export declare function hasPersonCachedSession(env?: NodeJS.ProcessEnv): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Whether hq-cloud currently sees readable machine creds (env override or
|
|
19
|
+
* default `~/.hq-agent/machine-creds.json`), honoring a synthetic `env`.
|
|
20
|
+
*/
|
|
21
|
+
export declare function probeMachineIdentity(env: NodeJS.ProcessEnv): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Whether this call should mint/cache via machine creds (USER_PASSWORD_AUTH).
|
|
24
|
+
*
|
|
25
|
+
* Machine path when:
|
|
26
|
+
* - explicit `{ tokenSource: "machine" }` and creds are readable, or
|
|
27
|
+
* - `HQ_MACHINE_CREDS_FILE` is set and readable, or
|
|
28
|
+
* - fresh-box fallback: env unset, no person cache, default
|
|
29
|
+
* `~/.hq-agent/machine-creds.json` readable.
|
|
30
|
+
*
|
|
31
|
+
* Explicit `{ tokenSource: "person" }` never uses machine creds. A present
|
|
32
|
+
* person cache keeps the person path even when the default creds file exists
|
|
33
|
+
* (do not flip a human's commands onto machine tokens).
|
|
34
|
+
*/
|
|
35
|
+
export declare function wantsMachineCognitoTokens(options?: {
|
|
36
|
+
tokenSource?: CognitoTokenSource;
|
|
37
|
+
}, env?: NodeJS.ProcessEnv): boolean;
|
|
38
|
+
//# sourceMappingURL=machine-principal-state.d.ts.map
|