@indigoai-us/hq-cli 5.57.0 → 5.58.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.
@@ -1,5 +1,5 @@
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]="18312d3d-7daf-5faf-ac29-c82ce7bd0b29")}catch(e){}}();
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]="12b04dfd-a264-56c7-bfbb-a448c35689b3")}catch(e){}}();
3
3
  import chalk from "chalk";
4
4
  import * as readline from "node:readline";
5
5
  import { spawn } from "node:child_process";
@@ -217,14 +217,8 @@ export function scrubSandboxOutput(text, secretNames = []) {
217
217
  .replace(/\b[A-Za-z0-9+/]{40,}={0,2}\b/g, "[REDACTED]");
218
218
  }
219
219
  function renderSandboxJobResult(job, secretNames) {
220
- if (job.stdout) {
221
- process.stdout.write(scrubSandboxOutput(job.stdout, secretNames));
222
- }
223
- if (job.stderr) {
224
- process.stderr.write(scrubSandboxOutput(job.stderr, secretNames));
225
- }
226
- if (job.logsTail) {
227
- process.stderr.write(scrubSandboxOutput(job.logsTail, secretNames));
220
+ if (job.output) {
221
+ process.stdout.write(scrubSandboxOutput(job.output, secretNames));
228
222
  }
229
223
  }
230
224
  function normalizePolicyRecord(secretPath, data) {
@@ -872,47 +866,46 @@ export function registerSecretsCommand(program) {
872
866
  });
873
867
  secrets
874
868
  .command("sandbox")
875
- .description("Run a skill in the hosted sandbox with secrets injected server-side")
869
+ .description("Run a command in the hosted sandbox with named secrets injected as env vars; open egress, secrets never touch this machine")
876
870
  .option("--company <slug>", "Company slug (resolves to companyUid)")
877
871
  .option("--personal", "Operate on the caller's personal vault (no sharing)")
878
- .option("--only <keys>", "Comma-separated list of secret names the skill may use")
879
- .option("--script <path>", "Attach local script identity for script-locked secrets")
872
+ .option("--only <keys>", "Comma-separated list of secret names to inject (required)")
880
873
  .allowUnknownOption(true)
881
874
  .action(async (opts, cmd) => {
882
875
  try {
883
- const dashIndex = process.argv.indexOf("--");
884
- const rawArgs = dashIndex !== -1 ? process.argv.slice(dashIndex + 1) : cmd.args;
885
- if (rawArgs.length === 0) {
886
- console.error(chalk.red("Error: no skill specified. Usage: hq secrets sandbox [--company X] [--only KEY1,KEY2] -- <skill> [args...]"));
876
+ if (!opts.only || opts.only.trim().length === 0) {
877
+ console.error(chalk.red("Error: --only is required and must name at least one secret."));
878
+ process.exit(1);
879
+ }
880
+ const rawArgs = cmd.args;
881
+ const command = rawArgs.join(" ").trim();
882
+ if (command.length === 0) {
883
+ console.error(chalk.red("Error: no command specified. Usage: hq secrets sandbox --company <slug> --only KEY1,KEY2 -- <command>"));
887
884
  process.exit(1);
888
885
  }
889
- const [skillId, ...skillArgs] = rawArgs;
890
- const keys = opts.only ? parseSecretNameList(opts.only) : [];
886
+ if (command.length > 8192) {
887
+ console.error(chalk.red("Error: sandbox command exceeds the 8192 character limit."));
888
+ process.exit(1);
889
+ }
890
+ const keys = parseSecretNameList(opts.only);
891
891
  const token = await ensureCognitoToken();
892
892
  const scope = scopeOpts(mergeScopeOpts(secrets.opts(), opts));
893
893
  const companyUid = await getEntityUid(token, scope);
894
- const usage = await buildSecretUsage("sandbox", opts.script);
895
894
  const client = new SandboxRunnerClient();
896
895
  const started = await client.startJob(token, {
897
- skillId,
898
896
  companyUid,
899
- args: skillArgs.length > 0 ? { argv: skillArgs } : undefined,
900
- companySlug: scope.companySlug,
901
- only: keys.length > 0 ? keys : undefined,
902
- usage,
897
+ secretNames: keys,
898
+ command,
903
899
  });
904
900
  const job = started.status === "succeeded" || started.status === "failed"
905
901
  ? await client.getJob(token, started.jobId)
906
902
  : await client.pollJob(token, started.jobId);
907
903
  renderSandboxJobResult(job, keys);
908
- if (job.status === "failed") {
909
- if (job.error) {
910
- console.error(chalk.red("Sandbox job failed:"), scrubSandboxOutput(job.error, keys));
911
- }
912
- process.exit(job.exitCode && job.exitCode > 0 ? job.exitCode : 1);
913
- }
914
- if (job.exitCode && job.exitCode !== 0) {
915
- process.exit(job.exitCode);
904
+ const exitCode = typeof job.exitCode === "number" ? job.exitCode : undefined;
905
+ if (job.success === false || (exitCode !== undefined && exitCode !== 0) || job.status === "failed") {
906
+ const code = exitCode && exitCode !== 0 ? exitCode : 1;
907
+ console.error(chalk.red(`Sandbox command failed with exit code ${code}.`));
908
+ process.exit(code);
916
909
  }
917
910
  }
918
911
  catch (err) {
@@ -1261,4 +1254,4 @@ export function registerSecretsCommand(program) {
1261
1254
  });
1262
1255
  }
1263
1256
  //# sourceMappingURL=secrets.js.map
1264
- //# debugId=18312d3d-7daf-5faf-ac29-c82ce7bd0b29
1257
+ //# debugId=12b04dfd-a264-56c7-bfbb-a448c35689b3
@@ -1,11 +1,8 @@
1
1
  export type SandboxRunnerState = "queued" | "running" | "succeeded" | "failed";
2
2
  export interface SandboxRunnerStartRequest {
3
- skillId: string;
4
3
  companyUid: string;
5
- args?: Record<string, unknown>;
6
- companySlug?: string;
7
- only?: string[];
8
- usage?: unknown;
4
+ secretNames: string[];
5
+ command: string;
9
6
  }
10
7
  export interface SandboxRunnerStartResponse {
11
8
  jobId: string;
@@ -14,11 +11,9 @@ export interface SandboxRunnerStartResponse {
14
11
  export interface SandboxRunnerJob {
15
12
  jobId: string;
16
13
  status: SandboxRunnerState;
17
- stdout?: string;
18
- stderr?: string;
19
- logsTail?: string;
14
+ output?: string;
20
15
  exitCode?: number;
21
- error?: string;
16
+ success?: boolean;
22
17
  }
23
18
  export interface SandboxRunnerClientOptions {
24
19
  baseUrl?: string;
@@ -1,5 +1,5 @@
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]="8203259b-d3ae-5c9b-845e-f4c1470c30f3")}catch(e){}}();
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]="3cbec729-3b2b-5d53-9e90-5680dd725b7d")}catch(e){}}();
3
3
  const DEFAULT_SANDBOX_RUNNER_URL = "https://hqapi.getindigo.ai/sandbox";
