@boxcompute/cli 0.2.2 → 0.2.4

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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,40 @@
1
+ # Changelog
2
+
3
+ This file records user-visible changes to the BoxCompute CLI. GitHub Releases
4
+ use the same notes and bind them to the exact source commit.
5
+
6
+ ## [0.2.4] - 2026-09-09
7
+
8
+ `bxc sandbox exec` now leaves arguments after `--` entirely to the program
9
+ running inside the sandbox.
10
+
11
+ ### CLI users
12
+
13
+ - Commands such as `bxc sandbox exec SANDBOX_ID -- python --version --json`
14
+ now pass `--version` and `--json` to Python instead of treating them as
15
+ global `bxc` options.
16
+ - No configuration changes are required. Upgrade normally with `bxc update`.
17
+
18
+ ### Security
19
+
20
+ No security-relevant changes.
21
+
22
+ ## [0.2.3] - 2026-09-06
23
+
24
+ - Added access to retained sandbox logs after runtime deletion.
25
+
26
+ ## [0.2.2] - 2026-09-05
27
+
28
+ - Added `bxc update` and its `bxc up` alias.
29
+
30
+ ## [0.2.1] - 2026-09-05
31
+
32
+ - Improved release validation and managed skill updates.
33
+
34
+ ## [0.2.0] - 2026-09-04
35
+
36
+ - Added the multi-instance Sandbox API v2 commands.
37
+
38
+ ## [0.1.1] - 2026-09-04
39
+
40
+ - Initial public npm release with browser login and managed agent skills.
package/README.md CHANGED
@@ -48,6 +48,7 @@ bxc workspaces
48
48
  bxc sandboxes
49
49
  bxc sandbox start WORKSPACE_ID
50
50
  # Use the returned sandbox instance ID for later commands.
51
+ bxc sandbox logs SANDBOX_ID --source execute
51
52
  bxc sandbox exec SANDBOX_ID -- python -m pytest
52
53
  ```
53
54
 
@@ -69,3 +70,30 @@ bxc update
69
70
  saved BoxCompute credential and refreshes untouched managed skills before the
70
71
  client's next agent session. The new CLI uses v2 for multi-instance sandboxes
71
72
  and gives a server-first upgrade message if it reaches an older deployment.
73
+
74
+ ## Development
75
+
76
+ ```sh
77
+ bun install --frozen-lockfile
78
+ bun run typecheck
79
+ bun test
80
+ bun run lint
81
+ bun run build
82
+ ```
83
+
84
+ The application-side authentication and customer Sandbox API implementations
85
+ live in the private `boxcompute/web-agent` repository. Changes to either side of
86
+ that contract must remain backward compatible during rollout: deploy the server
87
+ first, then publish the CLI.
88
+
89
+ ## Releases
90
+
91
+ Every user-visible change is recorded in [CHANGELOG.md](CHANGELOG.md). Releases
92
+ use semantic `vMAJOR.MINOR.PATCH` tags and are published from `main` by the
93
+ protected `Publish BoxCompute CLI` workflow. npm trusted publishing supplies a
94
+ short-lived release credential; the repository stores no npm token.
95
+
96
+ ## License
97
+
98
+ Copyright © 2026 BoxCompute. All rights reserved. The source is publicly
99
+ visible, but it is not offered under an open-source license.
package/dist/cli.js CHANGED
@@ -26,6 +26,7 @@ Commands:
26
26
  sandbox Manage isolated BoxCompute sandboxes
27
27
  start Create and start a workspace sandbox
28
28
  status Inspect one sandbox
29
+ logs Read current or retained sandbox logs
29
30
  exec Execute a program inside a sandbox
30
31
  delete [alias: rm] Destroy the runtime; the workspace remains
31
32
  skill [alias: skills] Manage coding-harness skills
@@ -54,6 +55,7 @@ Examples:
54
55
  $ bxc skill install
55
56
  $ bxc workspaces
56
57
  $ bxc sandbox start WORKSPACE_ID
58
+ $ bxc sandbox logs SANDBOX_ID --source execute
57
59
  $ bxc sandbox exec SANDBOX_ID -- python -m pytest
58
60
 
59
61
  Compatibility:
@@ -68,6 +70,7 @@ Commands:
68
70
 
69
71
  start WORKSPACE_ID Create and start a new sandbox instance
70
72
  status SANDBOX_ID Inspect one sandbox
73
+ logs SANDBOX_ID [options] Read logs without starting the runtime
71
74
  exec SANDBOX_ID [options] -- PROGRAM [ARG...]
72
75
  Execute a program inside a sandbox
73
76
  delete SANDBOX_ID --yes [alias: rm] Destroy the runtime; keep the workspace
@@ -78,6 +81,15 @@ Exec options:
78
81
  --env KEY=VALUE Set an environment variable; repeatable
79
82
  --timeout SECONDS Command timeout
80
83
  --max-output-bytes BYTES Maximum captured output
84
+
85
+ Log options:
86
+
87
+ --since TIMESTAMP Include entries at or after an RFC 3339 time
88
+ --until TIMESTAMP Include entries before an RFC 3339 time
89
+ --stream stdout|stderr Filter by output stream
90
+ --source workload|execute|process
91
+ Filter by log source
92
+ --limit ENTRIES Maximum entries (default: 1000, max: 5000)
81
93
  `;
