@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.
@@ -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.stderr)
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
@@ -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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.309.1",
3
+ "version": "5.310.0",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {