@indigoai-us/hq-cli 5.115.6 → 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 (55) hide show
  1. package/CHANGELOG.md +72 -11
  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/service-manager/index.d.ts +43 -0
  44. package/dist/lib/service-manager/index.js +114 -0
  45. package/dist/lib/service-manager/launchd.d.ts +23 -0
  46. package/dist/lib/service-manager/launchd.js +81 -0
  47. package/dist/lib/service-manager/systemd.d.ts +19 -0
  48. package/dist/lib/service-manager/systemd.js +72 -0
  49. package/dist/lib/service-manager/types.d.ts +32 -0
  50. package/dist/lib/service-manager/types.js +26 -0
  51. package/dist/utils/self-update.js +2 -30
  52. package/dist/utils/update-command-supervisor.cjs +194 -0
  53. package/dist/utils/version-gate.d.ts +18 -0
  54. package/dist/utils/version-gate.js +126 -7
  55. package/package.json +2 -2
@@ -0,0 +1,104 @@
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 { spawn } from "node:child_process";
12
+ import * as os from "node:os";
13
+ import { CLI_VERSION } from "../cli-version.js";
14
+ import { ensureCognitoToken } from "../utils/cognito-session.js";
15
+ import { vaultApiFetch } from "../utils/vault-api.js";
16
+ import { McpServer } from "../lib/agent-kit/mcp/jsonrpc.js";
17
+ import { buildAgentMcpTools } from "../lib/agent-kit/mcp/tools.js";
18
+ import { agentKitPaths } from "../lib/agent-kit/paths.js";
19
+ import { requireExternalCreds } from "./agent-kit.js";
20
+ export const AGENT_MCP_SERVER_NAME = "hq-agent";
21
+ export const AGENT_MCP_INSTRUCTIONS = "You are acting inside HQ as an enrolled external agent. Tools run as that machine identity: " +
22
+ "hq_whoami (identity), hq_search / hq_files_list / hq_files_read (company vault), " +
23
+ "hq_secrets_list / hq_secrets_exec (secrets by NAME only — values are never returned), " +
24
+ "hq_dm_send / hq_inbox_read (direct messages), hq_work_mesh_status (what the team is doing). " +
25
+ "Never ask a user for a token or secret value; if a secret is missing, name it and ask an admin to add it.";
26
+ export const HQ_SUBPROCESS_TIMEOUT_MS = 120_000;
27
+ export function defaultRunHq(nodeBinary, hqBinary, env) {
28
+ return (args) => new Promise((resolve) => {
29
+ const child = spawn(nodeBinary, [hqBinary, ...args], {
30
+ env,
31
+ stdio: ["ignore", "pipe", "pipe"],
32
+ });
33
+ let stdout = "";
34
+ let stderr = "";
35
+ const timer = setTimeout(() => child.kill("SIGKILL"), HQ_SUBPROCESS_TIMEOUT_MS);
36
+ child.stdout.on("data", (d) => (stdout += d.toString("utf8")));
37
+ child.stderr.on("data", (d) => (stderr += d.toString("utf8")));
38
+ child.on("error", (err) => {
39
+ clearTimeout(timer);
40
+ resolve({ code: 127, stdout, stderr: `${stderr}\n${err.message}` });
41
+ });
42
+ child.on("close", (code) => {
43
+ clearTimeout(timer);
44
+ resolve({ code: code ?? 1, stdout, stderr });
45
+ });
46
+ });
47
+ }
48
+ export function defaultMcpClients(creds, credsPath) {
49
+ const env = {
50
+ ...process.env,
51
+ HQ_MACHINE_CREDS_FILE: credsPath,
52
+ HQ_REQUIRE_MACHINE_IDENTITY: "1",
53
+ HQ_VAULT_API_URL: creds.apiBaseUrl,
54
+ NO_COLOR: "1",
55
+ };
56
+ return {
57
+ creds,
58
+ runHq: defaultRunHq(process.execPath, process.argv[1], env),
59
+ getToken: () => ensureCognitoToken({ tokenSource: "machine", interactive: false }),
60
+ apiJson: async (token, path, init) => {
61
+ const res = await vaultApiFetch({
62
+ token,
63
+ baseUrl: creds.apiBaseUrl,
64
+ path,
65
+ method: init?.method,
66
+ body: init?.body,
67
+ query: init?.query,
68
+ });
69
+ const body = await res.json().catch(() => ({}));
70
+ return { status: res.status, body };
71
+ },
72
+ };
73
+ }
74
+ export function createAgentMcpServer(clients) {
75
+ return new McpServer({
76
+ name: AGENT_MCP_SERVER_NAME,
77
+ version: CLI_VERSION,
78
+ instructions: AGENT_MCP_INSTRUCTIONS,
79
+ tools: buildAgentMcpTools(clients),
80
+ });
81
+ }
82
+ export function registerAgentMcpCommand(agent) {
83
+ agent
84
+ .command("mcp")
85
+ .description("Serve HQ as a stdio MCP server for the bot framework (whoami, search, files, secrets-exec, dm, inbox, work-mesh)")
86
+ .action(async () => {
87
+ const paths = agentKitPaths(os.homedir(), process.env);
88
+ let creds;
89
+ try {
90
+ creds = requireExternalCreds(paths);
91
+ }
92
+ catch (err) {
93
+ process.stderr.write(`${err instanceof Error ? err.message : String(err)}\n`);
94
+ process.exit(1);
95
+ }
96
+ process.env.HQ_MACHINE_CREDS_FILE = paths.machineCredsPath;
97
+ process.env.HQ_REQUIRE_MACHINE_IDENTITY = "1";
98
+ process.env.HQ_VAULT_API_URL = creds.apiBaseUrl;
99
+ const server = createAgentMcpServer(defaultMcpClients(creds, paths.machineCredsPath));
100
+ process.stderr.write(`hq agent mcp: serving as ${creds.entityUid} (${creds.companySlug})\n`);
101
+ await server.serve(process.stdin, process.stdout);
102
+ });
103
+ }
104
+ //# sourceMappingURL=agent-mcp.js.map
@@ -0,0 +1,71 @@
1
+ /**
2
+ * `hq agent probe` — end-to-end check that this host is a working external
3
+ * team member, one line per check, non-zero exit on any failure:
4
+ *
5
+ * whoami mint as the machine identity; token names this agent
6
+ * team-sync `hq sync pull --all` succeeds and the company folder exists
7
+ * work-mesh realtime credentials vend for this agent and the roster row
8
+ * shows presence online/stale (heartbeat landed)
9
+ * dm send a DM to self via the notify surface and read it back
10
+ * secrets `GET /secrets/{companyUid}` answers 200
11
+ *
12
+ * Output is the exact shape the console recipes promise: one
13
+ * `PASS <check> <detail>` / `FAIL <check> <detail>` line per check, then
14
+ * `heartbeat <age>`.
15
+ *
16
+ * The result is POSTed to /v1/agents/{uid}/probe-result (api-contract v1) so
17
+ * the console can flip the enrollment to "Enrolled". Also reports the age of
18
+ * the kit's last heartbeat.
19
+ */
20
+ import { Command } from "commander";
21
+ import { type AgentKitPaths } from "../lib/agent-kit/paths.js";
22
+ import type { ExternalMachineCreds } from "../lib/agent-kit/creds.js";
23
+ export interface ProbeCheck {
24
+ name: string;
25
+ ok: boolean;
26
+ detail: string;
27
+ }
28
+ export interface ProbeResult {
29
+ passed: boolean;
30
+ checks: ProbeCheck[];
31
+ at: string;
32
+ /** Seconds since the kit's last successful heartbeat, or null if never. */
33
+ heartbeatAgeSeconds: number | null;
34
+ }
35
+ export declare const DM_ROUNDTRIP_TIMEOUT_MS = 20000;
36
+ export declare const DM_ROUNDTRIP_POLL_MS = 2000;
37
+ export interface ProbeDeps {
38
+ paths: AgentKitPaths;
39
+ creds: ExternalMachineCreds;
40
+ hqRoot: string;
41
+ getToken: () => Promise<string>;
42
+ resolveCompanyUid: (token: string, slug: string) => Promise<string>;
43
+ runPull: (args: string[]) => Promise<number>;
44
+ vendRealtime: (token: string) => Promise<{
45
+ actorUid: string;
46
+ }>;
47
+ listRoster: (token: string, companyUid: string) => Promise<Array<Record<string, unknown>>>;
48
+ sendDm: (token: string, toUid: string, body: string) => Promise<void>;
49
+ readThread: (token: string, withUid: string) => Promise<Array<{
50
+ body: string;
51
+ }>>;
52
+ listSecrets: (token: string, companyUid: string) => Promise<number>;
53
+ postResult: (token: string, result: ProbeResult) => Promise<number>;
54
+ now?: () => Date;
55
+ sleep?: (ms: number) => Promise<void>;
56
+ nonce?: () => string;
57
+ dmTimeoutMs?: number;
58
+ }
59
+ export declare function heartbeatAgeSeconds(paths: Pick<AgentKitPaths, "lastHeartbeatPath">, now?: () => Date): number | null;
60
+ export declare function runProbe(deps: ProbeDeps): Promise<ProbeResult>;
61
+ export declare function formatHeartbeatAge(age: number | null): string;
62
+ /**
63
+ * Recipe contract: `PASS <check> <detail>` / `FAIL <check> <detail>` per
64
+ * check, then `heartbeat <age>`. Two-space separators; no trailing verdict
65
+ * line (the exit code carries it, and the console keys only on the POSTed
66
+ * probe-result).
67
+ */
68
+ export declare function formatProbeReport(result: ProbeResult, color?: boolean): string[];
69
+ export declare function defaultProbeDeps(paths: AgentKitPaths, creds: ExternalMachineCreds): ProbeDeps;
70
+ export declare function registerAgentProbeCommand(agent: Command): void;
71
+ //# sourceMappingURL=agent-probe.d.ts.map
@@ -0,0 +1,294 @@
1
+ /**
2
+ * `hq agent probe` — end-to-end check that this host is a working external
3
+ * team member, one line per check, non-zero exit on any failure:
4
+ *
5
+ * whoami mint as the machine identity; token names this agent
6
+ * team-sync `hq sync pull --all` succeeds and the company folder exists
7
+ * work-mesh realtime credentials vend for this agent and the roster row
8
+ * shows presence online/stale (heartbeat landed)
9
+ * dm send a DM to self via the notify surface and read it back
10
+ * secrets `GET /secrets/{companyUid}` answers 200
11
+ *
12
+ * Output is the exact shape the console recipes promise: one
13
+ * `PASS <check> <detail>` / `FAIL <check> <detail>` line per check, then
14
+ * `heartbeat <age>`.
15
+ *
16
+ * The result is POSTed to /v1/agents/{uid}/probe-result (api-contract v1) so
17
+ * the console can flip the enrollment to "Enrolled". Also reports the age of
18
+ * the kit's last heartbeat.
19
+ */
20
+ import chalk from "chalk";
21
+ import * as fs from "node:fs";
22
+ import * as os from "node:os";
23
+ import * as path from "node:path";
24
+ import { randomBytes } from "node:crypto";
25
+ import { CLI_VERSION } from "../cli-version.js";
26
+ import { ensureCognitoToken } from "../utils/cognito-session.js";
27
+ import { peekIdToken } from "../utils/id-token.js";
28
+ import { getCompanyUid, vaultApiFetch } from "../utils/vault-api.js";
29
+ import { listAgents, readAgentThread, sendAgentDm } from "./agents.js";
30
+ import { createContract3Fetcher } from "../lib/mesh/live/daemon/credentials.js";
31
+ import { agentKitPaths } from "../lib/agent-kit/paths.js";
32
+ import { readKitConfig } from "../lib/agent-kit/kit-config.js";
33
+ import { readLastHeartbeat } from "../lib/agent-kit/run/heartbeat.js";
34
+ import { defaultRunPull, syncPullArgs } from "../lib/agent-kit/run/sync.js";
35
+ import { requireExternalCreds } from "./agent-kit.js";
36
+ export const DM_ROUNDTRIP_TIMEOUT_MS = 20_000;
37
+ export const DM_ROUNDTRIP_POLL_MS = 2_000;
38
+ function errText(err) {
39
+ return err instanceof Error ? err.message : String(err);
40
+ }
41
+ export function heartbeatAgeSeconds(paths, now = () => new Date()) {
42
+ const last = readLastHeartbeat(paths);
43
+ if (!last)
44
+ return null;
45
+ const at = new Date(last.at).getTime();
46
+ if (Number.isNaN(at))
47
+ return null;
48
+ return Math.max(0, Math.round((now().getTime() - at) / 1000));
49
+ }
50
+ export async function runProbe(deps) {
51
+ const now = deps.now ?? (() => new Date());
52
+ const sleep = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
53
+ const nonce = (deps.nonce ?? (() => randomBytes(6).toString("hex")))();
54
+ const self = deps.creds.entityUid;
55
+ const checks = [];
56
+ let token = null;
57
+ let companyUid = null;
58
+ // 1. whoami
59
+ try {
60
+ token = await deps.getToken();
61
+ const claims = peekIdToken(token);
62
+ const uid = claims["custom:entityUid"];
63
+ const type = claims["custom:entityType"];
64
+ if (type === "agent" && uid === self) {
65
+ checks.push({ name: "whoami", ok: true, detail: `${self} (external, ${deps.creds.companySlug})` });
66
+ }
67
+ else {
68
+ checks.push({
69
+ name: "whoami",
70
+ ok: false,
71
+ detail: `token names ${String(type)}:${String(uid)}, expected agent:${self}`,
72
+ });
73
+ token = null;
74
+ }
75
+ }
76
+ catch (err) {
77
+ checks.push({ name: "whoami", ok: false, detail: `mint failed: ${errText(err)}` });
78
+ }
79
+ if (token) {
80
+ try {
81
+ companyUid = await deps.resolveCompanyUid(token, deps.creds.companySlug);
82
+ }
83
+ catch (err) {
84
+ checks.push({ name: "company", ok: false, detail: `cannot resolve ${deps.creds.companySlug}: ${errText(err)}` });
85
+ }
86
+ }
87
+ // 2. team-sync
88
+ if (token) {
89
+ try {
90
+ const code = await deps.runPull(syncPullArgs(deps.hqRoot));
91
+ const companyDir = path.join(deps.hqRoot, "companies", deps.creds.companySlug);
92
+ const present = fs.existsSync(companyDir);
93
+ const ok = code === 0 && present;
94
+ checks.push({
95
+ name: "team-sync",
96
+ ok,
97
+ detail: ok
98
+ ? `synced into ${companyDir}`
99
+ : code !== 0
100
+ ? `hq sync pull exited ${code}`
101
+ : `sync exited 0 but ${companyDir} is missing`,
102
+ });
103
+ }
104
+ catch (err) {
105
+ checks.push({ name: "team-sync", ok: false, detail: errText(err) });
106
+ }
107
+ }
108
+ else {
109
+ checks.push({ name: "team-sync", ok: false, detail: "skipped: no token" });
110
+ }
111
+ // 3. mesh-presence
112
+ if (token && companyUid) {
113
+ try {
114
+ const vend = await deps.vendRealtime(token);
115
+ if (vend.actorUid !== self) {
116
+ checks.push({ name: "work-mesh", ok: false, detail: `realtime vend is for ${vend.actorUid}` });
117
+ }
118
+ else {
119
+ const roster = await deps.listRoster(token, companyUid);
120
+ const row = roster.find((r) => r.uid === self);
121
+ const presence = typeof row?.presence === "string" ? row.presence : "unknown";
122
+ const last = typeof row?.lastHeartbeatAt === "string" ? row.lastHeartbeatAt : "never";
123
+ const ok = presence === "online" || presence === "stale";
124
+ checks.push({
125
+ name: "work-mesh",
126
+ ok,
127
+ detail: row
128
+ ? `realtime vend ok; presence=${presence} lastHeartbeatAt=${last}${ok ? "" : " — is `hq agent kit` installed and running?"}`
129
+ : "realtime vend ok but this agent is not on the company roster",
130
+ });
131
+ }
132
+ }
133
+ catch (err) {
134
+ checks.push({ name: "work-mesh", ok: false, detail: errText(err) });
135
+ }
136
+ }
137
+ else {
138
+ checks.push({ name: "work-mesh", ok: false, detail: "skipped: no token/company" });
139
+ }
140
+ // 4. dm-roundtrip
141
+ if (token) {
142
+ try {
143
+ const body = `hq agent probe ${nonce}`;
144
+ await deps.sendDm(token, self, body);
145
+ const deadline = now().getTime() + (deps.dmTimeoutMs ?? DM_ROUNDTRIP_TIMEOUT_MS);
146
+ let found = false;
147
+ for (;;) {
148
+ const msgs = await deps.readThread(token, self);
149
+ if (msgs.some((m) => typeof m.body === "string" && m.body.includes(nonce))) {
150
+ found = true;
151
+ break;
152
+ }
153
+ if (now().getTime() >= deadline)
154
+ break;
155
+ await sleep(DM_ROUNDTRIP_POLL_MS);
156
+ }
157
+ checks.push({
158
+ name: "dm",
159
+ ok: found,
160
+ detail: found ? "sent to self via /v1/notify/dm and read back" : `sent but not visible in thread after ${Math.round((deps.dmTimeoutMs ?? DM_ROUNDTRIP_TIMEOUT_MS) / 1000)}s`,
161
+ });
162
+ }
163
+ catch (err) {
164
+ checks.push({ name: "dm", ok: false, detail: errText(err) });
165
+ }
166
+ }
167
+ else {
168
+ checks.push({ name: "dm", ok: false, detail: "skipped: no token" });
169
+ }
170
+ // 5. secrets-list
171
+ if (token && companyUid) {
172
+ try {
173
+ const status = await deps.listSecrets(token, companyUid);
174
+ checks.push({
175
+ name: "secrets",
176
+ ok: status === 200,
177
+ detail: status === 200 ? `GET /secrets/${companyUid} → 200` : `GET /secrets/${companyUid} → ${status}`,
178
+ });
179
+ }
180
+ catch (err) {
181
+ checks.push({ name: "secrets", ok: false, detail: errText(err) });
182
+ }
183
+ }
184
+ else {
185
+ checks.push({ name: "secrets", ok: false, detail: "skipped: no token/company" });
186
+ }
187
+ const result = {
188
+ passed: checks.every((c) => c.ok),
189
+ checks,
190
+ at: now().toISOString(),
191
+ heartbeatAgeSeconds: heartbeatAgeSeconds(deps.paths, now),
192
+ };
193
+ if (token) {
194
+ try {
195
+ const status = await deps.postResult(token, result);
196
+ if (status < 200 || status >= 300) {
197
+ result.checks.push({ name: "report", ok: false, detail: `probe-result POST → ${status}` });
198
+ result.passed = false;
199
+ }
200
+ }
201
+ catch (err) {
202
+ result.checks.push({ name: "report", ok: false, detail: `probe-result POST failed: ${errText(err)}` });
203
+ result.passed = false;
204
+ }
205
+ }
206
+ return result;
207
+ }
208
+ export function formatHeartbeatAge(age) {
209
+ if (age === null)
210
+ return "never (kit heartbeat has not posted yet)";
211
+ if (age < 90)
212
+ return `${age}s ago`;
213
+ if (age < 3600)
214
+ return `${Math.round(age / 60)}m ago`;
215
+ return `${(age / 3600).toFixed(1)}h ago`;
216
+ }
217
+ /**
218
+ * Recipe contract: `PASS <check> <detail>` / `FAIL <check> <detail>` per
219
+ * check, then `heartbeat <age>`. Two-space separators; no trailing verdict
220
+ * line (the exit code carries it, and the console keys only on the POSTed
221
+ * probe-result).
222
+ */
223
+ export function formatProbeReport(result, color = true) {
224
+ const pass = color ? chalk.green("PASS") : "PASS";
225
+ const failTag = color ? chalk.red("FAIL") : "FAIL";
226
+ const lines = result.checks.map((c) => `${c.ok ? pass : failTag} ${c.name} ${c.detail}`);
227
+ lines.push(`heartbeat ${formatHeartbeatAge(result.heartbeatAgeSeconds)}`);
228
+ return lines;
229
+ }
230
+ export function defaultProbeDeps(paths, creds) {
231
+ process.env.HQ_MACHINE_CREDS_FILE = paths.machineCredsPath;
232
+ process.env.HQ_REQUIRE_MACHINE_IDENTITY = "1";
233
+ process.env.HQ_VAULT_API_URL = creds.apiBaseUrl;
234
+ const config = readKitConfig(paths, CLI_VERSION);
235
+ const base = creds.apiBaseUrl;
236
+ return {
237
+ paths,
238
+ creds,
239
+ hqRoot: config.hqRoot,
240
+ getToken: () => ensureCognitoToken({ tokenSource: "machine", interactive: false }),
241
+ resolveCompanyUid: (token, slug) => getCompanyUid(token, slug),
242
+ runPull: defaultRunPull(process.execPath, process.argv[1], process.env),
243
+ vendRealtime: async (token) => {
244
+ const bundle = await createContract3Fetcher({ token, baseUrl: base })();
245
+ return { actorUid: bundle.actorUid };
246
+ },
247
+ listRoster: async (token, companyUid) => (await listAgents(token, companyUid)),
248
+ sendDm: (token, to, body) => sendAgentDm(token, to, body),
249
+ readThread: async (token, withUid) => readAgentThread(token, withUid, 20),
250
+ listSecrets: async (token, companyUid) => {
251
+ const res = await vaultApiFetch({
252
+ token,
253
+ baseUrl: base,
254
+ path: `/secrets/${encodeURIComponent(companyUid)}`,
255
+ });
256
+ return res.status;
257
+ },
258
+ postResult: async (token, result) => {
259
+ const res = await vaultApiFetch({
260
+ token,
261
+ baseUrl: base,
262
+ path: `/v1/agents/${encodeURIComponent(creds.entityUid)}/probe-result`,
263
+ method: "POST",
264
+ body: { passed: result.passed, checks: result.checks, at: result.at },
265
+ });
266
+ return res.status;
267
+ },
268
+ };
269
+ }
270
+ export function registerAgentProbeCommand(agent) {
271
+ agent
272
+ .command("probe")
273
+ .description("Verify identity, vault sync, work-mesh presence, DMs and secrets; report to the console")
274
+ .option("--json", "Print machine-readable JSON")
275
+ .action(async (opts) => {
276
+ try {
277
+ const paths = agentKitPaths(os.homedir(), process.env);
278
+ const creds = requireExternalCreds(paths);
279
+ const result = await runProbe(defaultProbeDeps(paths, creds));
280
+ if (opts.json)
281
+ console.log(JSON.stringify(result, null, 2));
282
+ else
283
+ for (const line of formatProbeReport(result))
284
+ console.log(line);
285
+ if (!result.passed)
286
+ process.exit(1);
287
+ }
288
+ catch (err) {
289
+ console.error(chalk.red(err instanceof Error ? err.message : String(err)));
290
+ process.exit(1);
291
+ }
292
+ });
293
+ }
294
+ //# sourceMappingURL=agent-probe.js.map
@@ -0,0 +1,12 @@
1
+ /**
2
+ * `hq agent …` — commands a host runs to BE an external HQ agent (as opposed
3
+ * to `hq agents …`, which an admin runs to manage a company's agents):
4
+ *
5
+ * hq agent enroll <code> redeem a one-time enrollment code
6
+ * hq agent kit install|status|uninstall|run <service>
7
+ * hq agent probe end-to-end membership check
8
+ * hq agent mcp stdio MCP server for the bot framework
9
+ */
10
+ import { Command } from "commander";
11
+ export declare function registerAgentCommand(program: Command): void;
12
+ //# sourceMappingURL=agent.d.ts.map
@@ -0,0 +1,23 @@
1
+ /**
2
+ * `hq agent …` — commands a host runs to BE an external HQ agent (as opposed
3
+ * to `hq agents …`, which an admin runs to manage a company's agents):
4
+ *
5
+ * hq agent enroll <code> redeem a one-time enrollment code
6
+ * hq agent kit install|status|uninstall|run <service>
7
+ * hq agent probe end-to-end membership check
8
+ * hq agent mcp stdio MCP server for the bot framework
9
+ */
10
+ import { registerAgentEnrollCommand } from "./agent-enroll.js";
11
+ import { registerAgentKitCommand } from "./agent-kit.js";
12
+ import { registerAgentMcpCommand } from "./agent-mcp.js";
13
+ import { registerAgentProbeCommand } from "./agent-probe.js";
14
+ export function registerAgentCommand(program) {
15
+ const agent = program
16
+ .command("agent")
17
+ .description("Enroll and run this host as an external HQ agent");
18
+ registerAgentEnrollCommand(agent);
19
+ registerAgentKitCommand(agent);
20
+ registerAgentProbeCommand(agent);
21
+ registerAgentMcpCommand(agent);
22
+ }
23
+ //# sourceMappingURL=agent.js.map
@@ -15,6 +15,8 @@
15
15
  * hq agents start|stop <uid> — EC2 start/stop
