@indigoai-us/hq-cli 5.345.170 → 5.345.171

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.
Files changed (111) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/assets/scaffold/core/scripts/hq-delegate-send.sh +1 -1
  3. package/dist/command-catalog.generated.d.ts +132 -5
  4. package/dist/command-catalog.generated.js +164 -6
  5. package/dist/commands/access.d.ts +32 -1
  6. package/dist/commands/access.js +112 -3
  7. package/dist/commands/agent-kit.d.ts +1 -1
  8. package/dist/commands/agent-kit.js +3 -1
  9. package/dist/commands/agents.js +2 -1
  10. package/dist/commands/bot.d.ts +3 -0
  11. package/dist/commands/bot.js +137 -14
  12. package/dist/commands/cloud.d.ts +58 -1
  13. package/dist/commands/cloud.js +219 -5
  14. package/dist/commands/core-company-index-scope-flag.d.ts +8 -1
  15. package/dist/commands/core-company-index-scope-flag.js +9 -2
  16. package/dist/commands/core-delegate-w1b.js +5 -2
  17. package/dist/commands/core-dm-bind-native.js +19 -4
  18. package/dist/commands/core-rebuild-index-fast.js +24 -6
  19. package/dist/commands/core.js +18 -6
  20. package/dist/commands/dm.d.ts +17 -3
  21. package/dist/commands/dm.js +121 -26
  22. package/dist/commands/integrations-api.d.ts +4 -0
  23. package/dist/commands/integrations-api.js +18 -2
  24. package/dist/commands/integrations-connect.d.ts +10 -3
  25. package/dist/commands/integrations-connect.js +70 -6
  26. package/dist/commands/integrations-core.d.ts +23 -0
  27. package/dist/commands/integrations-core.js +100 -0
  28. package/dist/commands/integrations-manage.js +8 -1
  29. package/dist/commands/integrations.js +49 -5
  30. package/dist/commands/lanes-loop.d.ts +20 -0
  31. package/dist/commands/lanes-loop.js +69 -11
  32. package/dist/commands/lanes.js +9 -1
  33. package/dist/commands/rescue.d.ts +9 -0
  34. package/dist/commands/rescue.js +31 -0
  35. package/dist/commands/run.js +115 -40
  36. package/dist/commands/scan.d.ts +5 -1
  37. package/dist/commands/scan.js +16 -4
  38. package/dist/commands/secrets.js +50 -9
  39. package/dist/hq-cloud-sync-runner/bin/sync-runner.js +2367 -2109
  40. package/dist/hq-cloud-sync-runner/package.json +1 -1
  41. package/dist/lib/agent-kit/inbox-state.d.ts +17 -0
  42. package/dist/lib/agent-kit/inbox-state.js +41 -20
  43. package/dist/lib/agent-kit/kit-config.d.ts +2 -0
  44. package/dist/lib/agent-kit/kit-config.js +6 -0
  45. package/dist/lib/agent-kit/mcp/tools.js +12 -5
  46. package/dist/lib/agent-kit/run/heartbeat.d.ts +6 -3
  47. package/dist/lib/agent-kit/run/heartbeat.js +6 -3
  48. package/dist/lib/agent-kit/run/inbox.d.ts +8 -3
  49. package/dist/lib/agent-kit/run/inbox.js +42 -10
  50. package/dist/lib/agent-kit/run/request-timeout.d.ts +26 -0
  51. package/dist/lib/agent-kit/run/request-timeout.js +51 -0
  52. package/dist/lib/bot/api.d.ts +1 -1
  53. package/dist/lib/bot/config.d.ts +10 -0
  54. package/dist/lib/bot/config.js +17 -1
  55. package/dist/lib/bot/fleet-local.d.ts +127 -0
  56. package/dist/lib/bot/fleet-local.js +638 -0
  57. package/dist/lib/bot/index.d.ts +1 -0
  58. package/dist/lib/bot/index.js +1 -0
  59. package/dist/lib/bot/local-config.d.ts +3 -2
  60. package/dist/lib/bot/local-config.js +5 -2
  61. package/dist/lib/bot/promote.d.ts +10 -0
  62. package/dist/lib/bot/promote.js +3 -2
  63. package/dist/lib/bot/runtime/index.js +8 -4
  64. package/dist/lib/bot/runtime/scratch-dir.d.ts +24 -0
  65. package/dist/lib/bot/runtime/scratch-dir.js +41 -0
  66. package/dist/lib/caller-principal.d.ts +4 -0
  67. package/dist/lib/caller-principal.js +40 -0
  68. package/dist/lib/capacity/collectors/grok.js +8 -2
  69. package/dist/lib/cli-flag-keys.d.ts +2 -0
  70. package/dist/lib/cli-flag-keys.js +2 -0
  71. package/dist/lib/daemon/catalog.js +4 -1
  72. package/dist/lib/daemon/sync-core-canary.d.ts +3 -0
  73. package/dist/lib/daemon/sync-core-canary.js +8 -3
  74. package/dist/lib/doctor/checks/release-links.d.ts +62 -1
  75. package/dist/lib/doctor/checks/release-links.js +188 -2
  76. package/dist/lib/doctor/fix/apply.js +54 -0
  77. package/dist/lib/doctor/fix/remediation.d.ts +8 -1
  78. package/dist/lib/doctor/fix/remediation.js +22 -0
  79. package/dist/lib/flag-registry-worker-source.generated.d.ts +1 -1
  80. package/dist/lib/flag-registry-worker-source.generated.js +1 -1
  81. package/dist/lib/index-render/index.d.ts +9 -0
  82. package/dist/lib/index-render/index.js +65 -5
  83. package/dist/lib/index-render/session-scope.d.ts +69 -0
  84. package/dist/lib/index-render/session-scope.js +279 -0
  85. package/dist/lib/lanes/admission.js +7 -20
  86. package/dist/lib/lanes/reconcile.js +13 -15
  87. package/dist/lib/lanes/stale-lock.d.ts +50 -0
  88. package/dist/lib/lanes/stale-lock.js +105 -0
  89. package/dist/lib/lanes/store.js +11 -76
  90. package/dist/lib/notify-recipient.d.ts +10 -4
  91. package/dist/lib/notify-recipient.js +16 -4
  92. package/dist/lib/secret-input-link.d.ts +42 -0
  93. package/dist/lib/secret-input-link.js +93 -0
  94. package/dist/lib/secret-scan/range.d.ts +57 -0
  95. package/dist/lib/secret-scan/range.js +266 -0
  96. package/dist/lib/secret-scan/repo-config.d.ts +18 -3
  97. package/dist/lib/secret-scan/repo-config.js +135 -2
  98. package/dist/run/script-bound-schema.d.ts +27 -0
  99. package/dist/run/script-bound-schema.js +97 -0
  100. package/dist/secrets-env-action.d.ts +2 -0
  101. package/dist/secrets-env-action.js +7 -2
  102. package/dist/utils/cognito-session.d.ts +3 -30
  103. package/dist/utils/cognito-session.js +2 -100
  104. package/dist/utils/machine-principal-state.d.ts +38 -0
  105. package/dist/utils/machine-principal-state.js +110 -0
  106. package/dist/utils/safe-link.js +9 -0
  107. package/dist/utils/script-bound-exec.d.ts +158 -0
  108. package/dist/utils/script-bound-exec.js +568 -0
  109. package/dist/utils/sync-watchdog.d.ts +92 -0
  110. package/dist/utils/sync-watchdog.js +250 -0
  111. package/package.json +8 -8
