@indigoai-us/hq-cli 5.61.0 → 5.62.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 (80) hide show
  1. package/dist/commands/agents.d.ts +109 -0
  2. package/dist/commands/agents.js +385 -0
  3. package/dist/commands/db-migrate.d.ts +6 -0
  4. package/dist/commands/db-migrate.js +42 -0
  5. package/dist/commands/db-provision.d.ts +15 -0
  6. package/dist/commands/db-provision.js +78 -0
  7. package/dist/commands/db-sql.d.ts +9 -0
  8. package/dist/commands/db-sql.js +81 -0
  9. package/dist/commands/db-status.d.ts +7 -0
  10. package/dist/commands/db-status.js +70 -0
  11. package/dist/commands/db.d.ts +9 -0
  12. package/dist/commands/db.js +23 -0
  13. package/dist/commands/integrations.d.ts +78 -0
  14. package/dist/commands/integrations.js +309 -0
  15. package/dist/commands/members.js +4 -4
  16. package/dist/commands/outposts.d.ts +60 -0
  17. package/dist/commands/outposts.js +255 -0
  18. package/dist/commands/secrets.d.ts +8 -0
  19. package/dist/commands/secrets.js +23 -8
  20. package/dist/commands/skill.d.ts +153 -0
  21. package/dist/commands/skill.js +593 -0
  22. package/dist/commands/workers.d.ts +48 -0
  23. package/dist/commands/workers.js +229 -0
  24. package/dist/lib/db/control-plane.d.ts +45 -0
  25. package/dist/lib/db/control-plane.js +81 -0
  26. package/dist/lib/db/local.d.ts +49 -0
  27. package/dist/lib/db/local.js +106 -0
  28. package/dist/lib/db/migrate.d.ts +41 -0
  29. package/dist/lib/db/migrate.js +104 -0
  30. package/dist/lib/db/paths.d.ts +56 -0
  31. package/dist/lib/db/paths.js +103 -0
  32. package/dist/lib/db/remote-engine.d.ts +58 -0
  33. package/dist/lib/db/remote-engine.js +90 -0
  34. package/dist/lib/db/remote-sql.d.ts +22 -0
  35. package/dist/lib/db/remote-sql.js +39 -0
  36. package/dist/lib/db/sql.d.ts +49 -0
  37. package/dist/lib/db/sql.js +132 -0
  38. package/dist/main.js +27 -2
  39. package/dist/utils/cognito-session.js +3 -3
  40. package/dist/utils/sandbox-runner-client.js +3 -3
  41. package/package.json +9 -1
  42. package/pnpm-workspace.yaml +2 -0
  43. package/src/commands/agents.test.ts +297 -0
  44. package/src/commands/agents.ts +561 -0
  45. package/src/commands/db-migrate.ts +55 -0
  46. package/src/commands/db-provision.ts +102 -0
  47. package/src/commands/db-sql.ts +124 -0
  48. package/src/commands/db-status.ts +100 -0
  49. package/src/commands/db.ts +26 -0
  50. package/src/commands/integrations.test.ts +284 -0
  51. package/src/commands/integrations.ts +438 -0
  52. package/src/commands/members.ts +2 -2
  53. package/src/commands/outposts.test.ts +177 -0
  54. package/src/commands/outposts.ts +338 -0
  55. package/src/commands/secrets.parse-destination.test.ts +38 -0
  56. package/src/commands/secrets.test.ts +24 -0
  57. package/src/commands/secrets.ts +30 -10
  58. package/src/commands/skill.test.ts +770 -0
  59. package/src/commands/skill.ts +796 -0
  60. package/src/commands/workers.test.ts +158 -0
  61. package/src/commands/workers.ts +298 -0
  62. package/src/lib/db/control-plane.test.ts +59 -0
  63. package/src/lib/db/control-plane.ts +113 -0
  64. package/src/lib/db/local.test.ts +81 -0
  65. package/src/lib/db/local.ts +148 -0
  66. package/src/lib/db/migrate.test.ts +133 -0
  67. package/src/lib/db/migrate.ts +137 -0
  68. package/src/lib/db/paths.test.ts +112 -0
  69. package/src/lib/db/paths.ts +128 -0
  70. package/src/lib/db/remote-engine.test.ts +44 -0
  71. package/src/lib/db/remote-engine.ts +148 -0
  72. package/src/lib/db/remote-sql.test.ts +32 -0
  73. package/src/lib/db/remote-sql.ts +62 -0
  74. package/src/lib/db/sql.test.ts +106 -0
  75. package/src/lib/db/sql.ts +192 -0
  76. package/src/main.ts +31 -0
  77. package/src/utils/cognito-session.ts +1 -1
  78. package/src/utils/sandbox-runner-client.ts +1 -1
  79. package/test/commands/db-tenant-isolation.test.ts +94 -0
  80. package/test/commands/db.test.ts +85 -0