16
16
  * hq agents retry <uid> — resume setup from first non-done step
17
17
  * hq agents rm <uid> --yes — deprovision (destructive; flag-guarded)
18
+ * hq agents rotate <uid> — new enrollment code for an external agent
19
+ * hq agents revoke <uid> --yes — revoke an external agent (--remove deletes it)
18
20
  * hq agents jobs list <uid> — off-box job roster (schedule + rate)
19
21
  * hq agents jobs pause <uid> <jobId> — flip schedule State=DISABLED
20
22
  * hq agents jobs cancel <uid> <jobId> — delete schedule + drop the job
@@ -350,5 +352,30 @@ export declare function sendAgentDm(token: string, agentUid: string, message: st
350
352
  export declare function readAgentThread(token: string, agentUid: string, limit?: number): Promise<DmThreadMessage[]>;
351
353
  /** Render one thread message as a labeled line for the terminal. */
352
354
  export declare function formatThreadMessage(m: DmThreadMessage, agentLabel: string): string;
355
+ export interface EnrollmentIssue {
356
+ code: string;
357
+ expiresAt: string;
358
+ enrollmentId: string;
359
+ }
360
+ export interface RotateAgentResult {
361
+ agentUid: string;
362
+ enrollment: EnrollmentIssue;
363
+ }
364
+ /**
365
+ * POST /v1/agents/{uid}/rotate — invalidate the current secret and host key
366
+ * and issue a fresh one-time enrollment code (returned exactly once).
367
+ */
368
+ export declare function rotateAgent(token: string, agentUid: string): Promise<RotateAgentResult>;
369
+ /** Display form of a 24-char code: six groups of four. */
370
+ export declare function groupEnrollmentCode(code: string): string;
371
+ /**
372
+ * POST /v1/agents/{uid}/revoke — disable the Cognito user, drop membership,
373
+ * mark the enrollment revoked; `remove` also deletes the entity.
374
+ */
375
+ export declare function revokeAgent(token: string, agentUid: string, remove: boolean): Promise<{
376
+ removed: boolean;
377
+ }>;
378
+ /** Lines printed after a rotate: the code is shown once and never stored. */
379
+ export declare function formatRotateOutput(result: RotateAgentResult): string[];
353
380
  export declare function registerAgentsCommand(program: Command): void;
354
381
  //# sourceMappingURL=agents.d.ts.map