@indigoai-us/hq-cli 5.115.5 → 5.116.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.
Files changed (62) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/dist/command-catalog.generated.d.ts +162 -2
  3. package/dist/command-catalog.generated.js +205 -2
  4. package/dist/command-registration-plan.d.ts +6 -0
  5. package/dist/command-registration-plan.js +1 -0
  6. package/dist/commands/agent-enroll.d.ts +105 -0
  7. package/dist/commands/agent-enroll.js +273 -0
  8. package/dist/commands/agent-kit.d.ts +53 -0
  9. package/dist/commands/agent-kit.js +260 -0
  10. package/dist/commands/agent-mcp.d.ts +22 -0
  11. package/dist/commands/agent-mcp.js +104 -0
  12. package/dist/commands/agent-probe.d.ts +71 -0
  13. package/dist/commands/agent-probe.js +294 -0
  14. package/dist/commands/agent.d.ts +12 -0
  15. package/dist/commands/agent.js +23 -0
  16. package/dist/commands/agents.d.ts +27 -0
  17. package/dist/commands/agents.js +280 -6
  18. package/dist/commands/secrets.js +17 -5
  19. package/dist/lib/agent-kit/creds.d.ts +60 -0
  20. package/dist/lib/agent-kit/creds.js +123 -0
  21. package/dist/lib/agent-kit/kit-config.d.ts +29 -0
  22. package/dist/lib/agent-kit/kit-config.js +54 -0
  23. package/dist/lib/agent-kit/log.d.ts +17 -0
  24. package/dist/lib/agent-kit/log.js +46 -0
  25. package/dist/lib/agent-kit/mcp/jsonrpc.d.ts +84 -0
  26. package/dist/lib/agent-kit/mcp/jsonrpc.js +164 -0
  27. package/dist/lib/agent-kit/mcp/tools.d.ts +45 -0
  28. package/dist/lib/agent-kit/mcp/tools.js +280 -0
  29. package/dist/lib/agent-kit/paths.d.ts +42 -0
  30. package/dist/lib/agent-kit/paths.js +56 -0
  31. package/dist/lib/agent-kit/run/heartbeat.d.ts +52 -0
  32. package/dist/lib/agent-kit/run/heartbeat.js +97 -0
  33. package/dist/lib/agent-kit/run/inbox.d.ts +59 -0
  34. package/dist/lib/agent-kit/run/inbox.js +152 -0
  35. package/dist/lib/agent-kit/run/mesh-listener.d.ts +58 -0
  36. package/dist/lib/agent-kit/run/mesh-listener.js +193 -0
  37. package/dist/lib/agent-kit/run/sync.d.ts +33 -0
  38. package/dist/lib/agent-kit/run/sync.js +58 -0
  39. package/dist/lib/agent-kit/services.d.ts +21 -0
  40. package/dist/lib/agent-kit/services.js +46 -0
  41. package/dist/lib/agent-kit/skills.d.ts +18 -0
  42. package/dist/lib/agent-kit/skills.js +149 -0
  43. package/dist/lib/doctor/checks/sync-health.d.ts +19 -0
  44. package/dist/lib/doctor/checks/sync-health.js +55 -2
  45. package/dist/lib/doctor/fix/apply.d.ts +43 -6
  46. package/dist/lib/doctor/fix/apply.js +116 -18
  47. package/dist/lib/doctor/fix/remediation.d.ts +8 -3
  48. package/dist/lib/doctor/fix/remediation.js +21 -2
  49. package/dist/lib/scan-packages/index.js +158 -1
  50. package/dist/lib/service-manager/index.d.ts +43 -0
  51. package/dist/lib/service-manager/index.js +114 -0
  52. package/dist/lib/service-manager/launchd.d.ts +23 -0
  53. package/dist/lib/service-manager/launchd.js +81 -0
  54. package/dist/lib/service-manager/systemd.d.ts +19 -0
  55. package/dist/lib/service-manager/systemd.js +72 -0
  56. package/dist/lib/service-manager/types.d.ts +32 -0
  57. package/dist/lib/service-manager/types.js +26 -0
  58. package/dist/utils/self-update.js +2 -30
  59. package/dist/utils/update-command-supervisor.cjs +194 -0
  60. package/dist/utils/version-gate.d.ts +18 -0
  61. package/dist/utils/version-gate.js +126 -7
  62. package/package.json +2 -2
