@boxcompute/cli 0.2.2 → 0.2.3

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/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
 
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
 
@@ -243,6 +255,21 @@ function positive(value, name) {
243
255
  throw new UsageError(`${name} must be a positive integer`);
244
256
  return parsed;
245
257
  }
258
+ function timestamp(value, name) {
259
+ if (value === undefined)
260
+ return undefined;
261
+ const parsed = new Date(value);
262
+ if (Number.isNaN(parsed.valueOf()))
263
+ throw new UsageError(`${name} must be an RFC 3339 timestamp`);
264
+ return parsed.toISOString();
265
+ }
266
+ function oneOf(value, name, values) {
267
+ if (value === undefined)
268
+ return undefined;
269
+ if (!values.includes(value))
270
+ throw new UsageError(`${name} must be one of: ${values.join(", ")}`);
271
+ return value;
272
+ }
246
273
  function environment(tokens) {
247
274
  const values = [];
248
275
  for (;;) {
@@ -338,6 +365,21 @@ function executionOutput(io, json, sandboxId, result) {
338
365
  }
339
366
  return result.exitCode ?? 1;
340
367
  }
368
+ function logsOutput(io, json, logs) {
369
+ if (json) {
370
+ emit(io, true, { logs }, "");
371
+ return 0;
372
+ }
373
+ if (!logs.entries.length)
374
+ write(io.stdout, "No logs found.\n");
375
+ for (const entry of logs.entries) {
376
+ const process = entry.process_id ? ` ${entry.process_id}` : "";
377
+ write(io.stdout, `${entry.timestamp}\t${entry.stream}\t${entry.source}${process}\t${entry.message}\n`);
378
+ }
379
+ write(io.stderr, `sandbox=${logs.sandbox_id} entries=${logs.entries.length} retentionSeconds=${logs.retention_seconds}` +
380
+ `${logs.truncated ? " truncated=true" : ""}\n`);
381
+ return 0;
382
+ }
341
383
  export async function runCli(argv, supplied = {}) {
342
384
  const env = supplied.env ?? process.env;
343
385
  const io = supplied.io ?? { stdout: process.stdout, stderr: process.stderr };
@@ -534,6 +576,20 @@ export async function runCli(argv, supplied = {}) {
534
576
  emit(io, json, { sandbox }, sandboxLine(sandbox));
535
577
  return 0;
536
578
  }
579
+ if (action === "logs") {
580
+ const since = timestamp(option(args, "since"), "--since");
581
+ const until = timestamp(option(args, "until"), "--until");
582
+ const stream = oneOf(option(args, "stream"), "--stream", ["stdout", "stderr"]);
583
+ const source = oneOf(option(args, "source"), "--source", ["workload", "execute", "process"]);
584
+ const limit = positive(option(args, "limit"), "--limit");
585
+ if (limit !== undefined && limit > 5_000)
586
+ throw new UsageError("--limit must be 5000 or fewer");
587
+ if (since && until && since >= until)
588
+ throw new UsageError("--since must be earlier than --until");
589
+ if (args.length)
590
+ throw new UsageError(`Unknown sandbox logs option: ${args[0]}`);
591
+ return logsOutput(io, json, await client.logs(id, { since, until, stream, source, limit }));
592
+ }
537
593
  if (action === "delete") {
538
594
  if (!flag(args, "yes") || args.length)
539
595
  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.3",
4
4
  "description": "Connect local AI agents to BoxCompute sandboxes",
5
5
  "keywords": [
6
6
  "boxcompute",
@@ -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