@@ -1,6 +1,6 @@
1
1
 
2
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="e77a0f25-ac1a-574f-a1e4-985c6b3306d7")}catch(e){}}();
3
- const DEFAULT_SANDBOX_RUNNER_URL = "https://hqapi.getindigo.ai/sandbox";
2
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="590fc03f-67ee-5f1c-820f-d0bb5e256d78")}catch(e){}}();
3
+ const DEFAULT_SANDBOX_RUNNER_URL = "https://hqapi.hq.computer/sandbox";
4
4
  const RETRYABLE_STATUS_CODES = new Set([408, 425, 429, 500, 502, 503, 504]);
5
5
  const DEFAULT_RETRY_OPTIONS = {
6
6
  maxAttempts: 8,
@@ -180,4 +180,4 @@ export class SandboxRunnerClient {
180
180
  }
181
181
  }
182
182
  //# sourceMappingURL=sandbox-runner-client.js.map
183
- //# debugId=e77a0f25-ac1a-574f-a1e4-985c6b3306d7
183
+ //# debugId=590fc03f-67ee-5f1c-820f-d0bb5e256d78
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.61.0",
3
+ "version": "5.62.0",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -12,6 +12,7 @@
12
12
  "typecheck": "tsc --noEmit",
13
13
  "lint": "eslint .",
14
14
  "test": "vitest run",
15
+ "test:db": "vitest run src/lib/db test/commands/db.test.ts test/commands/db-tenant-isolation.test.ts",
15
16
  "test:e2e": "vitest run --config vitest.e2e.config.ts",
16
17
  "coverage": "vitest run --coverage --coverage.reporter=text-summary --coverage.reporter=json-summary",
17
18
  "vitest": "vitest",
@@ -21,6 +22,7 @@
21
22
  "@indigoai-us/hq-cloud": "^6.12.1",
22
23
  "@indigoai-us/hq-onboarding": "^0.1.0",
23
24
  "@sentry/node": "^10.49.0",
25
+ "better-sqlite3": "^12.11.1",
24
26
  "chalk": "^5.3.0",
25
27
  "commander": "^12.1.0",
26
28
  "js-yaml": "^4.1.0",
@@ -33,6 +35,7 @@
33
35
  "devDependencies": {
34
36
  "@aws-sdk/client-s3": "^3.1049.0",
35
37
  "@eslint/js": "^10.0.1",
38
+ "@types/better-sqlite3": "^7.6.13",
36
39
  "@types/js-yaml": "^4.0.9",
37
40
  "@types/node": "^22.0.0",
38
41
  "@types/semver": "^7.5.8",
@@ -58,5 +61,10 @@
58
61
  "type": "module",
59
62
  "engines": {
60
63
  "node": ">=20.0.0"
64
+ },
65
+ "pnpm": {
66
+ "onlyBuiltDependencies": [
67
+ "better-sqlite3"
68
+ ]
61
69
  }
62
70
  }