@@ -0,0 +1,273 @@
1
+ /**
2
+ * `hq agent enroll <code>` — turn this host into an external HQ agent.
3
+ *
4
+ * Flow (api-contract v1, POST /v1/agents/enroll):
5
+ * 1. refuse if this host already carries an identity (human session file or
6
+ * a machine-creds file) unless --replace
7
+ * 2. generate an Ed25519 key pair at ~/.hq-agent/host-key{,.pub} (0600)
8
+ * 3. POST { code, hostPublicKey, hostInfo } — unauthenticated, one-shot
9
+ * 4. write ~/.hq-agent/machine-creds.json (0600) with runtime "external",
10
+ * hostKeyPath, companySlug and apiBaseUrl
11
+ *
12
+ * The code is single-use and expires 15 minutes after an admin creates it,
13
+ * so every failure names the fix. The human session file is only checked
14
+ * for EXISTENCE — its contents are never read (policy
15
+ * never-extract-stored-session-tokens-to-forge-privileged-calls).
16
+ */
17
+ import chalk from "chalk";
18
+ import * as fs from "node:fs";
19
+ import * as os from "node:os";
20
+ import { CLI_VERSION } from "../cli-version.js";
21
+ import { DEFAULT_VAULT_API_URL, personTokenCacheFile, } from "../utils/cognito-session.js";
22
+ import { HQ_CLIENT_NAME } from "../utils/vault-api.js";
23
+ import { networkTransportErrorCode } from "../utils/network-transport-error.js";
24
+ import { generateHostKeyPair, machineCredsFileExists, readExternalMachineCreds, writeHostKeyPair, writeMachineCreds, } from "../lib/agent-kit/creds.js";
25
+ import { agentKitPaths } from "../lib/agent-kit/paths.js";
26
+ export const ENROLL_PATH = "/v1/agents/enroll";
27
+ /** Canonical code: un-grouped, uppercase (server hashes exactly this). */
28
+ export function normalizeEnrollmentCode(raw) {
29
+ return raw.replace(/[\s-]+/g, "").toUpperCase();
30
+ }
31
+ /** 24 chars of Crockford base32 (no I, L, O, U). */
32
+ export function isWellFormedEnrollmentCode(code) {
33
+ return /^[0-9A-HJKMNP-TV-Z]{24}$/.test(code);
34
+ }
35
+ export function collectHostInfo() {
36
+ return {
37
+ os: `${os.platform()} ${os.release()}`,
38
+ arch: os.arch(),
39
+ hostname: os.hostname(),
40
+ cliVersion: CLI_VERSION,
41
+ };
42
+ }
43
+ /**
44
+ * What identity already lives on this host. Existence checks only: the
45
+ * human token file is never opened.
46
+ */
47
+ export function detectExistingIdentity(paths, env = process.env) {
48
+ const humanFile = personTokenCacheFile(paths.home, env);
49
+ if (fs.existsSync(humanFile))
50
+ return { kind: "human", file: humanFile };
51
+ if (machineCredsFileExists(paths)) {
52
+ const existing = readExternalMachineCreds(paths);
53
+ return {
54
+ kind: "machine",
55
+ file: paths.machineCredsPath,
56
+ ...(existing ? { agentUid: existing.entityUid } : {}),
57
+ };
58
+ }
59
+ return { kind: "none" };
60
+ }
61
+ export function refusalMessage(existing) {
62
+ switch (existing.kind) {
63
+ case "none":
64
+ return null;
65
+ case "human":
66
+ return (`A human HQ session exists at ${existing.file}. An agent identity must not ` +
67
+ `share a host account with a person's login. Run enrollment under a dedicated ` +
68
+ `user (or container), or pass --replace to enroll anyway — the human session ` +
69
+ `file is left untouched and never read.`);
70
+ case "machine":
71
+ return (`This host already has a machine identity at ${existing.file}` +
72
+ `${existing.agentUid ? ` (${existing.agentUid})` : ""}. Re-run with --replace ` +
73
+ `to enroll as a new agent; the old creds and host key will be overwritten.`);
74
+ }
75
+ }
76
+ export class EnrollError extends Error {
77
+ status;
78
+ code;
79
+ constructor(message, status, code) {
80
+ super(message);
81
+ this.status = status;
82
+ this.code = code;
83
+ this.name = "EnrollError";
84
+ }
85
+ }
86
+ /** Map a non-2xx enroll response to an actionable message (contract codes). */
87
+ export function mapEnrollFailure(status, body, headers) {
88
+ const code = typeof body.code === "string" ? body.code : undefined;
89
+ const attemptsRemaining = typeof body.attemptsRemaining === "number" ? body.attemptsRemaining : undefined;
90
+ const attemptsNote = attemptsRemaining !== undefined
91
+ ? ` ${attemptsRemaining} attempt${attemptsRemaining === 1 ? "" : "s"} left before the code is revoked.`
92
+ : "";
93
+ const serverText = typeof body.message === "string"
94
+ ? body.message
95
+ : typeof body.error === "string"
96
+ ? body.error
97
+ : "";
98
+ const suffix = serverText ? ` (${serverText})` : "";
99
+ if (status === 410 || code === "expired") {
100
+ return new EnrollError(`Enrollment code expired — codes last 15 minutes. Ask a company admin for a ` +
101
+ `fresh one: console → Agents → the bot → Regenerate, or \`hq agents rotate <agentUid>\`.${suffix}`, status, code);
102
+ }
103
+ if (status === 409 || code === "already_redeemed") {
104
+ return new EnrollError(`This enrollment code was already used. Each code enrolls exactly one host; ` +
105
+ `ask an admin to rotate the agent (\`hq agents rotate <agentUid>\`) for a new code.${suffix}`, status, code);
106
+ }
107
+ if (status === 404 || code === "invalid_code") {
108
+ return new EnrollError(`Enrollment code not recognised. Check for typos (codes are 24 characters, ` +
109
+ `groups of 4, no I/L/O/U) and that you copied the whole code.${suffix}`, status, code);
110
+ }
111
+ if (status === 423 || code === "revoked") {
112
+ return new EnrollError(`This agent was revoked; its enrollment code no longer works. An admin must ` +
113
+ `create a new external agent or rotate this one.${suffix}`, status, code);
114
+ }
115
+ if (status === 402 || code === "billing_required") {
116
+ return new EnrollError(`External agents are available on paid HQ plans only. A company admin must ` +
117
+ `upgrade the plan (\`hq billing\` or the console billing page) before enrolling.${suffix}`, status, code);
118
+ }
119
+ if (status === 429 || code === "rate_limited") {
120
+ const retryAfter = headers?.retryAfter?.trim();
121
+ const wait = retryAfter && /^\d+$/.test(retryAfter) ? `wait ${retryAfter}s and retry` : "wait a minute and retry";
122
+ return new EnrollError(`Too many enrollment attempts from this host — ${wait}. ` +
123
+ `After 5 failed attempts a code is revoked and must be regenerated.${suffix}`, status, code ?? "rate_limited");
124
+ }
125
+ if (code === "invalid_host_key") {
126
+ return new EnrollError(`The server rejected this host's public key (expected an Ed25519 SPKI PEM). ` +
127
+ `Re-run \`hq agent enroll\` to generate a fresh key; if it keeps failing, upgrade hq-cli.${attemptsNote}${suffix}`, status, code);
128
+ }
129
+ if (code === "invalid_host_info") {
130
+ return new EnrollError(`The server rejected this host's info block (os/arch/hostname/cliVersion). ` +
131
+ `Upgrade hq-cli and retry.${attemptsNote}${suffix}`, status, code);
132
+ }
133
+ return new EnrollError(`Enrollment failed (HTTP ${status}${code ? ` ${code}` : ""})${suffix}`, status, code);
134
+ }
135
+ export function mapEnrollTransportError(err, apiBaseUrl) {
136
+ const code = networkTransportErrorCode(err);
137
+ const detail = err instanceof Error ? err.message : String(err);
138
+ return new EnrollError(`Could not reach ${apiBaseUrl}${code ? ` (${code})` : ""}: ${detail}. Check network ` +
139
+ `access / proxy settings; the HQ_VAULT_API_URL env var overrides the control plane.`);
140
+ }
141
+ function parseEnrollResponse(raw) {
142
+ const r = (raw ?? {});
143
+ const c = (r.credentials ?? {});
144
+ const need = (obj, k) => {
145
+ const v = obj[k];
146
+ if (typeof v !== "string" || v.length === 0) {
147
+ throw new EnrollError(`Enrollment response is missing "${k}"`);
148
+ }
149
+ return v;
150
+ };
151
+ const entityUid = need(c, "entityUid");
152
+ if (!entityUid.startsWith("agt_")) {
153
+ throw new EnrollError(`Enrollment response entityUid is not an agent uid: ${entityUid}`);
154
+ }
155
+ return {
156
+ agentUid: need(r, "agentUid"),
157
+ companyUid: need(r, "companyUid"),
158
+ companySlug: need(r, "companySlug"),
159
+ hostFingerprint: need(r, "hostFingerprint"),
160
+ credentials: {
161
+ username: need(c, "username"),
162
+ secret: need(c, "secret"),
163
+ ...(typeof c.userPoolId === "string" && c.userPoolId ? { userPoolId: c.userPoolId } : {}),
164
+ clientId: need(c, "clientId"),
165
+ region: need(c, "region"),
166
+ entityType: "agent",
167
+ entityUid,
168
+ runtime: "external",
169
+ },
170
+ };
171
+ }
172
+ /** Pure-ish orchestration so tests can drive it without a TTY or network. */
173
+ export async function enrollHost(opts, deps = {}) {
174
+ const env = deps.env ?? process.env;
175
+ const paths = deps.paths ?? agentKitPaths(os.homedir(), env);
176
+ const doFetch = deps.fetch ?? fetch;
177
+ const apiBaseUrl = (opts.apiBaseUrl ?? env.HQ_VAULT_API_URL ?? DEFAULT_VAULT_API_URL).replace(/\/+$/, "");
178
+ const code = normalizeEnrollmentCode(opts.code);
179
+ if (!isWellFormedEnrollmentCode(code)) {
180
+ throw new EnrollError(`Enrollment code must be 24 base32 characters (shown as XXXX-XXXX-XXXX-XXXX-XXXX-XXXX); got ${code.length}.`);
181
+ }
182
+ const existing = detectExistingIdentity(paths, env);
183
+ const refusal = refusalMessage(existing);
184
+ if (refusal && !opts.replace)
185
+ throw new EnrollError(refusal);
186
+ // Key first, then the network call: the public half must exist before the
187
+ // server can bind the code to it, and a failed call leaves a harmless
188
+ // unenrolled key behind (regenerated on the next attempt).
189
+ const pair = (deps.generateKeyPair ?? generateHostKeyPair)();
190
+ writeHostKeyPair(paths, pair);
191
+ let res;
192
+ try {
193
+ res = await doFetch(`${apiBaseUrl}${ENROLL_PATH}`, {
194
+ method: "POST",
195
+ headers: {
196
+ "Content-Type": "application/json",
197
+ "x-hq-client-name": HQ_CLIENT_NAME,
198
+ "x-hq-client-version": CLI_VERSION,
199
+ },
200
+ body: JSON.stringify({
201
+ code,
202
+ hostPublicKey: pair.publicPem,
203
+ hostInfo: deps.hostInfo ?? collectHostInfo(),
204
+ }),
205
+ });
206
+ }
207
+ catch (err) {
208
+ throw mapEnrollTransportError(err, apiBaseUrl);
209
+ }
210
+ const body = (await res.json().catch(() => ({})));
211
+ if (!res.ok) {
212
+ throw mapEnrollFailure(res.status, body, { retryAfter: res.headers.get("retry-after") });
213
+ }
214
+ const parsed = parseEnrollResponse(body);
215
+ if (parsed.hostFingerprint !== pair.fingerprint) {
216
+ throw new EnrollError(`Server bound the code to fingerprint ${parsed.hostFingerprint} but this host's key is ` +
217
+ `${pair.fingerprint}. Refusing to write credentials; ask an admin to rotate the agent.`);
218
+ }
219
+ const creds = {
220
+ ...parsed.credentials,
221
+ hostKeyPath: paths.hostKeyPath,
222
+ companySlug: parsed.companySlug,
223
+ apiBaseUrl,
224
+ };
225
+ writeMachineCreds(paths, creds);
226
+ return {
227
+ agentUid: parsed.agentUid,
228
+ companySlug: parsed.companySlug,
229
+ companyUid: parsed.companyUid,
230
+ hostFingerprint: parsed.hostFingerprint,
231
+ credsPath: paths.machineCredsPath,
232
+ hostKeyPath: paths.hostKeyPath,
233
+ ...(opts.company && opts.company !== parsed.companySlug && opts.company !== parsed.companyUid
234
+ ? { companyMismatch: opts.company }
235
+ : {}),
236
+ };
237
+ }
238
+ export function registerAgentEnrollCommand(agent) {
239
+ agent
240
+ .command("enroll <code>")
241
+ .description("Enroll this host as an external HQ agent using a one-time code")
242
+ .option("--company <slug>", "Expected company slug (warns when the code belongs elsewhere)")
243
+ .option("--replace", "Overwrite an existing machine identity on this host")
244
+ .option("--api-base-url <url>", "hq-pro control plane (default: HQ_VAULT_API_URL or production)")
245
+ .action(async (code, opts) => {
246
+ try {
247
+ const result = await enrollHost({
248
+ code,
249
+ company: opts.company,
250
+ replace: opts.replace,
251
+ apiBaseUrl: opts.apiBaseUrl,
252
+ });
253
+ console.log(chalk.green(`Enrolled as ${result.agentUid} in ${result.companySlug}.`));
254
+ console.log(` host fingerprint: ${result.hostFingerprint}`);
255
+ console.log(` credentials: ${result.credsPath} (0600)`);
256
+ console.log(` host key: ${result.hostKeyPath} (0600)`);
257
+ if (result.companyMismatch) {
258
+ console.log(chalk.yellow(` note: --company ${result.companyMismatch} did not match the code's company ` +
259
+ `(${result.companySlug}); the code's company wins.`));
260
+ }
261
+ console.log("");
262
+ console.log("Next:");
263
+ console.log(" hq whoami # should report the agent identity");
264
+ console.log(" hq agent kit install # sync, work-mesh, inbox and heartbeat services");
265
+ console.log(" hq agent probe # end-to-end check, reported to the console");
266
+ }
267
+ catch (err) {
268
+ console.error(chalk.red(err instanceof Error ? err.message : String(err)));
269
+ process.exit(1);
270
+ }
271
+ });
272
+ }
273
+ //# sourceMappingURL=agent-enroll.js.map
@@ -0,0 +1,53 @@
1
+ /**
2
+ * `hq agent kit install|status|uninstall|run <service>` — the resident
3
+ * services that make an external agent a full team member:
4
+ *
5
+ * sync company vault pull loop → component-sync
6
+ * mesh work-mesh doorbell listener → component-mesh
7
+ * inbox inbox poller (mirror + optional ack) → component-inbox
8
+ * heartbeat 60 s POST /v1/agents/{uid}/heartbeat with the three above
9
+ *
10
+ * `install` renders one user-level unit per service (LaunchAgent on macOS,
11
+ * systemd --user on Linux), each of which just runs
12
+ * `node hq agent kit run <service>`; the Docker image runs the same four
13
+ * commands. Logs: ~/.hq-agent/logs/<service>.log. Skills:
14
+ * ~/.hq-agent/skills/<name>/SKILL.md.
15
+ */
16
+ import { Command } from "commander";
17
+ import { type ExternalMachineCreds } from "../lib/agent-kit/creds.js";
18
+ import { type KitConfig } from "../lib/agent-kit/kit-config.js";
19
+ import { type KitLogger } from "../lib/agent-kit/log.js";
20
+ import { type AgentKitPaths } from "../lib/agent-kit/paths.js";
21
+ import { type KitService } from "../lib/agent-kit/services.js";
22
+ import { type ServiceHostPaths, type ServiceManagerDeps, type ServiceSetResult } from "../lib/service-manager/index.js";
23
+ export declare class KitError extends Error {
24
+ constructor(message: string);
25
+ }
26
+ /** Load the external creds or explain exactly what to run first. */
27
+ export declare function requireExternalCreds(paths: AgentKitPaths): ExternalMachineCreds;
28
+ export declare function ensureKitDirs(paths: AgentKitPaths): void;
29
+ export declare function resolveServiceHost(paths: AgentKitPaths): ServiceHostPaths;
30
+ export interface KitInstallOptions {
31
+ hqRoot?: string;
32
+ inboxAck?: boolean;
33
+ activate?: boolean;
34
+ }
35
+ export interface KitInstallResult {
36
+ config: KitConfig;
37
+ skills: string[];
38
+ services: ServiceSetResult;
39
+ }
40
+ export declare function installKit(paths: AgentKitPaths, opts: KitInstallOptions, host: ServiceHostPaths, deps?: ServiceManagerDeps): KitInstallResult;
41
+ export declare function formatServiceSet(result: ServiceSetResult): string[];
42
+ /** Shared runtime for `kit run <service>`. */
43
+ export interface KitRuntime {
44
+ paths: AgentKitPaths;
45
+ creds: ExternalMachineCreds;
46
+ config: KitConfig;
47
+ getToken: () => Promise<string>;
48
+ log: KitLogger;
49
+ }
50
+ export declare function buildKitRuntime(service: KitService): KitRuntime;
51
+ export declare function runKitService(service: KitService, rt: KitRuntime): Promise<void>;
52
+ export declare function registerAgentKitCommand(agent: Command): void;
53
+ //# sourceMappingURL=agent-kit.d.ts.map
@@ -0,0 +1,260 @@
1
+ /**
2
+ * `hq agent kit install|status|uninstall|run <service>` — the resident
3
+ * services that make an external agent a full team member:
4
+ *
5
+ * sync company vault pull loop → component-sync
6
+ * mesh work-mesh doorbell listener → component-mesh
7
+ * inbox inbox poller (mirror + optional ack) → component-inbox
8
+ * heartbeat 60 s POST /v1/agents/{uid}/heartbeat with the three above
9
+ *
10
+ * `install` renders one user-level unit per service (LaunchAgent on macOS,
11
+ * systemd --user on Linux), each of which just runs
12
+ * `node hq agent kit run <service>`; the Docker image runs the same four
13
+ * commands. Logs: ~/.hq-agent/logs/<service>.log. Skills:
14
+ * ~/.hq-agent/skills/<name>/SKILL.md.
15
+ */
16
+ import chalk from "chalk";
17
+ import * as fs from "node:fs";
18
+ import * as os from "node:os";
19
+ import { CLI_VERSION } from "../cli-version.js";
20
+ import { ensureCognitoToken } from "../utils/cognito-session.js";
21
+ import { readExternalMachineCreds, } from "../lib/agent-kit/creds.js";
22
+ import { defaultKitConfig, readKitConfig, writeKitConfig, } from "../lib/agent-kit/kit-config.js";
23
+ import { createKitLogger } from "../lib/agent-kit/log.js";
24
+ import { agentKitPaths } from "../lib/agent-kit/paths.js";
25
+ import { isKitService, KIT_SERVICES, kitServiceSpecs, } from "../lib/agent-kit/services.js";
26
+ import { writeKitSkills } from "../lib/agent-kit/skills.js";
27
+ import { runHeartbeatLoop } from "../lib/agent-kit/run/heartbeat.js";
28
+ import { runInboxLoop } from "../lib/agent-kit/run/inbox.js";
29
+ import { startMeshListener } from "../lib/agent-kit/run/mesh-listener.js";
30
+ import { runSyncLoop } from "../lib/agent-kit/run/sync.js";
31
+ import { resolveHqBinary, resolveNodeBinary, } from "../lib/mesh/live/daemon/install.js";
32
+ import { installServices, servicesStatus, uninstallServices, } from "../lib/service-manager/index.js";
33
+ export class KitError extends Error {
34
+ constructor(message) {
35
+ super(message);
36
+ this.name = "KitError";
37
+ }
38
+ }
39
+ /** Load the external creds or explain exactly what to run first. */
40
+ export function requireExternalCreds(paths) {
41
+ const creds = readExternalMachineCreds(paths);
42
+ if (!creds) {
43
+ throw new KitError(`No external agent credentials at ${paths.machineCredsPath}. Run ` +
44
+ `\`hq agent enroll <code>\` first (an admin creates the code in the HQ console).`);
45
+ }
46
+ return creds;
47
+ }
48
+ export function ensureKitDirs(paths) {
49
+ for (const d of [paths.agentDir, paths.stateDir, paths.inboxDir, paths.logsDir, paths.skillsDir]) {
50
+ fs.mkdirSync(d, { recursive: true, mode: 0o700 });
51
+ }
52
+ }
53
+ export function resolveServiceHost(paths) {
54
+ return {
55
+ home: paths.home,
56
+ nodeBinary: resolveNodeBinary(),
57
+ hqBinary: resolveHqBinary(),
58
+ };
59
+ }
60
+ export function installKit(paths, opts, host, deps = {}) {
61
+ requireExternalCreds(paths);
62
+ ensureKitDirs(paths);
63
+ const existing = fs.existsSync(paths.kitConfigPath)
64
+ ? readKitConfig(paths, CLI_VERSION)
65
+ : defaultKitConfig(paths, CLI_VERSION);
66
+ const config = {
67
+ ...existing,
68
+ ...(opts.hqRoot ? { hqRoot: opts.hqRoot } : {}),
69
+ ...(opts.inboxAck !== undefined ? { inboxAck: opts.inboxAck } : {}),
70
+ cliVersion: CLI_VERSION,
71
+ };
72
+ fs.mkdirSync(config.hqRoot, { recursive: true, mode: 0o700 });
73
+ writeKitConfig(paths, config);
74
+ const skills = writeKitSkills(paths);
75
+ const services = installServices(kitServiceSpecs(paths), host, {
76
+ ...deps,
77
+ activate: opts.activate ?? deps.activate ?? true,
78
+ });
79
+ return { config, skills, services };
80
+ }
81
+ export function formatServiceSet(result) {
82
+ const lines = [];
83
+ for (const u of result.units) {
84
+ const state = !u.installed
85
+ ? chalk.dim("not installed")
86
+ : u.running === true
87
+ ? chalk.green("running")
88
+ : u.running === false
89
+ ? chalk.yellow(u.error ? `not running — ${u.error}` : "installed (not running)")
90
+ : chalk.dim("installed");
91
+ lines.push(` ${u.name.padEnd(10)} ${state}${u.unitPath ? chalk.dim(` ${u.unitPath}`) : ""}`);
92
+ }
93
+ if (result.manualCommands) {
94
+ lines.push(" No service manager on this platform. Run each service yourself:");
95
+ for (const c of result.manualCommands)
96
+ lines.push(` ${c}`);
97
+ }
98
+ return lines;
99
+ }
100
+ export function buildKitRuntime(service) {
101
+ const paths = agentKitPaths(os.homedir(), process.env);
102
+ const creds = requireExternalCreds(paths);
103
+ ensureKitDirs(paths);
104
+ const config = readKitConfig(paths, CLI_VERSION);
105
+ // Pin every mint in this process (and any child `hq`) to the kit's creds
106
+ // and control plane — the same values the enrolled file carries.
107
+ process.env.HQ_MACHINE_CREDS_FILE = paths.machineCredsPath;
108
+ process.env.HQ_REQUIRE_MACHINE_IDENTITY = "1";
109
+ process.env.HQ_VAULT_API_URL = creds.apiBaseUrl;
110
+ const log = createKitLogger(paths, service, { echo: true });
111
+ const getToken = () => ensureCognitoToken({ tokenSource: "machine", interactive: false });
112
+ return { paths, creds, config, getToken, log };
113
+ }
114
+ export async function runKitService(service, rt) {
115
+ rt.log("info", `${service} starting agent=${rt.creds.entityUid} cli=${CLI_VERSION}`);
116
+ switch (service) {
117
+ case "sync":
118
+ await runSyncLoop({
119
+ paths: rt.paths,
120
+ hqRoot: rt.config.hqRoot,
121
+ intervalMs: rt.config.syncIntervalMs,
122
+ log: rt.log,
123
+ });
124
+ return;
125
+ case "heartbeat":
126
+ await runHeartbeatLoop({
127
+ paths: rt.paths,
128
+ agentUid: rt.creds.entityUid,
129
+ apiBaseUrl: rt.creds.apiBaseUrl,
130
+ intervalMs: rt.config.heartbeatIntervalMs,
131
+ getToken: rt.getToken,
132
+ log: rt.log,
133
+ });
134
+ return;
135
+ case "inbox":
136
+ await runInboxLoop({
137
+ paths: rt.paths,
138
+ agentUid: rt.creds.entityUid,
139
+ apiBaseUrl: rt.creds.apiBaseUrl,
140
+ pollMs: rt.config.inboxPollMs,
141
+ ack: rt.config.inboxAck,
142
+ getToken: rt.getToken,
143
+ log: rt.log,
144
+ });
145
+ return;
146
+ case "mesh": {
147
+ const handle = await startMeshListener({
148
+ paths: rt.paths,
149
+ agentUid: rt.creds.entityUid,
150
+ apiBaseUrl: rt.creds.apiBaseUrl,
151
+ refreshMs: rt.config.meshRefreshMs,
152
+ getToken: rt.getToken,
153
+ log: rt.log,
154
+ });
155
+ const stop = () => {
156
+ void handle.stop().then(() => process.exit(0));
157
+ };
158
+ process.once("SIGTERM", stop);
159
+ process.once("SIGINT", stop);
160
+ await new Promise(() => {
161
+ /* resident until signalled */
162
+ });
163
+ return;
164
+ }
165
+ }
166
+ }
167
+ function fail(err) {
168
+ console.error(chalk.red(err instanceof Error ? err.message : String(err)));
169
+ process.exit(1);
170
+ }
171
+ export function registerAgentKitCommand(agent) {
172
+ const kit = agent
173
+ .command("kit")
174
+ .description("Install, inspect, or run the external-agent background services");
175
+ kit
176
+ .command("install")
177
+ .description("Install sync, work-mesh, inbox and heartbeat services plus the skills directory")
178
+ .option("--hq-root <path>", "Local HQ tree to sync the company vault into (default: ~/.hq-agent/hq)")
179
+ .option("--inbox-ack", "Ack mirrored inbox items on the server (default: leave acking to the bot)")
180
+ .option("--no-activate", "Write unit files but do not load them")
181
+ .option("--json", "Print machine-readable JSON")
182
+ .action((opts) => {
183
+ try {
184
+ const paths = agentKitPaths(os.homedir(), process.env);
185
+ const result = installKit(paths, { hqRoot: opts.hqRoot, inboxAck: opts.inboxAck, activate: opts.activate }, resolveServiceHost(paths));
186
+ if (opts.json) {
187
+ console.log(JSON.stringify({ ok: true, ...result }, null, 2));
188
+ return;
189
+ }
190
+ console.log(chalk.green("HQ agent kit installed."));
191
+ console.log(` hq root: ${result.config.hqRoot}`);
192
+ console.log(` logs: ${paths.logsDir}`);
193
+ console.log(` skills: ${paths.skillsDir} (${result.skills.length} skills)`);
194
+ console.log(" services:");
195
+ for (const line of formatServiceSet(result.services))
196
+ console.log(line);
197
+ console.log("");
198
+ console.log("Next: hq agent probe");
199
+ }
200
+ catch (err) {
201
+ fail(err);
202
+ }
203
+ });
204
+ kit
205
+ .command("status")
206
+ .description("Show whether each kit service is installed and running")
207
+ .option("--json", "Print machine-readable JSON")
208
+ .action((opts) => {
209
+ try {
210
+ const paths = agentKitPaths(os.homedir(), process.env);
211
+ const result = servicesStatus(kitServiceSpecs(paths), resolveServiceHost(paths));
212
+ if (opts.json) {
213
+ console.log(JSON.stringify({ ok: true, ...result }, null, 2));
214
+ return;
215
+ }
216
+ for (const line of formatServiceSet(result))
217
+ console.log(line);
218
+ }
219
+ catch (err) {
220
+ fail(err);
221
+ }
222
+ });
223
+ kit
224
+ .command("uninstall")
225
+ .description("Stop and remove the kit services (credentials and skills are kept)")
226
+ .option("--json", "Print machine-readable JSON")
227
+ .action((opts) => {
228
+ try {
229
+ const paths = agentKitPaths(os.homedir(), process.env);
230
+ const result = uninstallServices(kitServiceSpecs(paths), resolveServiceHost(paths));
231
+ if (opts.json) {
232
+ console.log(JSON.stringify({ ok: true, ...result }, null, 2));
233
+ return;
234
+ }
235
+ console.log(chalk.green("HQ agent kit services removed."));
236
+ for (const line of formatServiceSet(result))
237
+ console.log(line);
238
+ }
239
+ catch (err) {
240
+ fail(err);
241
+ }
242
+ });
243
+ kit
244
+ .command("run <service>")
245
+ .description(`Run one kit service in the foreground (${KIT_SERVICES.join("|")})`)
246
+ .action(async (service) => {
247
+ if (!isKitService(service)) {
248
+ console.error(chalk.red(`Unknown kit service "${service}". Expected one of: ${KIT_SERVICES.join(", ")}`));
249
+ process.exit(1);
250
+ }
251
+ try {
252
+ const rt = buildKitRuntime(service);
253
+ await runKitService(service, rt);
254
+ }
255
+ catch (err) {
256
+ fail(err);
257
+ }
258
+ });
259
+ }
260
+ //# sourceMappingURL=agent-kit.js.map
@@ -0,0 +1,22 @@
1
+ /**
2
+ * `hq agent mcp` — stdio MCP server backed by ~/.hq-agent/machine-creds.json.
3
+ *
4
+ * The console recipes register HQ in the bot's framework as
5
+ * { command: "hq", args: ["agent", "mcp"], env: { HQ_MACHINE_CREDS_FILE: … } }
6
+ * so this command must never print anything but JSON-RPC on stdout; all
7
+ * diagnostics go to stderr. Tools wrap existing CLI capability (see
8
+ * lib/agent-kit/mcp/tools.ts); subprocess tools re-invoke the same node +
9
+ * hq binary with the machine-identity env pinned.
10
+ */
11
+ import { Command } from "commander";
12
+ import { McpServer } from "../lib/agent-kit/mcp/jsonrpc.js";
13
+ import { type HqRunResult, type McpToolClients } from "../lib/agent-kit/mcp/tools.js";
14
+ import type { ExternalMachineCreds } from "../lib/agent-kit/creds.js";
15
+ export declare const AGENT_MCP_SERVER_NAME = "hq-agent";
16
+ export declare const AGENT_MCP_INSTRUCTIONS: string;
17
+ export declare const HQ_SUBPROCESS_TIMEOUT_MS = 120000;
18
+ export declare function defaultRunHq(nodeBinary: string, hqBinary: string, env: NodeJS.ProcessEnv): (args: string[]) => Promise<HqRunResult>;
19
+ export declare function defaultMcpClients(creds: ExternalMachineCreds, credsPath: string): McpToolClients;
20
+ export declare function createAgentMcpServer(clients: McpToolClients): McpServer;
21
+ export declare function registerAgentMcpCommand(agent: Command): void;
22
+ //# sourceMappingURL=agent-mcp.d.ts.map