@@ -0,0 +1,638 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import * as crypto from "node:crypto";
3
+ import * as fs from "node:fs";
4
+ import * as os from "node:os";
5
+ import * as path from "node:path";
6
+ import { HQ_FLEET_RUNTIME_ENGINE_ID, } from "./config.js";
7
+ export const HQ_FLEET_LOCAL_RELEASE = {
8
+ repository: "indigoai-us/hq-fleet-local",
9
+ tag: "v0.4.2",
10
+ version: "0.4.2",
11
+ artifact: "hq-fleet-local-0.4.2.zip",
12
+ artifactSha256: "61af88c3e783f5cc6bc1bf99920e981680f67d3485fae6edc52cdfee61ce2108",
13
+ runtimeArtifact: "hq-fleet-runtime-abccac13bd9e.tar",
14
+ runtimeArtifactSha256: "29bcfe718ad7fbd3636afd00623772f2a25bc84c4f6f10d8d57f38e549d528d5",
15
+ manifestSha256: "d55a4b84d08659991f8bf2dba4b8590bd7874bf3adf4dad6bc1e08eac5462884",
16
+ signatureSha256: "31f0d27890f54956f3684e9401d04ed49b9f57fcb6da0440c25f5a31c5db1082",
17
+ runtimeCommit: "abccac13bd9ea593b24c8cd9c0ba28d5c6050cc9",
18
+ };
19
+ export const HQ_FLEET_LOCAL_PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----
20
+ MCowBQYDK2VwAyEAOOL7NRKQgjMYI5cWxLY+HGppXf2AtwpQbCg5v+nBksM=
21
+ -----END PUBLIC KEY-----
22
+ `;
23
+ function digest(value) {
24
+ return crypto.createHash("sha256").update(value).digest("hex");
25
+ }
26
+ function expectedError(message) {
27
+ return Object.assign(new Error(message), { expected: true });
28
+ }
29
+ function engineMetadata(release) {
30
+ return {
31
+ id: HQ_FLEET_RUNTIME_ENGINE_ID,
32
+ distribution: "hq-fleet-local",
33
+ version: release.version,
34
+ artifactSha256: release.artifactSha256,
35
+ runtimeCommit: release.runtimeCommit,
36
+ };
37
+ }
38
+ export function fleetHome(home = os.homedir(), env = process.env) {
39
+ return env.HQ_FLEET_HOME?.trim() || path.join(home, ".hq-fleet");
40
+ }
41
+ export function verifyFleetLocalRelease(files, release = HQ_FLEET_LOCAL_RELEASE, publicKey = HQ_FLEET_LOCAL_PUBLIC_KEY) {
42
+ if (digest(files.archive) !== release.artifactSha256) {
43
+ throw expectedError(`HQ Fleet Local ${release.version} archive hash did not match the release pin.`);
44
+ }
45
+ if (digest(files.runtimeArchive) !== release.runtimeArtifactSha256) {
46
+ throw expectedError(`HQ Fleet Local ${release.version} runtime archive hash did not match the release pin.`);
47
+ }
48
+ if (digest(files.manifest) !== release.manifestSha256) {
49
+ throw expectedError(`HQ Fleet Local ${release.version} manifest hash did not match the release pin.`);
50
+ }
51
+ if (digest(files.signature) !== release.signatureSha256) {
52
+ throw expectedError(`HQ Fleet Local ${release.version} signature hash did not match the release pin.`);
53
+ }
54
+ let manifest;
55
+ let signature;
56
+ try {
57
+ manifest = JSON.parse(files.manifest.toString("utf8"));
58
+ signature = JSON.parse(files.signature.toString("utf8"));
59
+ }
60
+ catch (error) {
61
+ throw expectedError(`HQ Fleet Local ${release.version} release metadata is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
62
+ }
63
+ const distribution = manifest.distribution;
64
+ const runtime = manifest.runtime;
65
+ if (distribution?.name !== "hq-fleet-local" ||
66
+ distribution.version !== release.version ||
67
+ runtime?.commit !== release.runtimeCommit) {
68
+ throw expectedError(`HQ Fleet Local ${release.version} manifest does not match the pinned distribution.`);
69
+ }
70
+ if (signature.algorithm !== "ed25519" || typeof signature.signature !== "string") {
71
+ throw expectedError(`HQ Fleet Local ${release.version} signature record is invalid.`);
72
+ }
73
+ const valid = crypto.verify(null, files.manifest, crypto.createPublicKey(publicKey), Buffer.from(signature.signature, "base64"));
74
+ if (!valid) {
75
+ throw expectedError(`HQ Fleet Local ${release.version} manifest signature is invalid.`);
76
+ }
77
+ }
78
+ export function resolveFleetReleaseToken(env = process.env) {
79
+ for (const name of ["HQ_FLEET_RELEASE_GITHUB_TOKEN", "GH_TOKEN", "GITHUB_TOKEN"]) {
80
+ const value = env[name]?.trim();
81
+ if (value)
82
+ return value;
83
+ }
84
+ try {
85
+ const value = execFileSync("gh", ["auth", "token"], {
86
+ encoding: "utf8",
87
+ stdio: ["ignore", "pipe", "ignore"],
88
+ windowsHide: true,
89
+ }).trim();
90
+ if (value)
91
+ return value;
92
+ }
93
+ catch {
94
+ throw expectedError("HQ Fleet Local is a private Indigo preview. Sign in with GitHub CLI or set HQ_FLEET_RELEASE_GITHUB_TOKEN before creating this bot.");
95
+ }
96
+ throw expectedError("GitHub CLI returned an empty token for the private HQ Fleet Local release. Run gh auth login and retry.");
97
+ }
98
+ async function responseBytes(response, label) {
99
+ if (!response.ok) {
100
+ throw expectedError(`Could not download ${label}: GitHub returned HTTP ${response.status}.`);
101
+ }
102
+ return Buffer.from(await response.arrayBuffer());
103
+ }
104
+ export const FLEET_LOCAL_METADATA_DOWNLOAD_TIMEOUT_MS = 30_000;
105
+ export const FLEET_LOCAL_ARCHIVE_DOWNLOAD_TIMEOUT_MS = 5 * 60_000;
106
+ export const FLEET_LOCAL_RUNTIME_DOWNLOAD_TIMEOUT_MS = 20 * 60_000;
107
+ async function downloadFleetLocalRelease(release, fetcher, token) {
108
+ const headers = {
109
+ Accept: "application/vnd.github+json",
110
+ Authorization: `Bearer ${token}`,
111
+ "User-Agent": "hq-cli-fleet-local",
112
+ "X-GitHub-Api-Version": "2022-11-28",
113
+ };
114
+ const releaseUrl = `https://api.github.com/repos/${release.repository}/releases/tags/${release.tag}`;
115
+ const response = await fetcher(releaseUrl, { headers, signal: AbortSignal.timeout(30_000) });
116
+ if (!response.ok) {
117
+ throw expectedError(`Could not read HQ Fleet Local ${release.tag}: GitHub returned HTTP ${response.status}.`);
118
+ }
119
+ const body = (await response.json());
120
+ const assets = new Map((body.assets ?? [])
121
+ .filter((asset) => !!asset.name && !!asset.url)
122
+ .map((asset) => [asset.name, asset.url]));
123
+ const get = async (name, timeoutMs) => {
124
+ const url = assets.get(name);
125
+ if (!url)
126
+ throw expectedError(`HQ Fleet Local ${release.tag} is missing release asset ${name}.`);
127
+ if (new URL(url).origin !== "https://api.github.com") {
128
+ throw expectedError(`HQ Fleet Local ${release.tag} returned an untrusted asset URL for ${name}.`);
129
+ }
130
+ return responseBytes(await fetcher(url, {
131
+ headers: { ...headers, Accept: "application/octet-stream" },
132
+ signal: AbortSignal.timeout(timeoutMs),
133
+ }), name);
134
+ };
135
+ const [archive, runtimeArchive, manifest, signature] = await Promise.all([
136
+ get(release.artifact, FLEET_LOCAL_ARCHIVE_DOWNLOAD_TIMEOUT_MS),
137
+ get(release.runtimeArtifact, FLEET_LOCAL_RUNTIME_DOWNLOAD_TIMEOUT_MS),
138
+ get("release-manifest.json", FLEET_LOCAL_METADATA_DOWNLOAD_TIMEOUT_MS),
139
+ get("release-manifest.sig", FLEET_LOCAL_METADATA_DOWNLOAD_TIMEOUT_MS),
140
+ ]);
141
+ return { archive, runtimeArchive, manifest, signature };
142
+ }
143
+ function installedFiles(dir, release) {
144
+ const archive = path.join(dir, release.artifact);
145
+ const runtimeArchive = path.join(dir, release.runtimeArtifact);
146
+ const manifest = path.join(dir, "release-manifest.json");
147
+ const signature = path.join(dir, "release-manifest.sig");
148
+ if (![archive, runtimeArchive, manifest, signature].every((file) => fs.existsSync(file)))
149
+ return null;
150
+ return {
151
+ archive: fs.readFileSync(archive),
152
+ runtimeArchive: fs.readFileSync(runtimeArchive),
153
+ manifest: fs.readFileSync(manifest),
154
+ signature: fs.readFileSync(signature),
155
+ };
156
+ }
157
+ function installFiles(target, files, release, publicKey) {
158
+ const parent = path.dirname(target);
159
+ fs.mkdirSync(parent, { recursive: true, mode: 0o700 });
160
+ const staging = path.join(parent, `.install-${release.version}-${process.pid}-${crypto.randomUUID()}`);
161
+ const backup = path.join(parent, `.replace-${release.version}-${process.pid}-${crypto.randomUUID()}`);
162
+ fs.mkdirSync(staging, { mode: 0o700 });
163
+ try {
164
+ fs.writeFileSync(path.join(staging, release.artifact), files.archive, { mode: 0o644 });
165
+ fs.writeFileSync(path.join(staging, release.runtimeArtifact), files.runtimeArchive, { mode: 0o644 });
166
+ fs.writeFileSync(path.join(staging, "release-manifest.json"), files.manifest, { mode: 0o644 });
167
+ fs.writeFileSync(path.join(staging, "release-manifest.sig"), files.signature, { mode: 0o644 });
168
+ fs.writeFileSync(path.join(staging, "release-public.pem"), publicKey, { mode: 0o644 });
169
+ fs.writeFileSync(path.join(staging, "install.json"), `${JSON.stringify({ schemaVersion: 1, release, engine: engineMetadata(release) }, null, 2)}\n`, { mode: 0o644 });
170
+ if (fs.existsSync(target))
171
+ fs.renameSync(target, backup);
172
+ try {
173
+ fs.renameSync(staging, target);
174
+ }
175
+ catch (error) {
176
+ if (fs.existsSync(backup) && !fs.existsSync(target))
177
+ fs.renameSync(backup, target);
178
+ throw error;
179
+ }
180
+ if (fs.existsSync(backup))
181
+ fs.rmSync(backup, { recursive: true, force: true });
182
+ }
183
+ finally {
184
+ if (fs.existsSync(staging))
185
+ fs.rmSync(staging, { recursive: true, force: true });
186
+ }
187
+ }
188
+ export async function installPinnedFleetLocalDistribution(deps = {}) {
189
+ const release = deps.release ?? HQ_FLEET_LOCAL_RELEASE;
190
+ const publicKey = deps.publicKey ?? HQ_FLEET_LOCAL_PUBLIC_KEY;
191
+ const root = deps.fleetHome ?? fleetHome();
192
+ const dir = path.join(root, "distributions", "hq-fleet-local", release.version);
193
+ const current = installedFiles(dir, release);
194
+ if (current) {
195
+ verifyFleetLocalRelease(current, release, publicKey);
196
+ }
197
+ else {
198
+ const token = (deps.token ?? resolveFleetReleaseToken)();
199
+ const files = await downloadFleetLocalRelease(release, deps.fetch ?? fetch, token);
200
+ verifyFleetLocalRelease(files, release, publicKey);
201
+ installFiles(dir, files, release, publicKey);
202
+ }
203
+ return {
204
+ release,
205
+ dir,
206
+ archive: path.join(dir, release.artifact),
207
+ runtimeArchive: path.join(dir, release.runtimeArtifact),
208
+ manifest: path.join(dir, "release-manifest.json"),
209
+ signature: path.join(dir, "release-manifest.sig"),
210
+ engine: engineMetadata(release),
211
+ };
212
+ }
213
+ function profileRoot(agentUid, root) {
214
+ return path.join(root, "profiles", agentUid);
215
+ }
216
+ function venvExecutable(runtimeDir, name, platform) {
217
+ return platform === "win32"
218
+ ? path.join(runtimeDir, "Scripts", `${name}.exe`)
219
+ : path.join(runtimeDir, "bin", name);
220
+ }
221
+ function fleetLocalExecutables(agentUid, deps) {
222
+ if (!/^agt_[A-Za-z0-9_-]+$/.test(agentUid)) {
223
+ throw expectedError(`Cannot use an HQ Fleet profile for invalid bot id ${agentUid}.`);
224
+ }
225
+ const root = deps.fleetHome ?? fleetHome();
226
+ const platform = deps.platform ?? process.platform;
227
+ const profileDir = profileRoot(agentUid, root);
228
+ const runtimeDir = path.join(profileDir, "runtime");
229
+ const profile = path.join(profileDir, "profile.json");
230
+ const runtime = venvExecutable(runtimeDir, "hq-fleet", platform);
231
+ const helper = venvExecutable(runtimeDir, "hq-fleet-local", platform);
232
+ if (![profile, runtime, helper].every((item) => fs.existsSync(item))) {
233
+ throw expectedError(`HQ Fleet Local is not fully installed for ${agentUid}.`);
234
+ }
235
+ return { profileDir, profile, runtime, helper };
236
+ }
237
+ function runJson(run, executable, args, options, label) {
238
+ let output;
239
+ try {
240
+ output = run(executable, args, { windowsHide: true, encoding: "utf8", ...options });
241
+ }
242
+ catch {
243
+ throw expectedError(`HQ Fleet Local ${label} failed.`);
244
+ }
245
+ try {
246
+ const value = JSON.parse(Buffer.isBuffer(output) ? output.toString("utf8") : output);
247
+ if (!value || typeof value !== "object" || Array.isArray(value))
248
+ throw new Error("not an object");
249
+ return value;
250
+ }
251
+ catch {
252
+ throw expectedError(`HQ Fleet Local ${label} returned an invalid result.`);
253
+ }
254
+ }
255
+ export function controlFleetLocalBot(agentUid, action, deps = {}) {
256
+ const run = deps.run ?? execFileSync;
257
+ const files = fleetLocalExecutables(agentUid, deps);
258
+ return runJson(run, files.helper, ["service", action, "--profile", files.profileDir, "--runtime", files.runtime], { stdio: ["ignore", "pipe", "pipe"] }, `service ${action}`);
259
+ }
260
+ const providerProfiles = {
261
+ chatgpt: {
262
+ provider: "custom:personal-chatgpt",
263
+ runtimeProvider: "openai-codex",
264
+ model: "gpt-5.6-sol",
265
+ baseUrl: "https://chatgpt.com/backend-api/codex",
266
+ transport: "codex_responses",
267
+ oauth: true,
268
+ },
269
+ codex: {
270
+ provider: "custom:personal-chatgpt",
271
+ runtimeProvider: "openai-codex",
272
+ model: "gpt-5.6-sol",
273
+ baseUrl: "https://chatgpt.com/backend-api/codex",
274
+ transport: "codex_responses",
275
+ oauth: true,
276
+ },
277
+ "openai-codex": {
278
+ provider: "custom:personal-chatgpt",
279
+ runtimeProvider: "openai-codex",
280
+ model: "gpt-5.6-sol",
281
+ baseUrl: "https://chatgpt.com/backend-api/codex",
282
+ transport: "codex_responses",
283
+ oauth: true,
284
+ },
285
+ grok: {
286
+ provider: "custom:personal-grok",
287
+ runtimeProvider: "xai-oauth",
288
+ model: "grok-4",
289
+ baseUrl: "https://api.x.ai/v1",
290
+ transport: "chat_completions",
291
+ oauth: true,
292
+ },
293
+ xai: {
294
+ provider: "custom:personal-grok",
295
+ runtimeProvider: "xai-oauth",
296
+ model: "grok-4",
297
+ baseUrl: "https://api.x.ai/v1",
298
+ transport: "chat_completions",
299
+ oauth: true,
300
+ },
301
+ "xai-oauth": {
302
+ provider: "custom:personal-grok",
303
+ runtimeProvider: "xai-oauth",
304
+ model: "grok-4",
305
+ baseUrl: "https://api.x.ai/v1",
306
+ transport: "chat_completions",
307
+ oauth: true,
308
+ },
309
+ claude: {
310
+ provider: "custom:personal-anthropic",
311
+ runtimeProvider: "anthropic",
312
+ model: "claude-sonnet-4-6",
313
+ baseUrl: "https://api.anthropic.com",
314
+ transport: "anthropic_messages",
315
+ oauth: false,
316
+ },
317
+ anthropic: {
318
+ provider: "custom:personal-anthropic",
319
+ runtimeProvider: "anthropic",
320
+ model: "claude-sonnet-4-6",
321
+ baseUrl: "https://api.anthropic.com",
322
+ transport: "anthropic_messages",
323
+ oauth: false,
324
+ },
325
+ };
326
+ function replaceProfileProvider(profilePath, agentUid, provider) {
327
+ let profile;
328
+ try {
329
+ profile = JSON.parse(fs.readFileSync(profilePath, "utf8"));
330
+ }
331
+ catch {
332
+ throw expectedError(`HQ Fleet Local profile is unreadable for ${agentUid}.`);
333
+ }
334
+ if (profile.schemaVersion !== 1 || profile.botId !== agentUid) {
335
+ throw expectedError(`HQ Fleet Local profile does not belong to ${agentUid}.`);
336
+ }
337
+ profile.provider = provider;
338
+ const credentialProviders = Array.isArray(profile.credentialProviders)
339
+ ? profile.credentialProviders.filter((value) => typeof value === "string")
340
+ : [];
341
+ profile.credentialProviders = [...new Set([...credentialProviders, provider.credentialProvider])];
342
+ const temporary = `${profilePath}.${process.pid}.${crypto.randomUUID()}`;
343
+ fs.writeFileSync(temporary, `${JSON.stringify(profile, null, 2)}\n`, { mode: 0o600 });
344
+ fs.renameSync(temporary, profilePath);
345
+ }
346
+ export function exportFleetLocalMachineCredential(agentUid, deps = {}) {
347
+ const run = deps.run ?? execFileSync;
348
+ const files = fleetLocalExecutables(agentUid, deps);
349
+ const record = runJson(run, files.helper, ["machine-credential", "export", "--profile", files.profileDir, "--bot-id", agentUid], { stdio: ["ignore", "pipe", "pipe"] }, "machine credential export");
350
+ const text = (key) => (typeof record[key] === "string" ? record[key].trim() : "");
351
+ const credential = {
352
+ username: text("username"),
353
+ secret: typeof record.secret === "string" ? record.secret : "",
354
+ entityType: "agent",
355
+ entityUid: text("entityUid"),
356
+ clientId: text("clientId"),
357
+ region: text("region"),
358
+ };
359
+ const entityType = record.entityType ?? "agent";
360
+ if (entityType !== "agent" ||
361
+ credential.entityUid !== agentUid ||
362
+ !credential.username ||
363
+ !credential.secret ||
364
+ !credential.clientId ||
365
+ !credential.region) {
366
+ credential.secret = "";
367
+ throw expectedError(`HQ Fleet Local machine credential does not belong to ${agentUid}.`);
368
+ }
369
+ return credential;
370
+ }
371
+ export function fleetLocalPromotionRuntime(agentUid, deps = {}) {
372
+ return {
373
+ stop: () => {
374
+ controlFleetLocalBot(agentUid, "stop", deps);
375
+ },
376
+ machineCredential: () => exportFleetLocalMachineCredential(agentUid, deps),
377
+ };
378
+ }
379
+ export function removeFleetLocalBot(agentUid, deps = {}) {
380
+ const run = deps.run ?? execFileSync;
381
+ if (!/^agt_[A-Za-z0-9_-]+$/.test(agentUid)) {
382
+ throw expectedError(`Cannot use an HQ Fleet profile for invalid bot id ${agentUid}.`);
383
+ }
384
+ const profileDir = profileRoot(agentUid, deps.fleetHome ?? fleetHome());
385
+ if (!fs.existsSync(profileDir))
386
+ return { removed: false, credentialsRemoved: 0 };
387
+ const platform = deps.platform ?? process.platform;
388
+ const runtimeDir = path.join(profileDir, "runtime");
389
+ const partial = ![
390
+ path.join(profileDir, "profile.json"),
391
+ venvExecutable(runtimeDir, "hq-fleet", platform),
392
+ venvExecutable(runtimeDir, "hq-fleet-local", platform),
393
+ ].every((item) => fs.existsSync(item));
394
+ if (partial) {
395
+ fs.rmSync(profileDir, { recursive: true, force: true });
396
+ return { removed: true, credentialsRemoved: 0 };
397
+ }
398
+ const files = fleetLocalExecutables(agentUid, deps);
399
+ let profile;
400
+ try {
401
+ profile = JSON.parse(fs.readFileSync(files.profile, "utf8"));
402
+ }
403
+ catch {
404
+ throw expectedError(`HQ Fleet Local profile is unreadable for ${agentUid}.`);
405
+ }
406
+ const providers = Array.isArray(profile.credentialProviders)
407
+ ? profile.credentialProviders.filter((value) => typeof value === "string")
408
+ : [];
409
+ runJson(run, files.helper, ["service", "remove", "--profile", files.profileDir, "--runtime", files.runtime], { stdio: ["ignore", "pipe", "pipe"] }, "service removal");
410
+ for (const provider of providers) {
411
+ runJson(run, files.helper, ["auth", "logout", "--profile", files.profileDir, "--bot-id", agentUid, "--provider", provider], { stdio: ["ignore", "pipe", "pipe"] }, `${provider} credential removal`);
412
+ }
413
+ runJson(run, files.helper, ["machine-credential", "delete", "--profile", files.profileDir, "--bot-id", agentUid], { stdio: ["ignore", "pipe", "pipe"] }, "machine credential removal");
414
+ fs.rmSync(files.profileDir, { recursive: true, force: true });
415
+ return { removed: true, credentialsRemoved: providers.length + 1 };
416
+ }
417
+ export function authenticateFleetLocalBot(input, deps = {}) {
418
+ const requested = input.provider.trim().toLowerCase();
419
+ if (!/^[a-z0-9][a-z0-9._:-]*$/.test(requested)) {
420
+ throw expectedError("HQ Fleet provider identifier is invalid.");
421
+ }
422
+ const known = providerProfiles[requested];
423
+ const provider = known ?? {
424
+ provider: `custom:personal-${requested}`,
425
+ runtimeProvider: requested,
426
+ model: input.model?.trim() ?? "",
427
+ baseUrl: input.baseUrl?.trim() ?? "",
428
+ transport: input.transport ?? "chat_completions",
429
+ oauth: false,
430
+ };
431
+ const model = input.model?.trim() || provider.model;
432
+ const baseUrl = input.baseUrl?.trim() || provider.baseUrl;
433
+ const transport = input.transport ?? provider.transport;
434
+ if (!model)
435
+ throw expectedError(`Pass --model for runtime provider ${requested}.`);
436
+ if (!baseUrl)
437
+ throw expectedError(`Pass --base-url for runtime provider ${requested}.`);
438
+ if (!provider.oauth && !input.apiKey) {
439
+ throw expectedError(`Pass --api-key and provide ${provider.runtimeProvider} credentials on stdin.`);
440
+ }
441
+ const run = deps.run ?? execFileSync;
442
+ const files = fleetLocalExecutables(input.agentUid, deps);
443
+ const authArgs = provider.oauth && !input.apiKey
444
+ ? [
445
+ "auth",
446
+ "login",
447
+ "--profile",
448
+ files.profileDir,
449
+ "--bot-id",
450
+ input.agentUid,
451
+ "--provider",
452
+ provider.runtimeProvider,
453
+ ...(input.openBrowser === false ? ["--no-browser"] : []),
454
+ ]
455
+ : [
456
+ "auth",
457
+ "api-key",
458
+ "--profile",
459
+ files.profileDir,
460
+ "--bot-id",
461
+ input.agentUid,
462
+ "--provider",
463
+ provider.runtimeProvider,
464
+ "--base-url",
465
+ baseUrl,
466
+ ];
467
+ const auth = runJson(run, files.helper, authArgs, input.apiKey
468
+ ? { input: input.apiKey, stdio: ["pipe", "pipe", "pipe"] }
469
+ : { stdio: ["inherit", "pipe", "inherit"] }, `${provider.runtimeProvider} authentication`);
470
+ if (auth.authenticated !== true) {
471
+ throw expectedError(`HQ Fleet Local could not authenticate ${provider.runtimeProvider}.`);
472
+ }
473
+ replaceProfileProvider(files.profile, input.agentUid, {
474
+ id: provider.provider,
475
+ model,
476
+ credentialProvider: provider.runtimeProvider,
477
+ baseUrl,
478
+ transport,
479
+ });
480
+ runJson(run, files.helper, ["render-profile", "--spec", files.profile, "--mode", "update"], { stdio: ["ignore", "pipe", "pipe"] }, "profile update");
481
+ runJson(run, files.helper, ["doctor-profile", "--profile", files.profileDir, "--runtime", files.runtime], { stdio: ["ignore", "pipe", "pipe"] }, "profile validation");
482
+ const restarted = controlFleetLocalBot(input.agentUid, "restart", deps).ok === true;
483
+ return {
484
+ provider: requested,
485
+ runtimeProvider: provider.runtimeProvider,
486
+ model,
487
+ authenticated: true,
488
+ restarted,
489
+ };
490
+ }
491
+ export function writeFleetLocalProfile(input, deps = {}) {
492
+ if (!/^agt_[A-Za-z0-9_-]+$/.test(input.agentUid)) {
493
+ throw expectedError(`Cannot create an HQ Fleet profile for invalid bot id ${input.agentUid}.`);
494
+ }
495
+ if (!/^prs_[A-Za-z0-9_-]+$/.test(input.ownerUid)) {
496
+ throw expectedError(`Cannot create an HQ Fleet profile for invalid owner id ${input.ownerUid}.`);
497
+ }
498
+ const root = deps.fleetHome ?? fleetHome();
499
+ const dir = profileRoot(input.agentUid, root);
500
+ const runtimeDir = path.join(dir, "runtime");
501
+ const destination = path.join(dir, "profile.json");
502
+ const temporary = path.join(dir, `.profile-${process.pid}-${crypto.randomUUID()}`);
503
+ const profile = {
504
+ schemaVersion: 1,
505
+ botId: input.agentUid,
506
+ ownerUid: input.ownerUid,
507
+ provider: { id: "custom:personal-chatgpt", model: "gpt-5.6-sol" },
508
+ hqRoot: path.resolve(input.hqRoot),
509
+ profileDir: path.resolve(dir),
510
+ runtime: { commit: input.installation.release.runtimeCommit, path: path.resolve(runtimeDir) },
511
+ paths: {
512
+ pluginSource: path.resolve(path.join(dir, "sources", "distribution", "src", "hq_fleet_local", "plugins", "hqdm")),
513
+ statusFile: path.resolve(path.join(input.botDir, "status.json")),
514
+ logsDir: path.resolve(path.join(input.botDir, "logs")),
515
+ },
516
+ };
517
+ fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
518
+ fs.mkdirSync(profile.paths.logsDir, { recursive: true, mode: 0o700 });
519
+ fs.writeFileSync(temporary, `${JSON.stringify(profile, null, 2)}\n`, { mode: 0o600 });
520
+ fs.renameSync(temporary, destination);
521
+ return destination;
522
+ }
523
+ function pythonCommand(run, platform) {
524
+ const candidates = platform === "win32"
525
+ ? [
526
+ { executable: "py", prefix: ["-3.13"] },
527
+ { executable: "py", prefix: ["-3.12"] },
528
+ { executable: "py", prefix: ["-3.11"] },
529
+ { executable: "python3", prefix: [] },
530
+ ]
531
+ : [
532
+ { executable: "python3.13", prefix: [] },
533
+ { executable: "python3.12", prefix: [] },
534
+ { executable: "python3.11", prefix: [] },
535
+ { executable: "python3", prefix: [] },
536
+ ];
537
+ for (const candidate of candidates) {
538
+ try {
539
+ run(candidate.executable, [
540
+ ...candidate.prefix,
541
+ "-c",
542
+ "import sys; raise SystemExit(0 if (3,11) <= sys.version_info[:2] < (3,14) else 1)",
543
+ ], { stdio: "ignore", windowsHide: true });
544
+ return candidate;
545
+ }
546
+ catch {
547
+ continue;
548
+ }
549
+ }
550
+ throw expectedError("HQ Fleet Runtime needs Python 3.11, 3.12, or 3.13. Install one and retry.");
551
+ }
552
+ function checkedRun(run, executable, args, options, label) {
553
+ try {
554
+ run(executable, args, { windowsHide: true, ...options });
555
+ }
556
+ catch {
557
+ throw expectedError(`HQ Fleet Local ${label} failed.`);
558
+ }
559
+ }
560
+ export function bootstrapFleetLocalBot(input, deps = {}) {
561
+ const run = deps.run ?? execFileSync;
562
+ const platform = deps.platform ?? process.platform;
563
+ const root = deps.fleetHome ?? fleetHome();
564
+ const profileDir = profileRoot(input.agentUid, root);
565
+ const sources = path.join(profileDir, "sources");
566
+ const distributionSource = path.join(sources, "distribution");
567
+ const runtimeSource = path.join(sources, "runtime");
568
+ const runtimeDir = path.join(profileDir, "runtime");
569
+ const marker = path.join(profileDir, "bootstrap.json");
570
+ const python = pythonCommand(run, platform);
571
+ fs.mkdirSync(profileDir, { recursive: true, mode: 0o700 });
572
+ if (!fs.existsSync(marker)) {
573
+ fs.rmSync(sources, { recursive: true, force: true });
574
+ fs.rmSync(runtimeDir, { recursive: true, force: true });
575
+ fs.mkdirSync(distributionSource, { recursive: true, mode: 0o700 });
576
+ fs.mkdirSync(runtimeSource, { recursive: true, mode: 0o700 });
577
+ checkedRun(run, python.executable, [
578
+ ...python.prefix,
579
+ "-m",
580
+ "zipfile",
581
+ "-e",
582
+ input.installation.archive,
583
+ distributionSource,
584
+ ], { stdio: "ignore" }, "distribution extraction");
585
+ checkedRun(run, python.executable, [
586
+ ...python.prefix,
587
+ "-m",
588
+ "tarfile",
589
+ "-e",
590
+ input.installation.runtimeArchive,
591
+ runtimeSource,
592
+ ], { stdio: "ignore" }, "runtime extraction");
593
+ checkedRun(run, python.executable, [
594
+ ...python.prefix,
595
+ "-m",
596
+ "venv",
597
+ runtimeDir,
598
+ ], { stdio: "ignore" }, "environment creation");
599
+ const runtimePython = venvExecutable(runtimeDir, "python", platform);
600
+ checkedRun(run, runtimePython, [
601
+ "-m",
602
+ "pip",
603
+ "install",
604
+ "--disable-pip-version-check",
605
+ "--editable",
606
+ `${runtimeSource}[all,messaging,slack,anthropic]`,
607
+ distributionSource,
608
+ ], { stdio: ["ignore", 2, 2] }, "package installation");
609
+ fs.writeFileSync(marker, `${JSON.stringify({ schemaVersion: 1, engine: input.installation.engine }, null, 2)}\n`, { mode: 0o600 });
610
+ }
611
+ const profile = writeFleetLocalProfile(input, { fleetHome: root, platform });
612
+ const helper = venvExecutable(runtimeDir, "hq-fleet-local", platform);
613
+ const runtime = venvExecutable(runtimeDir, "hq-fleet", platform);
614
+ if (!fs.existsSync(helper) || !fs.existsSync(runtime)) {
615
+ throw expectedError("HQ Fleet Local installed without its runtime executables.");
616
+ }
617
+ checkedRun(run, helper, ["render-profile", "--spec", profile, "--mode", "create"], { stdio: "ignore" }, "profile rendering");
618
+ const machineValue = fs.readFileSync(input.machineCredentials, "utf8").trim();
619
+ if (!machineValue) {
620
+ throw expectedError(`HQ Fleet Local machine credential file is empty: ${input.machineCredentials}`);
621
+ }
622
+ checkedRun(run, helper, [
623
+ "machine-credential",
624
+ "import",
625
+ "--profile",
626
+ profileDir,
627
+ "--bot-id",
628
+ input.agentUid,
629
+ ], { input: machineValue, stdio: ["pipe", "ignore", "pipe"] }, "machine credential import");
630
+ fs.rmSync(input.machineCredentials, { force: true });
631
+ checkedRun(run, helper, ["doctor-profile", "--profile", profileDir, "--runtime", runtime], { stdio: "ignore" }, "profile validation");
632
+ const start = deps.start !== false;
633
+ if (start) {
634
+ checkedRun(run, helper, ["service", "install", "--profile", profileDir, "--runtime", runtime], { stdio: ["ignore", 2, 2] }, "service installation");
635
+ }
636
+ return { profile, runtime, helper, running: start };
637
+ }
638
+ //# sourceMappingURL=fleet-local.js.map
@@ -25,4 +25,5 @@ export * from "./pack-link.js";
25
25
  export * from "./lock.js";
