@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,17 @@
1
+ /**
2
+ * Per-service log under ~/.hq-agent/logs/<service>.log, rotated at 5 MB.
3
+ * Lines are sanitized so a bearer token or secret reference can never land
4
+ * on disk even through an error message.
5
+ */
6
+ import type { AgentKitPaths } from "./paths.js";
7
+ export declare const KIT_LOG_MAX_BYTES: number;
8
+ export type KitLogLevel = "info" | "warn" | "error";
9
+ export type KitLogger = (level: KitLogLevel, message: string) => void;
10
+ export declare function sanitizeLogLine(message: string): string;
11
+ export declare function formatKitLogLine(level: KitLogLevel, message: string, now?: () => Date): string;
12
+ export declare function createKitLogger(paths: Pick<AgentKitPaths, "logsDir">, service: string, opts?: {
13
+ now?: () => Date;
14
+ echo?: boolean;
15
+ maxBytes?: number;
16
+ }): KitLogger;
17
+ //# sourceMappingURL=log.d.ts.map
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Per-service log under ~/.hq-agent/logs/<service>.log, rotated at 5 MB.
3
+ * Lines are sanitized so a bearer token or secret reference can never land
4
+ * on disk even through an error message.
5
+ */
6
+ import * as fs from "node:fs";
7
+ import * as path from "node:path";
8
+ import { serviceLogPath } from "./paths.js";
9
+ export const KIT_LOG_MAX_BYTES = 5 * 1024 * 1024;
10
+ export function sanitizeLogLine(message) {
11
+ return message
12
+ .replace(/Bearer\s+[A-Za-z0-9._-]+/gi, "Bearer [REDACTED]")
13
+ .replace(/eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/g, "[JWT REDACTED]")
14
+ .replace(/"secret"\s*:\s*"[^"]*"/g, '"secret":"[REDACTED]"')
15
+ .replace(/\r?\n/g, " ")
16
+ .slice(0, 1000);
17
+ }
18
+ export function formatKitLogLine(level, message, now = () => new Date()) {
19
+ return `${now().toISOString()} ${level} ${sanitizeLogLine(message)}`;
20
+ }
21
+ export function createKitLogger(paths, service, opts = {}) {
22
+ const file = serviceLogPath(paths, service);
23
+ const maxBytes = opts.maxBytes ?? KIT_LOG_MAX_BYTES;
24
+ return (level, message) => {
25
+ const line = formatKitLogLine(level, message, opts.now);
26
+ try {
27
+ fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
28
+ try {
29
+ if (fs.statSync(file).size > maxBytes) {
30
+ fs.renameSync(file, `${file}.1`);
31
+ }
32
+ }
33
+ catch {
34
+ /* no file yet */
35
+ }
36
+ fs.appendFileSync(file, `${line}\n`, { mode: 0o600 });
37
+ }
38
+ catch {
39
+ /* best-effort */
40
+ }
41
+ if (opts.echo) {
42
+ (level === "error" ? process.stderr : process.stdout).write(`${line}\n`);
43
+ }
44
+ };
45
+ }
46
+ //# sourceMappingURL=log.js.map
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Minimal stdio MCP server core (JSON-RPC 2.0, newline-delimited).
3
+ *
4
+ * hq-cli carries no MCP SDK dependency and the full HQ MCP surface lives in
5
+ * the separate @indigoai-us/hq-mcp package (a bin, not a library). `hq agent
6
+ * mcp` needs only the tools subset of the protocol — initialize, ping,
7
+ * tools/list, tools/call — so this module implements exactly that over a
8
+ * transport-agnostic `dispatch()` (tests drive it directly) plus a stdio
9
+ * pump. Every request is answered; notifications are consumed silently;
10
+ * malformed input yields a JSON-RPC error rather than a crash.
11
+ */
12
+ export declare const MCP_PROTOCOL_VERSIONS: readonly ["2025-06-18", "2025-03-26", "2024-11-05"];
13
+ export declare const MCP_LATEST_PROTOCOL_VERSION: "2025-06-18";
14
+ export interface JsonRpcRequest {
15
+ jsonrpc: "2.0";
16
+ id?: string | number | null;
17
+ method: string;
18
+ params?: unknown;
19
+ }
20
+ export interface JsonRpcResponse {
21
+ jsonrpc: "2.0";
22
+ id: string | number | null;
23
+ result?: unknown;
24
+ error?: {
25
+ code: number;
26
+ message: string;
27
+ data?: unknown;
28
+ };
29
+ }
30
+ export declare const JSONRPC_PARSE_ERROR = -32700;
31
+ export declare const JSONRPC_INVALID_REQUEST = -32600;
32
+ export declare const JSONRPC_METHOD_NOT_FOUND = -32601;
33
+ export declare const JSONRPC_INVALID_PARAMS = -32602;
34
+ export declare const JSONRPC_INTERNAL_ERROR = -32603;
35
+ export interface McpToolDefinition {
36
+ name: string;
37
+ description: string;
38
+ inputSchema: Record<string, unknown>;
39
+ }
40
+ export interface McpToolContent {
41
+ type: "text";
42
+ text: string;
43
+ }
44
+ export interface McpToolResult {
45
+ content: McpToolContent[];
46
+ isError?: boolean;
47
+ }
48
+ export type McpToolHandler = (args: Record<string, unknown>) => Promise<McpToolResult>;
49
+ export interface McpTool extends McpToolDefinition {
50
+ handler: McpToolHandler;
51
+ }
52
+ export interface McpServerOptions {
53
+ name: string;
54
+ version: string;
55
+ instructions?: string;
56
+ tools: McpTool[];
57
+ }
58
+ export declare class McpToolInputError extends Error {
59
+ constructor(message: string);
60
+ }
61
+ export declare function textResult(text: string): McpToolResult;
62
+ export declare function errorResult(text: string): McpToolResult;
63
+ export declare class McpServer {
64
+ private readonly opts;
65
+ private readonly tools;
66
+ private initialized;
67
+ constructor(opts: McpServerOptions);
68
+ listTools(): McpToolDefinition[];
69
+ /**
70
+ * Handle one decoded message. Returns null for notifications (no reply)
71
+ * and for responses the client sends us (we issue no requests).
72
+ */
73
+ dispatch(message: unknown): Promise<JsonRpcResponse | null>;
74
+ /** Decode one line, dispatch, and encode the reply (or null). */
75
+ handleLine(line: string): Promise<string | null>;
76
+ isInitialized(): boolean;
77
+ /**
78
+ * Pump newline-delimited messages from `input` to `output` until EOF.
79
+ * Requests are processed strictly in order so tool side effects (a DM
80
+ * send, a secrets exec) never interleave.
81
+ */
82
+ serve(input: NodeJS.ReadableStream, output: NodeJS.WritableStream): Promise<void>;
83
+ }
84
+ //# sourceMappingURL=jsonrpc.d.ts.map
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Minimal stdio MCP server core (JSON-RPC 2.0, newline-delimited).
3
+ *
4
+ * hq-cli carries no MCP SDK dependency and the full HQ MCP surface lives in
5
+ * the separate @indigoai-us/hq-mcp package (a bin, not a library). `hq agent
6
+ * mcp` needs only the tools subset of the protocol — initialize, ping,
7
+ * tools/list, tools/call — so this module implements exactly that over a
8
+ * transport-agnostic `dispatch()` (tests drive it directly) plus a stdio
9
+ * pump. Every request is answered; notifications are consumed silently;
10
+ * malformed input yields a JSON-RPC error rather than a crash.
11
+ */
12
+ export const MCP_PROTOCOL_VERSIONS = ["2025-06-18", "2025-03-26", "2024-11-05"];
13
+ export const MCP_LATEST_PROTOCOL_VERSION = MCP_PROTOCOL_VERSIONS[0];
14
+ export const JSONRPC_PARSE_ERROR = -32700;
15
+ export const JSONRPC_INVALID_REQUEST = -32600;
16
+ export const JSONRPC_METHOD_NOT_FOUND = -32601;
17
+ export const JSONRPC_INVALID_PARAMS = -32602;
18
+ export const JSONRPC_INTERNAL_ERROR = -32603;
19
+ export class McpToolInputError extends Error {
20
+ constructor(message) {
21
+ super(message);
22
+ this.name = "McpToolInputError";
23
+ }
24
+ }
25
+ export function textResult(text) {
26
+ return { content: [{ type: "text", text }] };
27
+ }
28
+ export function errorResult(text) {
29
+ return { content: [{ type: "text", text }], isError: true };
30
+ }
31
+ function ok(id, result) {
32
+ return { jsonrpc: "2.0", id, result };
33
+ }
34
+ function fail(id, code, message, data) {
35
+ return { jsonrpc: "2.0", id, error: { code, message, ...(data !== undefined ? { data } : {}) } };
36
+ }
37
+ export class McpServer {
38
+ opts;
39
+ tools = new Map();
40
+ initialized = false;
41
+ constructor(opts) {
42
+ this.opts = opts;
43
+ for (const tool of opts.tools) {
44
+ if (this.tools.has(tool.name))
45
+ throw new Error(`duplicate MCP tool ${tool.name}`);
46
+ this.tools.set(tool.name, tool);
47
+ }
48
+ }
49
+ listTools() {
50
+ return [...this.tools.values()].map(({ name, description, inputSchema }) => ({
51
+ name,
52
+ description,
53
+ inputSchema,
54
+ }));
55
+ }
56
+ /**
57
+ * Handle one decoded message. Returns null for notifications (no reply)
58
+ * and for responses the client sends us (we issue no requests).
59
+ */
60
+ async dispatch(message) {
61
+ if (!message || typeof message !== "object" || Array.isArray(message)) {
62
+ return fail(null, JSONRPC_INVALID_REQUEST, "expected a JSON-RPC object");
63
+ }
64
+ const msg = message;
65
+ if (typeof msg.method !== "string") {
66
+ // A response to a server-initiated request; we never send any.
67
+ if ("result" in msg || "error" in msg)
68
+ return null;
69
+ return fail(msg.id ?? null, JSONRPC_INVALID_REQUEST, "missing method");
70
+ }
71
+ const isNotification = msg.id === undefined;
72
+ const id = msg.id ?? null;
73
+ const params = (msg.params && typeof msg.params === "object" ? msg.params : {});
74
+ if (isNotification) {
75
+ if (msg.method === "notifications/initialized")
76
+ this.initialized = true;
77
+ return null;
78
+ }
79
+ switch (msg.method) {
80
+ case "initialize": {
81
+ const requested = typeof params.protocolVersion === "string" ? params.protocolVersion : "";
82
+ const protocolVersion = MCP_PROTOCOL_VERSIONS.includes(requested)
83
+ ? requested
84
+ : MCP_LATEST_PROTOCOL_VERSION;
85
+ return ok(id, {
86
+ protocolVersion,
87
+ capabilities: { tools: { listChanged: false } },
88
+ serverInfo: { name: this.opts.name, version: this.opts.version },
89
+ ...(this.opts.instructions ? { instructions: this.opts.instructions } : {}),
90
+ });
91
+ }
92
+ case "ping":
93
+ return ok(id, {});
94
+ case "tools/list":
95
+ return ok(id, { tools: this.listTools() });
96
+ case "tools/call": {
97
+ const name = typeof params.name === "string" ? params.name : "";
98
+ const tool = this.tools.get(name);
99
+ if (!tool)
100
+ return fail(id, JSONRPC_INVALID_PARAMS, `unknown tool: ${name || "(missing name)"}`);
101
+ const args = params.arguments && typeof params.arguments === "object" && !Array.isArray(params.arguments)
102
+ ? params.arguments
103
+ : {};
104
+ try {
105
+ return ok(id, await tool.handler(args));
106
+ }
107
+ catch (err) {
108
+ if (err instanceof McpToolInputError)
109
+ return fail(id, JSONRPC_INVALID_PARAMS, err.message);
110
+ // Tool execution failures are results, not protocol errors, so the
111
+ // model can read and react to them.
112
+ return ok(id, errorResult(err instanceof Error ? err.message : String(err)));
113
+ }
114
+ }
115
+ default:
116
+ return fail(id, JSONRPC_METHOD_NOT_FOUND, `method not found: ${msg.method}`);
117
+ }
118
+ }
119
+ /** Decode one line, dispatch, and encode the reply (or null). */
120
+ async handleLine(line) {
121
+ const trimmed = line.trim();
122
+ if (!trimmed)
123
+ return null;
124
+ let parsed;
125
+ try {
126
+ parsed = JSON.parse(trimmed);
127
+ }
128
+ catch {
129
+ return JSON.stringify(fail(null, JSONRPC_PARSE_ERROR, "invalid JSON"));
130
+ }
131
+ const reply = await this.dispatch(parsed);
132
+ return reply ? JSON.stringify(reply) : null;
133
+ }
134
+ isInitialized() {
135
+ return this.initialized;
136
+ }
137
+ /**
138
+ * Pump newline-delimited messages from `input` to `output` until EOF.
139
+ * Requests are processed strictly in order so tool side effects (a DM
140
+ * send, a secrets exec) never interleave.
141
+ */
142
+ async serve(input, output) {
143
+ let buffer = "";
144
+ const write = (s) => new Promise((resolve, reject) => output.write(`${s}\n`, (err) => (err ? reject(err) : resolve())));
145
+ input.setEncoding("utf8");
146
+ for await (const chunk of input) {
147
+ buffer += chunk;
148
+ let nl;
149
+ while ((nl = buffer.indexOf("\n")) >= 0) {
150
+ const line = buffer.slice(0, nl);
151
+ buffer = buffer.slice(nl + 1);
152
+ const reply = await this.handleLine(line);
153
+ if (reply)
154
+ await write(reply);
155
+ }
156
+ }
157
+ if (buffer.trim()) {
158
+ const reply = await this.handleLine(buffer);
159
+ if (reply)
160
+ await write(reply);
161
+ }
162
+ }
163
+ }
164
+ //# sourceMappingURL=jsonrpc.js.map
@@ -0,0 +1,45 @@
1
+ /**
2
+ * The `hq agent mcp` tool surface. Each tool wraps an existing hq CLI
3
+ * capability so a bot framework (OpenClaw, grokbot, any MCP client)
4
+ * can act as the enrolled agent without a token ever crossing the MCP
5
+ * channel: the machine identity lives in machine-creds.json and every call
6
+ * mints through hq-cloud.
7
+ *
8
+ * Two seams keep this testable and honest:
9
+ * - `runHq(args)` — spawn the SAME hq binary for capabilities that are
10
+ * already complete CLI commands (search, files, secrets exec, work mesh).
11
+ * - `api` — token + vault-API fetch for the notify surface (DM, inbox).
12
+ *
13
+ * `hq_secrets_exec` runs a command with secrets injected and returns ONLY
14
+ * the command's exit code and (bounded) output — never an environment dump,
15
+ * never a secret value. `hq_secrets_list` names secrets; values are never a
16
+ * tool result.
17
+ */
18
+ import type { ExternalMachineCreds } from "../creds.js";
19
+ import { type McpTool } from "./jsonrpc.js";
20
+ export interface HqRunResult {
21
+ code: number;
22
+ stdout: string;
23
+ stderr: string;
24
+ }
25
+ export interface McpToolClients {
26
+ creds: ExternalMachineCreds;
27
+ /** Run `hq <args…>` as the machine identity; never throws. */
28
+ runHq: (args: string[]) => Promise<HqRunResult>;
29
+ getToken: () => Promise<string>;
30
+ /** Authenticated JSON call against the control plane. */
31
+ apiJson: (token: string, path: string, init?: {
32
+ method?: string;
33
+ body?: Record<string, unknown>;
34
+ query?: Record<string, string>;
35
+ }) => Promise<{
36
+ status: number;
37
+ body: unknown;
38
+ }>;
39
+ }
40
+ export declare const MAX_TOOL_OUTPUT_CHARS = 40000;
41
+ export declare function clampOutput(text: string, max?: number): string;
42
+ /** Vault paths are company-anchored and must not escape. */
43
+ export declare function assertVaultPath(p: string): string;
44
+ export declare function buildAgentMcpTools(clients: McpToolClients): McpTool[];
45
+ //# sourceMappingURL=tools.d.ts.map
@@ -0,0 +1,280 @@
1
+ /**
2
+ * The `hq agent mcp` tool surface. Each tool wraps an existing hq CLI
3
+ * capability so a bot framework (OpenClaw, grokbot, any MCP client)
4
+ * can act as the enrolled agent without a token ever crossing the MCP
5
+ * channel: the machine identity lives in machine-creds.json and every call
6
+ * mints through hq-cloud.
7
+ *
8
+ * Two seams keep this testable and honest:
9
+ * - `runHq(args)` — spawn the SAME hq binary for capabilities that are
10
+ * already complete CLI commands (search, files, secrets exec, work mesh).
11
+ * - `api` — token + vault-API fetch for the notify surface (DM, inbox).
12
+ *
13
+ * `hq_secrets_exec` runs a command with secrets injected and returns ONLY
14
+ * the command's exit code and (bounded) output — never an environment dump,
15
+ * never a secret value. `hq_secrets_list` names secrets; values are never a
16
+ * tool result.
17
+ */
18
+ import { CLI_VERSION } from "../../../cli-version.js";
19
+ import { peekIdToken } from "../../../utils/id-token.js";
20
+ import { errorResult, McpToolInputError, textResult } from "./jsonrpc.js";
21
+ export const MAX_TOOL_OUTPUT_CHARS = 40_000;
22
+ function str(args, key, opts = {}) {
23
+ const v = args[key];
24
+ if (v === undefined || v === null) {
25
+ if (opts.required)
26
+ throw new McpToolInputError(`"${key}" is required`);
27
+ return undefined;
28
+ }
29
+ if (typeof v !== "string")
30
+ throw new McpToolInputError(`"${key}" must be a string`);
31
+ if (opts.required && v.trim().length === 0)
32
+ throw new McpToolInputError(`"${key}" must not be empty`);
33
+ if (opts.max !== undefined && v.length > opts.max)
34
+ throw new McpToolInputError(`"${key}" exceeds ${opts.max} characters`);
35
+ return v;
36
+ }
37
+ function int(args, key, opts) {
38
+ const v = args[key];
39
+ if (v === undefined || v === null)
40
+ return opts.fallback;
41
+ if (typeof v !== "number" || !Number.isInteger(v))
42
+ throw new McpToolInputError(`"${key}" must be an integer`);
43
+ return Math.min(opts.max, Math.max(opts.min, v));
44
+ }
45
+ function strList(args, key) {
46
+ const v = args[key];
47
+ if (!Array.isArray(v) || v.length === 0 || !v.every((x) => typeof x === "string" && x.length > 0)) {
48
+ throw new McpToolInputError(`"${key}" must be a non-empty array of strings`);
49
+ }
50
+ return v;
51
+ }
52
+ export function clampOutput(text, max = MAX_TOOL_OUTPUT_CHARS) {
53
+ return text.length > max ? `${text.slice(0, max)}\n… [truncated ${text.length - max} chars]` : text;
54
+ }
55
+ /** Vault paths are company-anchored and must not escape. */
56
+ export function assertVaultPath(p) {
57
+ const clean = p.trim().replace(/^\/+/, "");
58
+ if (!/^companies\/[a-z0-9][a-z0-9-]*(\/[^\0]*)?$/i.test(clean) || clean.split("/").includes("..")) {
59
+ throw new McpToolInputError(`"path" must be vault-relative and start with companies/<slug>/ (got ${JSON.stringify(p)})`);
60
+ }
61
+ return clean;
62
+ }
63
+ const SECRET_NAME = /^[A-Z][A-Z0-9_]*(\/[A-Z][A-Z0-9_]+)*$/;
64
+ function fromRun(r, label) {
65
+ const out = [r.stdout.trim(), r.stderr.trim()].filter(Boolean).join("\n");
66
+ if (r.code !== 0)
67
+ return errorResult(clampOutput(`${label} failed (exit ${r.code})\n${out}`));
68
+ return textResult(clampOutput(out || `${label}: (no output)`));
69
+ }
70
+ export function buildAgentMcpTools(clients) {
71
+ const { creds } = clients;
72
+ const company = creds.companySlug;
73
+ return [
74
+ {
75
+ name: "hq_whoami",
76
+ description: "Identity this MCP server acts as: the enrolled external agent, its company, and the control plane.",
77
+ inputSchema: { type: "object", properties: {}, additionalProperties: false },
78
+ handler: async () => {
79
+ let tokenOk;
80
+ let claimUid;
81
+ try {
82
+ const claims = peekIdToken(await clients.getToken());
83
+ claimUid = typeof claims["custom:entityUid"] === "string" ? claims["custom:entityUid"] : undefined;
84
+ tokenOk = claimUid === creds.entityUid;
85
+ }
86
+ catch {
87
+ tokenOk = false;
88
+ }
89
+ return textResult(JSON.stringify({
90
+ agentUid: creds.entityUid,
91
+ runtime: "external",
92
+ companySlug: company,
93
+ apiBaseUrl: creds.apiBaseUrl,
94
+ cliVersion: CLI_VERSION,
95
+ session: tokenOk ? "ok" : `unavailable${claimUid ? ` (token names ${claimUid})` : ""}`,
96
+ }, null, 2));
97
+ },
98
+ },
99
+ {
100
+ name: "hq_search",
101
+ description: "Search the synced company vault (local qmd index). Modes: keyword (default), semantic, hybrid.",
102
+ inputSchema: {
103
+ type: "object",
104
+ properties: {
105
+ query: { type: "string", description: "Search text" },
106
+ mode: { type: "string", enum: ["keyword", "semantic", "hybrid"] },
107
+ count: { type: "integer", minimum: 1, maximum: 50 },
108
+ },
109
+ required: ["query"],
110
+ additionalProperties: false,
111
+ },
112
+ handler: async (args) => {
113
+ const query = str(args, "query", { required: true, max: 2000 });
114
+ const mode = str(args, "mode");
115
+ if (mode !== undefined && !["keyword", "semantic", "hybrid"].includes(mode)) {
116
+ throw new McpToolInputError('"mode" must be keyword, semantic or hybrid');
117
+ }
118
+ const count = int(args, "count", { min: 1, max: 50, fallback: 10 });
119
+ const hqArgs = ["search", query, "--mode", mode ?? "keyword", "-n", String(count), "--json"];
120
+ return fromRun(await clients.runHq(hqArgs), "hq search");
121
+ },
122
+ },
123
+ {
124
+ name: "hq_files_list",
125
+ description: "List vault objects under a company path without syncing them (hq files browse).",
126
+ inputSchema: {
127
+ type: "object",
128
+ properties: { path: { type: "string", description: "companies/<slug>/<folder>" } },
129
+ required: ["path"],
130
+ additionalProperties: false,
131
+ },
132
+ handler: async (args) => {
133
+ const p = assertVaultPath(str(args, "path", { required: true, max: 1000 }));
134
+ return fromRun(await clients.runHq(["files", "browse", p]), "hq files browse");
135
+ },
136
+ },
137
+ {
138
+ name: "hq_files_read",
139
+ description: "Read one vault file's contents (hq files cat). Text files only; output is truncated at 40k chars.",
140
+ inputSchema: {
141
+ type: "object",
142
+ properties: { path: { type: "string", description: "companies/<slug>/<file>" } },
143
+ required: ["path"],
144
+ additionalProperties: false,
145
+ },
146
+ handler: async (args) => {
147
+ const p = assertVaultPath(str(args, "path", { required: true, max: 1000 }));
148
+ return fromRun(await clients.runHq(["files", "cat", p]), "hq files cat");
149
+ },
150
+ },
151
+ {
152
+ name: "hq_secrets_list",
153
+ description: "List the NAMES of company secrets this agent can use. Values are never returned.",
154
+ inputSchema: {
155
+ type: "object",
156
+ properties: { prefix: { type: "string", description: "Optional path prefix, e.g. DEV" } },
157
+ additionalProperties: false,
158
+ },
159
+ handler: async (args) => {
160
+ const prefix = str(args, "prefix", { max: 200 });
161
+ const hqArgs = ["secrets", "--company", company, "list"];
162
+ if (prefix)
163
+ hqArgs.push("--prefix", prefix);
164
+ return fromRun(await clients.runHq(hqArgs), "hq secrets list");
165
+ },
166
+ },
167
+ {
168
+ name: "hq_secrets_exec",
169
+ description: "Run a command with the named company secrets injected as environment variables. " +
170
+ "Returns the command's exit code and output only — never the secret values. " +
171
+ "Do not run commands that print their environment.",
172
+ inputSchema: {
173
+ type: "object",
174
+ properties: {
175
+ secrets: { type: "array", items: { type: "string" }, description: "Secret names to inject (UPPER_SNAKE, optional / path)" },
176
+ command: { type: "array", items: { type: "string" }, description: "argv of the command to run" },
177
+ },
178
+ required: ["secrets", "command"],
179
+ additionalProperties: false,
180
+ },
181
+ handler: async (args) => {
182
+ const secrets = strList(args, "secrets");
183
+ for (const s of secrets) {
184
+ if (!SECRET_NAME.test(s))
185
+ throw new McpToolInputError(`invalid secret name ${JSON.stringify(s)}`);
186
+ }
187
+ const command = strList(args, "command");
188
+ if (/^(env|printenv|export|set)$/.test(command[0])) {
189
+ throw new McpToolInputError("refusing to run an environment-dumping command under hq_secrets_exec");
190
+ }
191
+ const r = await clients.runHq(["secrets", "--company", company, "exec", "--only", secrets.join(","), "--", ...command]);
192
+ const out = [r.stdout.trim(), r.stderr.trim()].filter(Boolean).join("\n");
193
+ const text = clampOutput(`exit ${r.code}\n${out}`);
194
+ return r.code === 0 ? textResult(text) : errorResult(text);
195
+ },
196
+ },
197
+ {
198
+ name: "hq_dm_send",
199
+ description: "Send an HQ direct message as this agent to a person (email or prs_…) or agent (agt_…).",
200
+ inputSchema: {
201
+ type: "object",
202
+ properties: {
203
+ to: { type: "string", description: "Email, prs_… or agt_… uid" },
204
+ message: { type: "string" },
205
+ },
206
+ required: ["to", "message"],
207
+ additionalProperties: false,
208
+ },
209
+ handler: async (args) => {
210
+ const to = str(args, "to", { required: true, max: 320 }).trim();
211
+ const message = str(args, "message", { required: true, max: 20_000 }).trim();
212
+ const body = /^[^\s]+@[^\s]+$/.test(to)
213
+ ? { toEmail: to.toLowerCase(), body: message }
214
+ : /^(prs|agt)_[A-Za-z0-9_-]+$/.test(to)
215
+ ? { toPersonUid: to, body: message }
216
+ : (() => {
217
+ throw new McpToolInputError('"to" must be an email, prs_… or agt_… uid');
218
+ })();
219
+ const token = await clients.getToken();
220
+ const res = await clients.apiJson(token, "/v1/notify/dm", { method: "POST", body });
221
+ if (res.status < 200 || res.status >= 300) {
222
+ return errorResult(`DM send failed (${res.status}): ${describeError(res.body)}`);
223
+ }
224
+ return textResult(`sent to ${to}`);
225
+ },
226
+ },
227
+ {
228
+ name: "hq_inbox_read",
229
+ description: "Read this agent's recent incoming direct messages (newest first). Optionally only unread.",
230
+ inputSchema: {
231
+ type: "object",
232
+ properties: {
233
+ limit: { type: "integer", minimum: 1, maximum: 100 },
234
+ unread_only: { type: "boolean" },
235
+ },
236
+ additionalProperties: false,
237
+ },
238
+ handler: async (args) => {
239
+ const limit = int(args, "limit", { min: 1, max: 100, fallback: 20 });
240
+ const unreadOnly = args.unread_only === true;
241
+ const token = await clients.getToken();
242
+ const res = await clients.apiJson(token, "/v1/notify/inbox", { query: { limit: String(limit) } });
243
+ if (res.status !== 200)
244
+ return errorResult(`inbox read failed (${res.status}): ${describeError(res.body)}`);
245
+ const events = Array.isArray(res.body?.events)
246
+ ? (res.body.events)
247
+ : [];
248
+ const rows = events
249
+ .filter((e) => !unreadOnly || !e.acknowledgedAt)
250
+ .map((e) => ({
251
+ eventId: e.eventId,
252
+ from: e.fromDisplayName ?? e.fromEmail ?? e.fromPersonUid ?? "unknown",
253
+ fromUid: e.fromPersonUid,
254
+ at: e.createdAt,
255
+ unread: !e.acknowledgedAt,
256
+ body: e.body,
257
+ ...(e.prompt ? { prompt: e.prompt } : {}),
258
+ }));
259
+ return textResult(clampOutput(JSON.stringify({ count: rows.length, messages: rows }, null, 2)));
260
+ },
261
+ },
262
+ {
263
+ name: "hq_work_mesh_status",
264
+ description: "Live read of what the team is working on right now (work-mesh session status for this agent's company).",
265
+ inputSchema: { type: "object", properties: {}, additionalProperties: false },
266
+ handler: async () => fromRun(await clients.runHq(["mesh", "session", "status", "--company", company, "--json"]), "hq mesh session status"),
267
+ },
268
+ ];
269
+ }
270
+ function describeError(body) {
271
+ if (body && typeof body === "object") {
272
+ const b = body;
273
+ const code = typeof b.code === "string" ? `${b.code}: ` : "";
274
+ const msg = typeof b.message === "string" ? b.message : typeof b.error === "string" ? b.error : "";
275
+ if (code || msg)
276
+ return `${code}${msg}`;
277
+ }
278
+ return "request failed";
279
+ }
280
+ //# sourceMappingURL=tools.js.map