@@ -0,0 +1,2 @@
1
+ allowBuilds:
2
+ better-sqlite3: true
@@ -0,0 +1,297 @@
1
+ /**
2
+ * Unit tests for `hq agents` (agents.ts).
3
+ *
4
+ * Mirrors company.test.ts / members.test.ts: mock ensureCognitoToken +
5
+ * getCompanyUid, spy on global fetch, drive through a Commander program, and
6
+ * assert the /v1/agents request shape (method + path + body) plus the guard /
7
+ * validation / exit behavior. `vaultApiFetch` itself is NOT mocked — the spied
8
+ * global fetch verifies the real URL + init the helper produces.
9
+ */
10
+
11
+ import { Command } from "commander";
12
+ import {
13
+ afterEach,
14
+ beforeEach,
15
+ describe,
16
+ expect,
17
+ it,
18
+ vi,
19
+ type MockInstance,
20
+ } from "vitest";
21
+
22
+ vi.mock("../utils/cognito-session.js", async (importOriginal) => {
23
+ const original =
24
+ await importOriginal<typeof import("../utils/cognito-session.js")>();
25
+ return {
26
+ ...original,
27
+ ensureCognitoToken: vi.fn(async () => "test-token"),
28
+ };
29
+ });
30
+
31
+ vi.mock("../utils/vault-api.js", async (importOriginal) => {
32
+ const original = await importOriginal<typeof import("../utils/vault-api.js")>();
33
+ return {
34
+ ...original,
35
+ getCompanyUid: vi.fn(async () => "cmp_acme"),
36
+ };
37
+ });
38
+
39
+ import { ensureCognitoToken } from "../utils/cognito-session.js";
40
+ import { getCompanyUid } from "../utils/vault-api.js";
41
+ import { registerAgentsCommand } from "./agents.js";
42
+
43
+ function jsonResponse(status: number, body: unknown): Response {
44
+ return new Response(JSON.stringify(body), {
45
+ status,
46
+ headers: { "Content-Type": "application/json" },
47
+ });
48
+ }
49
+
50
+ let fetchSpy: MockInstance<typeof fetch>;
51
+ let exitSpy: MockInstance<typeof process.exit>;
52
+ let logSpy: MockInstance<typeof console.log>;
53
+ const mockEnsureCognitoToken = vi.mocked(ensureCognitoToken);
54
+ const mockGetCompanyUid = vi.mocked(getCompanyUid);
55
+
56
+ beforeEach(() => {
57
+ vi.clearAllMocks();
58
+ fetchSpy = vi.spyOn(globalThis, "fetch");
59
+ mockEnsureCognitoToken.mockResolvedValue("test-token");
60
+ mockGetCompanyUid.mockResolvedValue("cmp_acme");
61
+ exitSpy = vi.spyOn(process, "exit").mockImplementation((code?: number) => {
62
+ throw new Error(`process.exit(${code})`);
63
+ }) as unknown as MockInstance<typeof process.exit>;
64
+ logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
65
+ vi.spyOn(console, "error").mockImplementation(() => {});
66
+ });
67
+
68
+ afterEach(() => {
69
+ vi.restoreAllMocks();
70
+ });
71
+
72
+ function buildProgram(): Command {
73
+ const program = new Command();
74
+ program.name("hq").exitOverride();
75
+ registerAgentsCommand(program);
76
+ return program;
77
+ }
78
+
79
+ async function run(args: string[]): Promise<void> {
80
+ await buildProgram().parseAsync(["node", "hq", ...args]);
81
+ }
82
+
83
+ describe("hq agents list", () => {
84
+ it("GETs /v1/agents with the resolved companyUid", async () => {
85
+ fetchSpy.mockResolvedValueOnce(
86
+ jsonResponse(200, {
87
+ agents: [
88
+ { uid: "agt_1", name: "Ops", slug: "ops", agentStatus: "running" },
89
+ ],
90
+ }),
91
+ );
92
+
93
+ await run(["agents", "--company", "acme", "list"]);
94
+
95
+ expect(fetchSpy).toHaveBeenCalledTimes(1);
96
+ const [url, init] = fetchSpy.mock.calls[0];
97
+ expect(String(url)).toContain("/v1/agents");
98
+ expect(String(url)).toContain("companyUid=cmp_acme");
99
+ expect(init?.method ?? "GET").toBe("GET");
100
+ });
101
+
102
+ it("accepts --company on the subcommand too", async () => {
103
+ fetchSpy.mockResolvedValueOnce(jsonResponse(200, { agents: [] }));
104
+ await run(["agents", "list", "--company", "acme"]);
105
+ expect(mockGetCompanyUid).toHaveBeenCalledWith("test-token", "acme");
106
+ });
107
+
108
+ it("emits raw JSON with --json", async () => {
109
+ const stdoutSpy = vi
110
+ .spyOn(process.stdout, "write")
111
+ .mockImplementation(() => true);
112
+ fetchSpy.mockResolvedValueOnce(
113
+ jsonResponse(200, { agents: [{ uid: "agt_1", name: "Ops", slug: "ops" }] }),
114
+ );
115
+ await run(["agents", "--company", "acme", "list", "--json"]);
116
+ const printed = stdoutSpy.mock.calls.map((c) => String(c[0])).join("\n");
117
+ expect(printed).toContain('"uid": "agt_1"');
118
+ });
119
+
120
+ it("prints a friendly message when agents are not enabled (404)", async () => {
121
+ fetchSpy.mockResolvedValueOnce(jsonResponse(404, { error: "not found" }));
122
+ await run(["agents", "--company", "acme", "list"]);
123
+ const printed = logSpy.mock.calls.map((c) => String(c[0])).join("\n");
124
+ expect(printed).toMatch(/agents feature isn't enabled/i);
125
+ expect(exitSpy).not.toHaveBeenCalled();
126
+ });
127
+ });
128
+
129
+ describe("hq agents rename", () => {
130
+ it("PATCHes the profile with displayName", async () => {
131
+ fetchSpy.mockResolvedValueOnce(
132
+ jsonResponse(200, {
133
+ uid: "agt_1",
134
+ profile: { displayName: "New" },
135
+ slackUpdated: false,
136
+ }),
137
+ );
138
+
139
+ await run(["agents", "--company", "acme", "rename", "agt_1", "New"]);
140
+
141
+ const [url, init] = fetchSpy.mock.calls[0];
142
+ expect(String(url)).toContain("/v1/agents/agt_1/profile");
143
+ expect(init?.method).toBe("PATCH");
144
+ expect(JSON.parse(init?.body as string)).toEqual({ displayName: "New" });
145
+ });
146
+ });
147
+
148
+ describe("hq agents set", () => {
149
+ it("PATCHes only the supplied profile fields", async () => {
150
+ fetchSpy.mockResolvedValueOnce(
151
+ jsonResponse(200, { uid: "agt_1", profile: {}, slackUpdated: true }),
152
+ );
153
+
154
+ await run([
155
+ "agents",
156
+ "--company",
157
+ "acme",
158
+ "set",
159
+ "agt_1",
160
+ "--title",
161
+ "Chief of Staff",
162
+ ]);
163
+
164
+ const [url, init] = fetchSpy.mock.calls[0];
165
+ expect(String(url)).toContain("/v1/agents/agt_1/profile");
166
+ expect(init?.method).toBe("PATCH");
167
+ const sent = JSON.parse(init?.body as string);
168
+ expect(sent).toEqual({ title: "Chief of Staff" });
169
+ });
170
+
171
+ it("requires at least one field", async () => {
172
+ await expect(
173
+ run(["agents", "--company", "acme", "set", "agt_1"]),
174
+ ).rejects.toThrow("process.exit(1)");
175
+ expect(fetchSpy).not.toHaveBeenCalled();
176
+ });
177
+ });
178
+
179
+ describe("hq agents config", () => {
180
+ it("PATCHes runtime-config with model/effort/tier", async () => {
181
+ fetchSpy.mockResolvedValueOnce(
182
+ jsonResponse(200, { uid: "agt_1", codexModel: "gpt-5.5", applied: true }),
183
+ );
184
+
185
+ await run([
186
+ "agents",
187
+ "--company",
188
+ "acme",
189
+ "config",
190
+ "agt_1",
191
+ "--model",
192
+ "gpt-5.5",
193
+ "--effort",
194
+ "high",
195
+ "--tier",
196
+ "priority",
197
+ ]);
198
+
199
+ const [url, init] = fetchSpy.mock.calls[0];
200
+ expect(String(url)).toContain("/v1/agents/agt_1/runtime-config");
201
+ expect(init?.method).toBe("PATCH");
202
+ expect(JSON.parse(init?.body as string)).toEqual({
203
+ codexModel: "gpt-5.5",
204
+ codexReasoningEffort: "high",
205
+ codexServiceTier: "priority",
206
+ });
207
+ });
208
+
209
+ it("rejects an invalid --effort", async () => {
210
+ await expect(
211
+ run([
212
+ "agents",
213
+ "--company",
214
+ "acme",
215
+ "config",
216
+ "agt_1",
217
+ "--effort",
218
+ "turbo",
219
+ ]),
220
+ ).rejects.toThrow("process.exit(1)");
221
+ expect(fetchSpy).not.toHaveBeenCalled();
222
+ });
223
+
224
+ it("rejects an invalid --tier", async () => {
225
+ await expect(
226
+ run([
227
+ "agents",
228
+ "--company",
229
+ "acme",
230
+ "config",
231
+ "agt_1",
232
+ "--tier",
233
+ "turbo",
234
+ ]),
235
+ ).rejects.toThrow("process.exit(1)");
236
+ expect(fetchSpy).not.toHaveBeenCalled();
237
+ });
238
+
239
+ it("requires at least one field", async () => {
240
+ await expect(
241
+ run(["agents", "--company", "acme", "config", "agt_1"]),
242
+ ).rejects.toThrow("process.exit(1)");
243
+ expect(fetchSpy).not.toHaveBeenCalled();
244
+ });
245
+ });
246
+
247
+ describe("hq agents start/stop/retry", () => {
248
+ it("POSTs /start", async () => {
249
+ fetchSpy.mockResolvedValueOnce(jsonResponse(200, { uid: "agt_1" }));
250
+ await run(["agents", "--company", "acme", "start", "agt_1"]);
251
+ const [url, init] = fetchSpy.mock.calls[0];
252
+ expect(String(url)).toContain("/v1/agents/agt_1/start");
253
+ expect(init?.method).toBe("POST");
254
+ });
255
+
256
+ it("POSTs /stop", async () => {
257
+ fetchSpy.mockResolvedValueOnce(jsonResponse(200, { uid: "agt_1" }));
258
+ await run(["agents", "--company", "acme", "stop", "agt_1"]);
259
+ expect(String(fetchSpy.mock.calls[0][0])).toContain("/v1/agents/agt_1/stop");
260
+ });
261
+
262
+ it("POSTs /retry", async () => {
263
+ fetchSpy.mockResolvedValueOnce(jsonResponse(200, { uid: "agt_1" }));
264
+ await run(["agents", "--company", "acme", "retry", "agt_1"]);
265
+ expect(String(fetchSpy.mock.calls[0][0])).toContain("/v1/agents/agt_1/retry");
266
+ });
267
+ });
268
+
269
+ describe("hq agents rm", () => {
270
+ it("refuses without --yes and does not call the API", async () => {
271
+ await expect(
272
+ run(["agents", "--company", "acme", "rm", "agt_1"]),
273
+ ).rejects.toThrow("process.exit(1)");
274
+ expect(fetchSpy).not.toHaveBeenCalled();
275
+ });
276
+
277
+ it("DELETEs the agent with --yes", async () => {
278
+ fetchSpy.mockResolvedValueOnce(
279
+ jsonResponse(200, { uid: "agt_1", terminal: true }),
280
+ );
281
+ await run(["agents", "--company", "acme", "rm", "agt_1", "--yes"]);
282
+ const [url, init] = fetchSpy.mock.calls[0];
283
+ expect(String(url)).toContain("/v1/agents/agt_1");
284
+ expect(init?.method).toBe("DELETE");
285
+ });
286
+ });
287
+
288
+ describe("hq agents status", () => {
289
+ it("exits 1 on a non-2xx and surfaces the error", async () => {
290
+ fetchSpy.mockResolvedValueOnce(
291
+ jsonResponse(403, { error: "forbidden" }),
292
+ );
293
+ await expect(
294
+ run(["agents", "--company", "acme", "status", "agt_1"]),
295
+ ).rejects.toThrow("process.exit(1)");
296
+ });
297
+ });