82
94
  const skillHelp = `Manage coding-harness skills
83
95
 
@@ -228,8 +240,10 @@ function flag(tokens, name) {
228
240
  tokens.splice(index, 1);
229
241
  return true;
230
242
  }
231
- function anyFlag(tokens, ...names) {
232
- const index = tokens.findIndex((token) => names.includes(token));
243
+ function globalFlag(tokens, ...names) {
244
+ const separator = tokens.indexOf("--");
245
+ const boundary = separator < 0 ? tokens.length : separator;
246
+ const index = tokens.findIndex((token, position) => position < boundary && names.includes(token));
233
247
  if (index < 0)
234
248
  return false;
235
249
  tokens.splice(index, 1);
@@ -243,6 +257,21 @@ function positive(value, name) {
243
257
  throw new UsageError(`${name} must be a positive integer`);
244
258
  return parsed;
245
259
  }
260
+ function timestamp(value, name) {
261
+ if (value === undefined)
262
+ return undefined;
263
+ const parsed = new Date(value);
264
+ if (Number.isNaN(parsed.valueOf()))
265
+ throw new UsageError(`${name} must be an RFC 3339 timestamp`);
266
+ return parsed.toISOString();
267
+ }
268
+ function oneOf(value, name, values) {
269
+ if (value === undefined)
270
+ return undefined;
271
+ if (!values.includes(value))
272
+ throw new UsageError(`${name} must be one of: ${values.join(", ")}`);
273
+ return value;
274
+ }
246
275
  function environment(tokens) {
247
276
  const values = [];
248
277
  for (;;) {
@@ -338,6 +367,21 @@ function executionOutput(io, json, sandboxId, result) {
338
367
  }
339
368
  return result.exitCode ?? 1;
340
369
  }
370
+ function logsOutput(io, json, logs) {
371
+ if (json) {
372
+ emit(io, true, { logs }, "");
373
+ return 0;
374
+ }
375
+ if (!logs.entries.length)
376
+ write(io.stdout, "No logs found.\n");
377
+ for (const entry of logs.entries) {
378
+ const process = entry.process_id ? ` ${entry.process_id}` : "";
379
+ write(io.stdout, `${entry.timestamp}\t${entry.stream}\t${entry.source}${process}\t${entry.message}\n`);
380
+ }
381
+ write(io.stderr, `sandbox=${logs.sandbox_id} entries=${logs.entries.length} retentionSeconds=${logs.retention_seconds}` +
382
+ `${logs.truncated ? " truncated=true" : ""}\n`);
383
+ return 0;
384
+ }
341
385
  export async function runCli(argv, supplied = {}) {
342
386
  const env = supplied.env ?? process.env;
343
387
  const io = supplied.io ?? { stdout: process.stdout, stderr: process.stderr };
@@ -356,13 +400,13 @@ export async function runCli(argv, supplied = {}) {
356
400
  const skillText = supplied.readSkill ?? readSkill;
357
401
  const syncSkills = supplied.syncManagedSkills ?? syncManagedSkills;
358
402
  const args = [...argv];
359
- const json = flag(args, "json");
360
- const versionRequested = args[0] === "version" || anyFlag(args, "--version", "-V", "-v");
403
+ const json = globalFlag(args, "--json");
404
+ const versionRequested = args[0] === "version" || globalFlag(args, "--version", "-V", "-v");
361
405
  if (versionRequested) {
362
406
  emit(io, json, { version: CLI_VERSION }, `${CLI_VERSION}\n`);
363
407
  return 0;
364
408
  }
365
- const helpRequested = anyFlag(args, "--help", "-h");
409
+ const helpRequested = globalFlag(args, "--help", "-h");
366
410
  if (!args.length || args[0] === "help" || helpRequested) {
367
411
  const helpTarget = args[0] === "help" ? args[1] : args[0];
368
412
  write(io.stdout, helpFor(helpTarget));
@@ -534,6 +578,20 @@ export async function runCli(argv, supplied = {}) {
534
578
  emit(io, json, { sandbox }, sandboxLine(sandbox));
535
579
  return 0;
536
580
  }
581
+ if (action === "logs") {
582
+ const since = timestamp(option(args, "since"), "--since");
583
+ const until = timestamp(option(args, "until"), "--until");
584
+ const stream = oneOf(option(args, "stream"), "--stream", ["stdout", "stderr"]);
585
+ const source = oneOf(option(args, "source"), "--source", ["workload", "execute", "process"]);
586
+ const limit = positive(option(args, "limit"), "--limit");
587
+ if (limit !== undefined && limit > 5_000)
588
+ throw new UsageError("--limit must be 5000 or fewer");
589
+ if (since && until && since >= until)
590
+ throw new UsageError("--since must be earlier than --until");
591
+ if (args.length)
592
+ throw new UsageError(`Unknown sandbox logs option: ${args[0]}`);
593
+ return logsOutput(io, json, await client.logs(id, { since, until, stream, source, limit }));
594
+ }
537
595
  if (action === "delete") {
538
596
  if (!flag(args, "yes") || args.length)
539
597
  throw new UsageError("sandbox delete requires SANDBOX_ID --yes");
package/dist/client.d.ts CHANGED
@@ -5,6 +5,7 @@ export type Sandbox = {
5
5
  name: string;
6
6
  state: "cold" | "running";
7
7
  runtimeId: string | null;
8
+ retainedRuntimeId: string | null;
8
9
  image: string | null;
9
10
  createdAt: number;
10
11
  lastUsedAt: number | null;
@@ -23,6 +24,20 @@ export type Execution = {
23
24
  stderrTruncated: boolean;
24
25
  wallTimeSeconds: number;
25
26
  };
27
+ export type SandboxLogEntry = {
28
+ timestamp: string;
29
+ stream: "stdout" | "stderr";
30
+ source: "workload" | "execute" | "process";
31
+ message: string;
32
+ pod_uid: string;
33
+ process_id?: string;
34
+ };
35
+ export type SandboxLogs = {
36
+ sandbox_id: string;
37
+ entries: SandboxLogEntry[];
38
+ truncated: boolean;
39
+ retention_seconds: number;
40
+ };
26
41
  export declare class BoxComputeHttpError extends Error {
27
42
  readonly status: number;
28
43
  readonly code?: string | undefined;
@@ -46,5 +61,12 @@ export declare class BoxComputeClient {
46
61
  maxOutputBytes?: number;
47
62
  env?: Record<string, string>;
48
63
  }): Promise<Execution>;
64
+ logs(id: string, input?: {
65
+ since?: string;
66
+ until?: string;
67
+ stream?: "stdout" | "stderr";
68
+ source?: "workload" | "execute" | "process";
69
+ limit?: number;
70
+ }): Promise<SandboxLogs>;
49
71
  delete(id: string): Promise<void>;
50
72
  }
package/dist/client.js CHANGED
@@ -66,6 +66,21 @@ export class BoxComputeClient {
66
66
  body: JSON.stringify(input),
67
67
  })).result;
68
68
  }
69
+ async logs(id, input = {}) {
70
+ const query = new URLSearchParams();
71
+ if (input.since)
72
+ query.set("since", input.since);
73
+ if (input.until)
74
+ query.set("until", input.until);
75
+ if (input.stream)
76
+ query.set("stream", input.stream);
77
+ if (input.source)
78
+ query.set("source", input.source);
79
+ if (input.limit !== undefined)
80
+ query.set("limit", String(input.limit));
81
+ const suffix = query.size ? `?${query}` : "";
82
+ return (await this.request(`/api/v2/sandboxes/${encodeURIComponent(id)}/logs${suffix}`)).logs;
83
+ }
69
84
  async delete(id) {
70
85
  await this.request(`/api/v2/sandboxes/${encodeURIComponent(id)}`, { method: "DELETE" });
71
86
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boxcompute/cli",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "Connect local AI agents to BoxCompute sandboxes",
5
5
  "keywords": [
6
6
  "boxcompute",
@@ -11,18 +11,19 @@
11
11
  "homepage": "https://app.boxcompute.ai",
12
12
  "repository": {
13
13
  "type": "git",
14
- "url": "git+https://github.com/boxcompute/web-agent.git",
15
- "directory": "apps/cli"
14
+ "url": "git+https://github.com/boxcompute/cli.git"
16
15
  },
17
16
  "bugs": {
18
- "url": "https://github.com/boxcompute/web-agent/issues"
17
+ "url": "https://github.com/boxcompute/cli/issues"
19
18
  },
19
+ "license": "UNLICENSED",
20
20
  "type": "module",
21
21
  "bin": {
22
22
  "bxc": "dist/cli.js",
23
23
  "bcompute": "dist/cli.js"
24
24
  },
25
25
  "files": [
26
+ "CHANGELOG.md",
26
27
  "dist",
27
28
  "skills"
28
29
  ],
@@ -32,6 +33,7 @@
32
33
  "publishConfig": {
33
34
  "access": "public"
34
35
  },
36
+ "packageManager": "bun@1.3.14",
35
37
  "scripts": {
36
38
  "build": "tsc -p tsconfig.build.json",
37
39
  "lint": "oxlint --max-warnings 0 src test",
@@ -44,6 +44,11 @@ sandbox unless the user explicitly places those secrets in scope.
44
44
  ## Lifecycle and safety
45
45
 
46
46
  - Inspect uncertain state with `bxc --json sandbox status SANDBOX_ID`.
47
+ - Read runtime output with `bxc sandbox logs SANDBOX_ID`. Add `--json` for
48
+ structured entries or narrow results with `--since`, `--until`, `--stream`,
49
+ `--source`, and `--limit`. Log reads do not start stopped compute, and remain
50
+ available for the provider's nominal 30-day retention period after deletion;
51
+ storage pressure may shorten that period, so logs are not an archive.
47
52
  - Sandboxes persist across commands; do not destroy one merely because the
48
53
  current task is finished.
49
54
  - `bxc sandbox delete SANDBOX_ID --yes` destroys the remote runtime and is