4
4
  function normalizeBaseUrl(baseUrl) {
5
5
  return baseUrl.replace(/\/+$/, "");
@@ -25,21 +25,14 @@ function normalizeJob(body, jobIdFallback) {
25
25
  if (!isSandboxRunnerState(status)) {
26
26
  throw new Error("Sandbox Runner returned an invalid job status.");
27
27
  }
28
- const output = typeof body.stdout === "string"
29
- ? body.stdout
30
- : typeof body.output === "string"
31
- ? body.output
32
- : undefined;
33
28
  return {
34
29
  jobId: typeof body.jobId === "string" && body.jobId.length > 0
35
30
  ? body.jobId
36
31
  : jobIdFallback ?? requireString(body, "jobId"),
37
32
  status,
38
- stdout: output,
39
- stderr: typeof body.stderr === "string" ? body.stderr : undefined,
40
- logsTail: typeof body.logsTail === "string" ? body.logsTail : undefined,
33
+ output: typeof body.output === "string" ? body.output : undefined,
41
34
  exitCode: typeof body.exitCode === "number" ? body.exitCode : undefined,
42
- error: typeof body.error === "string" ? body.error : undefined,
35
+ success: typeof body.success === "boolean" ? body.success : undefined,
43
36
  };
44
37
  }
45
38
  function delay(ms) {
@@ -110,4 +103,4 @@ export class SandboxRunnerClient {
110
103
  }
111
104
  }
112
105
  //# sourceMappingURL=sandbox-runner-client.js.map
113
- //# debugId=8203259b-d3ae-5c9b-845e-f4c1470c30f3
106
+ //# debugId=3cbec729-3b2b-5d53-9e90-5680dd725b7d
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.57.0",
3
+ "version": "5.58.0",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -104,6 +104,7 @@ function jsonRes(body: unknown, status = 200): Response {
104
104
  describe("secrets sandbox", () => {
105
105
  let stdoutSpy: MockInstance<typeof process.stdout.write>;
106
106
  let stderrSpy: MockInstance<typeof process.stderr.write>;
107
+ let exitSpy: MockInstance<typeof process.exit>;
107
108
  let startJobSpy: MockInstance<SandboxRunnerClient["startJob"]>;
108
109
  let pollJobSpy: MockInstance<SandboxRunnerClient["pollJob"]>;
109
110
  let getJobSpy: MockInstance<SandboxRunnerClient["getJob"]>;
@@ -119,9 +120,14 @@ describe("secrets sandbox", () => {
119
120
  .mockResolvedValue({
120
121
  jobId: "job_123",
121
122
  status: "succeeded",
122
- stdout: "done\n",
123
+ output: "done\n",
124
+ exitCode: 0,
125
+ success: true,
123
126
  });
124
127
  getJobSpy = vi.spyOn(SandboxRunnerClient.prototype, "getJob");
128
+ exitSpy = vi.spyOn(process, "exit").mockImplementation((() => {
129
+ throw new Error("__exit__");
130
+ }) as never);
125
131
  });
126
132
 
127
133
  it("registers the sandbox channel value", () => {
@@ -129,7 +135,7 @@ describe("secrets sandbox", () => {
129
135
  expect(channel).toBe("sandbox");
130
136
  });
131
137
 
132
- it("parses --company, --only, and the -- <skill> separator", async () => {
138
+ it("parses --company, --only, and joins args after -- into a command", async () => {
133
139
  const program = buildProgram();
134
140
  await program.parseAsync([
135
141
  "node",
@@ -141,22 +147,59 @@ describe("secrets sandbox", () => {
141
147
  "--only",
142
148
  "API_KEY,OTHER_KEY",
143
149
  "--",
144
- "pull-triple-whale",
150
+ "node",
151
+ "-e",
152
+ "console.log('hi')",
145
153
  "--days",
146
154
  "7",
147
155
  ]);
148
156
 
149
157
  expect(startJobSpy).toHaveBeenCalledWith("test-token", {
150
- skillId: "pull-triple-whale",
151
158
  companyUid: "prs_alice",
152
- args: { argv: ["--days", "7"] },
153
- companySlug: "acme",
154
- only: ["API_KEY", "OTHER_KEY"],
155
- usage: { channel: "sandbox" },
159
+ secretNames: ["API_KEY", "OTHER_KEY"],
160
+ command: "node -e console.log('hi') --days 7",
156
161
  });
157
162
  expect(stdoutSpy).toHaveBeenCalledWith("done\n");
158
163
  });
159
164
 
165
+ it("requires --only", async () => {
166
+ const program = buildProgram();
167
+
168
+ await expect(
169
+ program.parseAsync([
170
+ "node",
171
+ "hq",
172
+ "secrets",
173
+ "sandbox",
174
+ "--",
175
+ "env",
176
+ ]),
177
+ ).rejects.toThrow("__exit__");
178
+
179
+ expect(exitSpy).toHaveBeenCalledWith(1);
180
+ expect(errSpy.mock.calls.flat().join(" ")).toMatch(/--only is required/i);
181
+ expect(startJobSpy).not.toHaveBeenCalled();
182
+ });
183
+
184
+ it("requires a command after --", async () => {
185
+ const program = buildProgram();
186
+
187
+ await expect(
188
+ program.parseAsync([
189
+ "node",
190
+ "hq",
191
+ "secrets",
192
+ "sandbox",
193
+ "--only",
194
+ "API_KEY",
195
+ ]),
196
+ ).rejects.toThrow("__exit__");
197
+
198
+ expect(exitSpy).toHaveBeenCalledWith(1);
199
+ expect(errSpy.mock.calls.flat().join(" ")).toMatch(/no command specified/i);
200
+ expect(startJobSpy).not.toHaveBeenCalled();
201
+ });
202
+
160
203
  it("resolves identity before posting the runner job", async () => {
161
204
  const program = buildProgram();
162
205
  await program.parseAsync([
@@ -166,8 +209,10 @@ describe("secrets sandbox", () => {
166
209
  "--company",
167
210
  "parent-co",
168
211
  "sandbox",
212
+ "--only",
213
+ "API_KEY",
169
214
  "--",
170
- "my-skill",
215
+ "env",
171
216
  ]);
172
217
 
173
218
  expect(ensureCognitoToken).toHaveBeenCalled();
@@ -175,58 +220,20 @@ describe("secrets sandbox", () => {
175
220
  personal: false,
176
221
  companySlug: "parent-co",
177
222
  });
178
- expect(startJobSpy).toHaveBeenCalledWith(
179
- "test-token",
180
- expect.objectContaining({
181
- skillId: "my-skill",
182
- companyUid: "prs_alice",
183
- companySlug: "parent-co",
184
- usage: { channel: "sandbox" },
185
- }),
186
- );
187
- });
188
-
189
- it("can attach script attestation metadata to sandbox usage", async () => {
190
- const scriptPath = join(tempDir, "sandbox-script.sh");
191
- const scriptBody = "#!/usr/bin/env bash\necho sandbox\n";
192
- writeFileSync(scriptPath, scriptBody);
193
- const expectedSha = createHash("sha256").update(scriptBody).digest("hex");
194
-
195
- const program = buildProgram();
196
- await program.parseAsync([
197
- "node",
198
- "hq",
199
- "secrets",
200
- "sandbox",
201
- "--script",
202
- scriptPath,
203
- "--",
204
- "my-skill",
205
- ]);
206
-
207
- expect(startJobSpy).toHaveBeenCalledWith(
208
- "test-token",
209
- expect.objectContaining({
210
- usage: {
211
- channel: "sandbox",
212
- script: {
213
- scriptId: scriptPath,
214
- path: scriptPath,
215
- sha256: expectedSha,
216
- attestationLevel: "self-asserted-hash",
217
- },
218
- },
219
- }),
220
- );
223
+ expect(startJobSpy).toHaveBeenCalledWith("test-token", {
224
+ companyUid: "prs_alice",
225
+ secretNames: ["API_KEY"],
226
+ command: "env",
227
+ });
221
228
  });
222
229
 
223
- it("never batch-loads or prints a plaintext secret value locally", async () => {
230
+ it("prints server-scrubbed output and never batch-loads secrets locally", async () => {
224
231
  pollJobSpy.mockResolvedValueOnce({
225
232
  jobId: "job_123",
226
233
  status: "succeeded",
227
- stdout: "token sk-test-secret-value\nAPI_KEY=sk-another-secret\n",
228
- stderr: "debug sk-stderr-secret\n",
229
- logsTail: "tail sk-log-secret\n",
234
+ output: "token [REDACTED]\nAPI_KEY=[REDACTED]\n",
235
+ exitCode: 0,
236
+ success: true,
230
237
  });
231
238
 
232
239
  const program = buildProgram();
@@ -253,37 +260,36 @@ describe("secrets sandbox", () => {
253
260
  ...logSpy.mock.calls.flat().map(String),
254
261
  ...errSpy.mock.calls.flat().map(String),
255
262
  ].join("\n");
256
- expect(rendered).not.toContain("sk-test-secret-value");
257
- expect(rendered).not.toContain("sk-another-secret");
258
- expect(rendered).not.toContain("sk-stderr-secret");
259
- expect(rendered).not.toContain("sk-log-secret");
260
263
  expect(rendered).toContain("[REDACTED]");
264
+ expect(stdoutSpy).toHaveBeenCalledWith("token [REDACTED]\nAPI_KEY=[REDACTED]\n");
261
265
  });
262
266
 
263
- it("renders scrubbed stdout, stderr, and log tails", async () => {
267
+ it("exits non-zero when the sandbox command fails", async () => {
264
268
  pollJobSpy.mockResolvedValueOnce({
265
269
  jobId: "job_123",
266
- status: "succeeded",
267
- stdout: "ok\nAPI_KEY=sk-stdout-secret\n",
268
- stderr: "warn sk-stderr-secret\n",
269
- logsTail: "tail sk-log-secret\n",
270
+ status: "failed",
271
+ output: "boom\n",
272
+ exitCode: 7,
273
+ success: false,
270
274
  });
271
275
 
272
276
  const program = buildProgram();
273
- await program.parseAsync([
274
- "node",
275
- "hq",
276
- "secrets",
277
- "sandbox",
278
- "--only",
279
- "API_KEY",
280
- "--",
281
- "my-skill",
282
- ]);
277
+ await expect(
278
+ program.parseAsync([
279
+ "node",
280
+ "hq",
281
+ "secrets",
282
+ "sandbox",
283
+ "--only",
284
+ "API_KEY",
285
+ "--",
286
+ "my-skill",
287
+ ]),
288
+ ).rejects.toThrow("__exit__");
283
289
 
284
- expect(stdoutSpy).toHaveBeenCalledWith("ok\nAPI_KEY=[REDACTED]\n");
285
- expect(stderrSpy).toHaveBeenCalledWith("warn [REDACTED]\n");
286
- expect(stderrSpy).toHaveBeenCalledWith("tail [REDACTED]\n");
290
+ expect(stdoutSpy).toHaveBeenCalledWith("boom\n");
291
+ expect(errSpy.mock.calls.flat().join(" ")).toMatch(/exit code 7/i);
292
+ expect(exitSpy).toHaveBeenCalledWith(7);
287
293
  expect(getJobSpy).not.toHaveBeenCalled();
288
294
  });
289
295
  });
@@ -368,14 +368,8 @@ export function scrubSandboxOutput(text: string, secretNames: string[] = []): st
368
368
  }
369
369
 
370
370
  function renderSandboxJobResult(job: SandboxRunnerJob, secretNames: string[]): void {
371
- if (job.stdout) {
372
- process.stdout.write(scrubSandboxOutput(job.stdout, secretNames));
373
- }
374
- if (job.stderr) {
375
- process.stderr.write(scrubSandboxOutput(job.stderr, secretNames));
376
- }
377
- if (job.logsTail) {
378
- process.stderr.write(scrubSandboxOutput(job.logsTail, secretNames));
371
+ if (job.output) {
372
+ process.stdout.write(scrubSandboxOutput(job.output, secretNames));
379
373
  }
380
374
  }
381
375
 
@@ -1249,38 +1243,41 @@ export function registerSecretsCommand(program: Command): void {
1249
1243
 
1250
1244
  secrets
1251
1245
  .command("sandbox")
1252
- .description("Run a skill in the hosted sandbox with secrets injected server-side")
1246
+ .description("Run a command in the hosted sandbox with named secrets injected as env vars; open egress, secrets never touch this machine")
1253
1247
  .option("--company <slug>", "Company slug (resolves to companyUid)")
1254
1248
  .option(
1255
1249
  "--personal",
1256
1250
  "Operate on the caller's personal vault (no sharing)",
1257
1251
  )
1258
- .option("--only <keys>", "Comma-separated list of secret names the skill may use")
1259
- .option("--script <path>", "Attach local script identity for script-locked secrets")
1252
+ .option("--only <keys>", "Comma-separated list of secret names to inject (required)")
1260
1253
  .allowUnknownOption(true)
1261
- .action(async (opts: { company?: string; personal?: boolean; only?: string; script?: string }, cmd: Command) => {
1254
+ .action(async (opts: { company?: string; personal?: boolean; only?: string }, cmd: Command) => {
1262
1255
  try {
1263
- const dashIndex = process.argv.indexOf("--");
1264
- const rawArgs = dashIndex !== -1 ? process.argv.slice(dashIndex + 1) : cmd.args;
1265
- if (rawArgs.length === 0) {
1266
- console.error(chalk.red("Error: no skill specified. Usage: hq secrets sandbox [--company X] [--only KEY1,KEY2] -- <skill> [args...]"));
1256
+ if (!opts.only || opts.only.trim().length === 0) {
1257
+ console.error(chalk.red("Error: --only is required and must name at least one secret."));
1258
+ process.exit(1);
1259
+ }
1260
+
1261
+ const rawArgs = cmd.args;
1262
+ const command = rawArgs.join(" ").trim();
1263
+ if (command.length === 0) {
1264
+ console.error(chalk.red("Error: no command specified. Usage: hq secrets sandbox --company <slug> --only KEY1,KEY2 -- <command>"));
1265
+ process.exit(1);
1266
+ }
1267
+ if (command.length > 8192) {
1268
+ console.error(chalk.red("Error: sandbox command exceeds the 8192 character limit."));
1267
1269
  process.exit(1);
1268
1270
  }
1269
1271
 
1270
- const [skillId, ...skillArgs] = rawArgs;
1271
- const keys = opts.only ? parseSecretNameList(opts.only) : [];
1272
+ const keys = parseSecretNameList(opts.only);
1272
1273
  const token = await ensureCognitoToken();
1273
1274
  const scope = scopeOpts(mergeScopeOpts(secrets.opts(), opts));
1274
1275
  const companyUid = await getEntityUid(token, scope);
1275
- const usage = await buildSecretUsage("sandbox", opts.script);
1276
1276
  const client = new SandboxRunnerClient();
1277
1277
  const started = await client.startJob(token, {
1278
- skillId,
1279
1278
  companyUid,
1280
- args: skillArgs.length > 0 ? { argv: skillArgs } : undefined,
1281
- companySlug: scope.companySlug,
1282
- only: keys.length > 0 ? keys : undefined,
1283
- usage,
1279
+ secretNames: keys,
1280
+ command,
1284
1281
  });
1285
1282
  const job =
1286
1283
  started.status === "succeeded" || started.status === "failed"
@@ -1288,14 +1285,11 @@ export function registerSecretsCommand(program: Command): void {
1288
1285
  : await client.pollJob(token, started.jobId);
1289
1286
 
1290
1287
  renderSandboxJobResult(job, keys);
1291
- if (job.status === "failed") {
1292
- if (job.error) {
1293
- console.error(chalk.red("Sandbox job failed:"), scrubSandboxOutput(job.error, keys));
1294
- }
1295
- process.exit(job.exitCode && job.exitCode > 0 ? job.exitCode : 1);
1296
- }
1297
- if (job.exitCode && job.exitCode !== 0) {
1298
- process.exit(job.exitCode);
1288
+ const exitCode = typeof job.exitCode === "number" ? job.exitCode : undefined;
1289
+ if (job.success === false || (exitCode !== undefined && exitCode !== 0) || job.status === "failed") {
1290
+ const code = exitCode && exitCode !== 0 ? exitCode : 1;
1291
+ console.error(chalk.red(`Sandbox command failed with exit code ${code}.`));
1292
+ process.exit(code);
1299
1293
  }
1300
1294
  } catch (err) {
1301
1295
  console.error(
@@ -19,11 +19,9 @@ describe("SandboxRunnerClient", () => {
19
19
  });
20
20
 
21
21
  const result = await client.startJob("jwt-token", {
22
- skillId: "my-skill",
23
22
  companyUid: "cmp_123",
24
- args: { argv: ["--x"] },
25
- only: ["API_KEY"],
26
- usage: { channel: "sandbox" },
23
+ secretNames: ["API_KEY"],
24
+ command: "node script.js --x",
27
25
  });
28
26
 
29
27
  expect(result).toEqual({ jobId: "job_1", status: "queued" });
@@ -34,11 +32,9 @@ describe("SandboxRunnerClient", () => {
34
32
  "Content-Type": "application/json",
35
33
  },
36
34
  body: JSON.stringify({
37
- skillId: "my-skill",
38
35
  companyUid: "cmp_123",
39
- args: { argv: ["--x"] },
40
- only: ["API_KEY"],
41
- usage: { channel: "sandbox" },
36
+ secretNames: ["API_KEY"],
37
+ command: "node script.js --x",
42
38
  }),
43
39
  });
44
40
  });
@@ -49,7 +45,13 @@ describe("SandboxRunnerClient", () => {
49
45
  .mockResolvedValueOnce(jsonRes({ jobId: "job_1", status: "queued" }))
50
46
  .mockResolvedValueOnce(jsonRes({ jobId: "job_1", status: "running" }))
51
47
  .mockResolvedValueOnce(
52
- jsonRes({ jobId: "job_1", status: "succeeded", stdout: "ok\n" }),
48
+ jsonRes({
49
+ jobId: "job_1",
50
+ status: "succeeded",
51
+ output: "ok\n",
52
+ exitCode: 0,
53
+ success: true,
54
+ }),
53
55
  );
54
56
  const client = new SandboxRunnerClient({
55
57
  baseUrl: "https://runner.example",
@@ -63,11 +65,9 @@ describe("SandboxRunnerClient", () => {
63
65
  expect(result).toEqual({
64
66
  jobId: "job_1",
65
67
  status: "succeeded",
66
- stdout: "ok\n",
67
- stderr: undefined,
68
- logsTail: undefined,
69
- exitCode: undefined,
70
- error: undefined,
68
+ output: "ok\n",
69
+ exitCode: 0,
70
+ success: true,
71
71
  });
72
72
  expect(fetchImpl).toHaveBeenCalledTimes(3);
73
73
  expect(fetchImpl).toHaveBeenNthCalledWith(
@@ -82,9 +82,9 @@ describe("SandboxRunnerClient", () => {
82
82
  jsonRes({
83
83
  jobId: "job_1",
84
84
  status: "failed",
85
- stderr: "boom\n",
85
+ output: "boom\n",
86
86
  exitCode: 2,
87
- error: "command failed",
87
+ success: false,
88
88
  }),
89
89
  );
90
90
  const client = new SandboxRunnerClient({
@@ -97,15 +97,15 @@ describe("SandboxRunnerClient", () => {
97
97
  ).resolves.toMatchObject({
98
98
  jobId: "job_1",
99
99
  status: "failed",
100
- stderr: "boom\n",
100
+ output: "boom\n",
101
101
  exitCode: 2,
102
- error: "command failed",
102
+ success: false,
103
103
  });
104
104
  });
105
105
 
106
106
  it("uses the requested job id when the live status response omits it", async () => {
107
107
  const fetchImpl = vi.fn<typeof fetch>(async () =>
108
- jsonRes({ status: "succeeded", logsTail: "ok\n" }),
108
+ jsonRes({ status: "succeeded", output: "ok\n" }),
109
109
  );
110
110
  const client = new SandboxRunnerClient({
111
111
  baseUrl: "https://runner.example",
@@ -115,11 +115,9 @@ describe("SandboxRunnerClient", () => {
115
115
  await expect(client.getJob("jwt-token", "job_live")).resolves.toEqual({
116
116
  jobId: "job_live",
117
117
  status: "succeeded",
118
- stdout: undefined,
119
- stderr: undefined,
120
- logsTail: "ok\n",
118
+ output: "ok\n",
121
119
  exitCode: undefined,
122
- error: undefined,
120
+ success: undefined,
123
121
  });
124
122
  });
125
123
  });
@@ -1,12 +1,9 @@
1
1
  export type SandboxRunnerState = "queued" | "running" | "succeeded" | "failed";
2
2
 
3
3
  export interface SandboxRunnerStartRequest {
4
- skillId: string;
5
4
  companyUid: string;
6
- args?: Record<string, unknown>;
7
- companySlug?: string;
8
- only?: string[];
9
- usage?: unknown;
5
+ secretNames: string[];
6
+ command: string;
10
7
  }
11
8
 
12
9
  export interface SandboxRunnerStartResponse {
@@ -17,11 +14,9 @@ export interface SandboxRunnerStartResponse {
17
14
  export interface SandboxRunnerJob {
18
15
  jobId: string;
19
16
  status: SandboxRunnerState;
20
- stdout?: string;
21
- stderr?: string;
22
- logsTail?: string;
17
+ output?: string;
23
18
  exitCode?: number;
24
- error?: string;
19
+ success?: boolean;
25
20
  }
26
21
 
27
22
  export interface SandboxRunnerClientOptions {
@@ -70,23 +65,15 @@ function normalizeJob(
70
65
  if (!isSandboxRunnerState(status)) {
71
66
  throw new Error("Sandbox Runner returned an invalid job status.");
72
67
  }
73
- const output =
74
- typeof body.stdout === "string"
75
- ? body.stdout
76
- : typeof body.output === "string"
77
- ? body.output
78
- : undefined;
79
68
  return {
80
69
  jobId:
81
70
  typeof body.jobId === "string" && body.jobId.length > 0
82
71
  ? body.jobId
83
72
  : jobIdFallback ?? requireString(body, "jobId"),
84
73
  status,
85
- stdout: output,
86
- stderr: typeof body.stderr === "string" ? body.stderr : undefined,
87
- logsTail: typeof body.logsTail === "string" ? body.logsTail : undefined,
74
+ output: typeof body.output === "string" ? body.output : undefined,
88
75
  exitCode: typeof body.exitCode === "number" ? body.exitCode : undefined,
89
- error: typeof body.error === "string" ? body.error : undefined,
76
+ success: typeof body.success === "boolean" ? body.success : undefined,
90
77
  };
91
78
  }
92
79