26
26
  export * from "./display-name.js";
27
27
  export * from "./decisions.js";
28
+ export * from "./fleet-local.js";
28
29
  //# sourceMappingURL=index.d.ts.map
@@ -25,4 +25,5 @@ export * from "./pack-link.js";
25
25
  export * from "./lock.js";
26
26
  export * from "./display-name.js";
27
27
  export * from "./decisions.js";
28
+ export * from "./fleet-local.js";
28
29
  //# sourceMappingURL=index.js.map
@@ -9,7 +9,7 @@
9
9
  * checks the result before it leaves this machine: a bot's machine credentials
10
10
  * live only in ~/.hq/bots/<name>/machine-creds.json and never travel here.
11
11
  */
12
- import { type BotConfig, type BotKind, type BotMemoryMode, type BotRuntimeId } from "./config.js";
12
+ import { type BotConfig, type BotEngineMetadata, type BotKind, type BotMemoryMode, type BotRuntimeId } from "./config.js";
13
13
  /**
14
14
  * Everything needed to rebuild a bot here, minus anything secret. Written on
15
15
  * create, returned by `GET /v1/agents/mine?local=1` and by the credential
@@ -18,6 +18,7 @@ import { type BotConfig, type BotKind, type BotMemoryMode, type BotRuntimeId } f
18
18
  export interface BotLocalConfig {
19
19
  v: 1;
20
20
  runtime?: BotRuntimeId;
21
+ engine?: BotEngineMetadata;
21
22
  /** "scaffold" (its own personal/workers/<name>) or "worker" (an HQ worker). */
22
23
  workerSource?: "scaffold" | "worker";
23
24
  workerId?: string;
@@ -37,7 +38,7 @@ export interface BotLocalConfig {
37
38
  /** Walks a value and throws when any key looks like it could carry a secret. */
38
39
  export declare function assertNoSecretKeys(value: unknown, path?: string): void;
39
40
  /** The bot.json fields a localConfig is built from — nothing else is read. */
40
- export type BotLocalConfigSource = Pick<BotConfig, "runtime" | "workerSource" | "workerId" | "companySlug" | "model" | "effort" | "kind" | "companies" | "autoApprove" | "memoryDir" | "displayName" | "intro" | "kickoff">;
41
+ export type BotLocalConfigSource = Pick<BotConfig, "runtime" | "engine" | "workerSource" | "workerId" | "companySlug" | "model" | "effort" | "kind" | "companies" | "autoApprove" | "memoryDir" | "displayName" | "intro" | "kickoff">;
41
42
  /**
42
43
  * The non-secret settings of a bot, for the cloud to keep. Absolute paths
43
44
  * (hqRoot, memoryDir, workerDir) are deliberately left out: they belong to the