@indigoai-us/hq-cli 5.309.1 → 5.310.0
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 +3 -1
- package/assets/scaffold/core/scripts/hq-dm-bind.sh +585 -0
- package/assets/scaffold/core/scripts/work-mesh-live-bind-trusted.sh +152 -0
- package/dist/command-catalog.generated.d.ts +26 -0
- package/dist/command-catalog.generated.js +32 -0
- package/dist/commands/core-dm-bind-native.d.ts +32 -0
- package/dist/commands/core-dm-bind-native.js +668 -0
- package/dist/commands/core-native-utilities.d.ts +2 -1
- package/dist/commands/core-native-utilities.js +4 -2
- package/dist/commands/core-work-mesh-bind-trusted-native.d.ts +32 -0
- package/dist/commands/core-work-mesh-bind-trusted-native.js +190 -0
- package/dist/commands/core.js +6 -0
- package/package.json +1 -1
|
@@ -5,6 +5,7 @@ export type UtilityOutput = {
|
|
|
5
5
|
code: number;
|
|
6
6
|
stdout: string;
|
|
7
7
|
stderr: string;
|
|
8
|
+
stderrBytes?: Uint8Array;
|
|
8
9
|
};
|
|
9
10
|
export type UtilityContext = {
|
|
10
11
|
root: string;
|
|
@@ -18,7 +19,7 @@ export type NativeUtilityDefinition = {
|
|
|
18
19
|
root: CoreCommandRoot;
|
|
19
20
|
summary: string;
|
|
20
21
|
resolveRoot: (cwd: string, hqRoot?: string) => string;
|
|
21
|
-
run: (args: string[], context: UtilityContext) => UtilityOutput
|
|
22
|
+
run: (args: string[], context: UtilityContext) => UtilityOutput | Promise<UtilityOutput>;
|
|
22
23
|
};
|
|
23
24
|
/** Register one raw-argv command and retain the original shell as rollback. */
|
|
24
25
|
export declare function registerNativeUtility(core: Command, definition: NativeUtilityDefinition): void;
|
|
@@ -31,10 +31,12 @@ export function registerNativeUtility(core, definition) {
|
|
|
31
31
|
process.exitCode = result.code;
|
|
32
32
|
return;
|
|
33
33
|
}
|
|
34
|
-
const result = definition.run(operands, { root, cwd, env: process.env, now: () => new Date() });
|
|
34
|
+
const result = await definition.run(operands, { root, cwd, env: process.env, now: () => new Date() });
|
|
35
35
|
if (result.stdout)
|
|
36
36
|
process.stdout.write(result.stdout);
|
|
37
|
-
if (result.
|
|
37
|
+
if (result.stderrBytes)
|
|
38
|
+
process.stderr.write(result.stderrBytes);
|
|
39
|
+
else if (result.stderr)
|
|
38
40
|
process.stderr.write(result.stderr);
|
|
39
41
|
if (result.code !== 0)
|
|
40
42
|
process.exitCode = result.code;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** Native implementation of core/scripts/work-mesh-live-bind-trusted.sh. */
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import type { Command } from "commander";
|
|
4
|
+
import { type UtilityOutput } from "./core-native-utilities.js";
|
|
5
|
+
export declare const WORK_MESH_BIND_TRUSTED_ASSET = "core/scripts/work-mesh-live-bind-trusted.sh";
|
|
6
|
+
export declare const WORK_MESH_BIND_TRUSTED_HELP = "hq-core: public\nUS-011 \u2014 write session meta + reconcile with observation.trustedContext.\nNo --trusted CLI flag in hq-cli; trusted bind is the observation field.\nset -euo pipefail\n\nROOT=\"${HQ_ROOT:-${CLAUDE_PROJECT_DIR:-}}\"\nif [ -z \"$ROOT\" ]; then\n";
|
|
7
|
+
export type WorkMeshBindDeps = {
|
|
8
|
+
root: string;
|
|
9
|
+
cwd: string;
|
|
10
|
+
env: NodeJS.ProcessEnv;
|
|
11
|
+
now: () => Date;
|
|
12
|
+
fs: typeof fs;
|
|
13
|
+
runHq: (args: string[], env: NodeJS.ProcessEnv) => {
|
|
14
|
+
code: number;
|
|
15
|
+
stdout: string;
|
|
16
|
+
stderr: string;
|
|
17
|
+
};
|
|
18
|
+
runHqSession: (root: string, args: string[], env: NodeJS.ProcessEnv) => {
|
|
19
|
+
code: number;
|
|
20
|
+
stdout: string;
|
|
21
|
+
stderr: string;
|
|
22
|
+
};
|
|
23
|
+
spawnHq: (args: string[], env: NodeJS.ProcessEnv) => void;
|
|
24
|
+
pid: number;
|
|
25
|
+
tmpDir: string;
|
|
26
|
+
ulid: (now: Date) => string;
|
|
27
|
+
};
|
|
28
|
+
export declare const createWorkMeshBindDeps: (root: string, cwd: string, env: NodeJS.ProcessEnv, now: () => Date) => WorkMeshBindDeps;
|
|
29
|
+
export declare function resolveWorkMeshBindSessionId(root: string, env: NodeJS.ProcessEnv, fileSystem?: typeof fs): string;
|
|
30
|
+
export declare function runWorkMeshBindTrusted(args: string[], deps: WorkMeshBindDeps): UtilityOutput;
|
|
31
|
+
export declare function registerCoreWorkMeshBindTrustedNative(core: Command): void;
|
|
32
|
+
//# sourceMappingURL=core-work-mesh-bind-trusted-native.d.ts.map
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/** Native implementation of core/scripts/work-mesh-live-bind-trusted.sh. */
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
5
|
+
import { generateUlid } from "../lib/mesh/live/ulid.js";
|
|
6
|
+
import { resolveLiveRoot } from "../utils/hq-roots.js";
|
|
7
|
+
import { registerNativeUtility } from "./core-native-utilities.js";
|
|
8
|
+
export const WORK_MESH_BIND_TRUSTED_ASSET = "core/scripts/work-mesh-live-bind-trusted.sh";
|
|
9
|
+
export const WORK_MESH_BIND_TRUSTED_HELP = `hq-core: public
|
|
10
|
+
US-011 — write session meta + reconcile with observation.trustedContext.
|
|
11
|
+
No --trusted CLI flag in hq-cli; trusted bind is the observation field.
|
|
12
|
+
set -euo pipefail
|
|
13
|
+
|
|
14
|
+
ROOT="\${HQ_ROOT:-\${CLAUDE_PROJECT_DIR:-}}"
|
|
15
|
+
if [ -z "$ROOT" ]; then
|
|
16
|
+
`;
|
|
17
|
+
export const createWorkMeshBindDeps = (root, cwd, env, now) => ({
|
|
18
|
+
root, cwd, env, now, fs, pid: process.pid, tmpDir: env.TMPDIR || "/tmp", ulid: (date) => generateUlid(date.getTime()),
|
|
19
|
+
runHq: (args, childEnv) => {
|
|
20
|
+
const result = spawnSync("hq", args, { cwd, env: { ...childEnv, HQ_ROOT: root }, encoding: "utf8" });
|
|
21
|
+
return { code: result.status ?? 127, stdout: result.stdout ?? "", stderr: result.stderr ?? (result.error ? `${result.error.message}\n` : "") };
|
|
22
|
+
},
|
|
23
|
+
runHqSession: (scriptRoot, args, childEnv) => {
|
|
24
|
+
const result = spawnSync("bash", [path.join(scriptRoot, "core/scripts/hq-session.sh"), ...args], { cwd, env: childEnv, encoding: "utf8" });
|
|
25
|
+
return { code: result.status ?? 127, stdout: result.stdout ?? "", stderr: result.stderr ?? (result.error ? `${result.error.message}\n` : "") };
|
|
26
|
+
},
|
|
27
|
+
spawnHq: (args, childEnv) => {
|
|
28
|
+
const child = spawn("hq", args, { cwd, env: { ...childEnv, HQ_ROOT: root }, detached: true, stdio: "ignore" });
|
|
29
|
+
child.unref();
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
export function resolveWorkMeshBindSessionId(root, env, fileSystem = fs) {
|
|
33
|
+
for (const key of ["HQ_SESSION_ID", "CLAUDE_CODE_SESSION_ID", "CLAUDE_SESSION_ID", "CODEX_SESSION_ID", "CODEX_THREAD_ID"]) {
|
|
34
|
+
const value = (env[key] ?? "").replace(/\s/g, "");
|
|
35
|
+
if (value)
|
|
36
|
+
return value;
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
const value = fileSystem.readFileSync(path.join(root, "workspace/sessions/.current"), "utf8").replace(/\s/g, "");
|
|
40
|
+
return value;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return "";
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function hasExecutable(deps, command) {
|
|
47
|
+
for (const directory of (deps.env.PATH ?? "").split(path.delimiter)) {
|
|
48
|
+
if (!directory)
|
|
49
|
+
continue;
|
|
50
|
+
try {
|
|
51
|
+
const stat = deps.fs.statSync(path.join(directory, command));
|
|
52
|
+
if (stat.isFile() && (stat.mode & 0o111) !== 0)
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
catch { /* PATH entry is absent */ }
|
|
56
|
+
}
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
function workMeshJsonQuote(value) {
|
|
60
|
+
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t")}"`;
|
|
61
|
+
}
|
|
62
|
+
function workMeshObservationJson(observation) {
|
|
63
|
+
const identity = observation.identity;
|
|
64
|
+
const trusted = observation.trustedContext;
|
|
65
|
+
let json = `{"contractVersion":1,"clientOperationId":${workMeshJsonQuote(String(observation.clientOperationId))},"identity":{"sessionId":${workMeshJsonQuote(identity.sessionId)},"harness":${workMeshJsonQuote(identity.harness)},"adapterVersion":${workMeshJsonQuote(identity.adapterVersion)}},"cwd":${workMeshJsonQuote(String(observation.cwd))},"hqRoot":${workMeshJsonQuote(String(observation.hqRoot))}`;
|
|
66
|
+
if (trusted) {
|
|
67
|
+
json += ',"trustedContext":{';
|
|
68
|
+
let first = true;
|
|
69
|
+
for (const [key, value] of Object.entries(trusted)) {
|
|
70
|
+
if (!first)
|
|
71
|
+
json += ",";
|
|
72
|
+
first = false;
|
|
73
|
+
json += `${workMeshJsonQuote(key)}:${workMeshJsonQuote(value)}`;
|
|
74
|
+
}
|
|
75
|
+
json += "}";
|
|
76
|
+
}
|
|
77
|
+
return `${json}}`;
|
|
78
|
+
}
|
|
79
|
+
export function runWorkMeshBindTrusted(args, deps) {
|
|
80
|
+
let root = deps.env.HQ_ROOT || deps.env.CLAUDE_PROJECT_DIR || deps.root;
|
|
81
|
+
let company = "", project = "", task = "", sid = "", noReconcile = false;
|
|
82
|
+
const output = [];
|
|
83
|
+
for (let i = 0; i < args.length;) {
|
|
84
|
+
const arg = args[i];
|
|
85
|
+
if (["--company", "--company-slug", "--project", "--task", "--session", "--session-id", "--root"].includes(arg)) {
|
|
86
|
+
const value = args[i + 1] ?? "";
|
|
87
|
+
if (arg === "--company" || arg === "--company-slug")
|
|
88
|
+
company = value;
|
|
89
|
+
else if (arg === "--project")
|
|
90
|
+
project = value;
|
|
91
|
+
else if (arg === "--task")
|
|
92
|
+
task = value;
|
|
93
|
+
else if (arg === "--session" || arg === "--session-id")
|
|
94
|
+
sid = value;
|
|
95
|
+
else
|
|
96
|
+
root = value;
|
|
97
|
+
i += 2;
|
|
98
|
+
}
|
|
99
|
+
else if (arg === "--no-reconcile") {
|
|
100
|
+
noReconcile = true;
|
|
101
|
+
i += 1;
|
|
102
|
+
}
|
|
103
|
+
else if (arg === "-h" || arg === "--help")
|
|
104
|
+
return { code: 0, stdout: WORK_MESH_BIND_TRUSTED_HELP.replace(/^# ?/gm, ""), stderr: "" };
|
|
105
|
+
else
|
|
106
|
+
i += 1;
|
|
107
|
+
}
|
|
108
|
+
if (!sid) {
|
|
109
|
+
sid = resolveWorkMeshBindSessionId(root, deps.env, deps.fs);
|
|
110
|
+
if (!sid) {
|
|
111
|
+
try {
|
|
112
|
+
sid = deps.fs.readFileSync(path.join(root, "workspace/sessions/.current"), "utf8").replace(/\s/g, "");
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
sid = "";
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
const childEnv = { ...deps.env, HQ_ROOT: root };
|
|
120
|
+
const pin = sid ? ["--session-id", sid] : [];
|
|
121
|
+
let stderr = "";
|
|
122
|
+
for (const [key, value] of [["company_slug", company], ["project", project], ["task", task]]) {
|
|
123
|
+
if (!value)
|
|
124
|
+
continue;
|
|
125
|
+
const result = deps.runHqSession(root, [...pin, "set", key, value], deps.env);
|
|
126
|
+
output.push(result.stdout);
|
|
127
|
+
stderr += result.stderr;
|
|
128
|
+
if (result.code !== 0)
|
|
129
|
+
return { code: result.code, stdout: output.join(""), stderr };
|
|
130
|
+
}
|
|
131
|
+
if (noReconcile || !sid)
|
|
132
|
+
return { code: 0, stdout: output.join(""), stderr };
|
|
133
|
+
let harness = deps.env.HQ_HARNESS || deps.env.HQ_WORK_MESH_HARNESS || "claude-code";
|
|
134
|
+
if (["claude", "Claude", "ClaudeCode"].includes(harness))
|
|
135
|
+
harness = "claude-code";
|
|
136
|
+
else if (harness === "Codex")
|
|
137
|
+
harness = "codex";
|
|
138
|
+
else if (harness === "Grok")
|
|
139
|
+
harness = "grok";
|
|
140
|
+
const adapter = deps.env.HQ_ADAPTER_CONTRACT_VERSION || "1.0.0";
|
|
141
|
+
const clientOperationId = deps.ulid(deps.now());
|
|
142
|
+
const observation = {
|
|
143
|
+
contractVersion: 1,
|
|
144
|
+
clientOperationId,
|
|
145
|
+
identity: { sessionId: sid, harness, adapterVersion: adapter },
|
|
146
|
+
cwd: deps.env.PWD ?? "",
|
|
147
|
+
hqRoot: root,
|
|
148
|
+
};
|
|
149
|
+
if (company || project || task) {
|
|
150
|
+
const trusted = {};
|
|
151
|
+
if (company)
|
|
152
|
+
trusted.companySlug = company;
|
|
153
|
+
if (project)
|
|
154
|
+
trusted.project = project;
|
|
155
|
+
if (task)
|
|
156
|
+
trusted.task = task;
|
|
157
|
+
observation.trustedContext = trusted;
|
|
158
|
+
}
|
|
159
|
+
const directory = path.join(deps.tmpDir, "hq-work-mesh-obs");
|
|
160
|
+
try {
|
|
161
|
+
deps.fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
162
|
+
deps.fs.chmodSync(directory, 0o700);
|
|
163
|
+
}
|
|
164
|
+
catch { /* legacy mkdir/chmod are best effort */ }
|
|
165
|
+
const observationFile = path.join(directory, `${sid}.bind.${deps.pid}.json`);
|
|
166
|
+
try {
|
|
167
|
+
deps.fs.writeFileSync(observationFile, `${workMeshObservationJson(observation)}\n`);
|
|
168
|
+
}
|
|
169
|
+
catch { /* legacy printf redirection failure is non-fatal */ }
|
|
170
|
+
if (deps.env.HQ_WORK_MESH_RECONCILE_LOG) {
|
|
171
|
+
try {
|
|
172
|
+
deps.fs.appendFileSync(deps.env.HQ_WORK_MESH_RECONCILE_LOG, `reconcile-trusted ${observationFile}\n`);
|
|
173
|
+
}
|
|
174
|
+
catch { /* legacy log write is best effort */ }
|
|
175
|
+
}
|
|
176
|
+
if (deps.env.HQ_WORK_MESH_RECONCILE_STUB === "1")
|
|
177
|
+
return { code: 0, stdout: output.join(""), stderr };
|
|
178
|
+
if (hasExecutable(deps, "hq") && deps.fs.existsSync(observationFile)) {
|
|
179
|
+
deps.spawnHq(["mesh", "context", "reconcile", "--observation-file", observationFile, "--machine"], childEnv);
|
|
180
|
+
}
|
|
181
|
+
return { code: 0, stdout: output.join(""), stderr };
|
|
182
|
+
}
|
|
183
|
+
export function registerCoreWorkMeshBindTrustedNative(core) {
|
|
184
|
+
registerNativeUtility(core, {
|
|
185
|
+
name: "work-mesh-bind-trusted", asset: WORK_MESH_BIND_TRUSTED_ASSET, root: "live", summary: "Bind trusted session context and reconcile it with Work Mesh",
|
|
186
|
+
resolveRoot: (_cwd, hqRoot) => resolveLiveRoot({ hqRoot }),
|
|
187
|
+
run: (args, context) => runWorkMeshBindTrusted(args, createWorkMeshBindDeps(context.root, context.cwd, context.env, context.now)),
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
//# sourceMappingURL=core-work-mesh-bind-trusted-native.js.map
|
package/dist/commands/core.js
CHANGED
|
@@ -40,9 +40,11 @@ import { registerCoreJobsNativeCommand } from "./core-job-native.js";
|
|
|
40
40
|
import { registerCoreResolveCompanyNativeCommand } from "./core-resolve-company-command.js";
|
|
41
41
|
import { registerCoreCheckpointGateSettingCommand } from "./core-checkpoint-gate-setting.js";
|
|
42
42
|
import { registerCoreAuditLogNative } from "./core-audit-log-native.js";
|
|
43
|
+
import { registerCoreDmBindNative } from "./core-dm-bind-native.js";
|
|
43
44
|
import { registerCoreGitPackExtensionCheckNative } from "./core-git-pack-extension-check-native.js";
|
|
44
45
|
import { registerCorePinNative } from "./core-pin-native.js";
|
|
45
46
|
import { registerCoreRefreshVaultAccessNative } from "./core-refresh-vault-access-native.js";
|
|
47
|
+
import { registerCoreWorkMeshBindTrustedNative } from "./core-work-mesh-bind-trusted-native.js";
|
|
46
48
|
import { resolveLiveRoot } from "../utils/hq-roots.js";
|
|
47
49
|
import { interpreterFor, runBundledScript } from "../utils/run-bundled-script.js";
|
|
48
50
|
import { CompanyIndexUserError, renderIndexTarget } from "../lib/index-render/index.js";
|
|
@@ -227,6 +229,8 @@ export const SCAFFOLD_ONLY_ASSETS = [
|
|
|
227
229
|
{ asset: "core/scripts/hq-job-run.sh", root: "live", summary: "Scheduled jobs runner used by generated systemd units" },
|
|
228
230
|
{ asset: "core/scripts/hq-job-notify.sh", root: "live", summary: "Scheduled jobs failure notification helper" },
|
|
229
231
|
{ asset: "core/scripts/hq-job-remediate.sh", root: "live", summary: "Scheduled jobs bounded remediation helper" },
|
|
232
|
+
{ asset: "core/scripts/hq-dm-bind.sh", root: "live", summary: "Rollback shell for native dm-bind" },
|
|
233
|
+
{ asset: "core/scripts/work-mesh-live-bind-trusted.sh", root: "live", summary: "Rollback shell for native work-mesh-bind-trusted" },
|
|
230
234
|
// Shared shell libs sourced by the two relocated scripts (and hooks). Never
|
|
231
235
|
// dispatched on their own (so no `hq core` command), but they MUST ship in the
|
|
232
236
|
// tarball, or the bundled hq-session / checkpoint-stop-gate die at `source`
|
|
@@ -372,6 +376,8 @@ export function registerCoreCommands(program) {
|
|
|
372
376
|
registerCoreGitPackExtensionCheckNative(core);
|
|
373
377
|
registerCorePinNative(core);
|
|
374
378
|
registerCoreRefreshVaultAccessNative(core);
|
|
379
|
+
registerCoreDmBindNative(core);
|
|
380
|
+
registerCoreWorkMeshBindTrustedNative(core);
|
|
375
381
|
// Wave 2 handoff/worktree commands retain their byte-identical shell assets
|
|
376
382
|
// as a default-off rollback path for one release. Keep the gate helper small
|
|
377
383
|
// so sibling Wave 2 work can reuse it without editing this command table.
|