opensandbox-cli 1.1.1__tar.gz → 1.1.2.dev2026102001__tar.gz

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 (32) hide show
  1. {opensandbox_cli-1.1.1 → opensandbox_cli-1.1.2.dev2026102001}/PKG-INFO +24 -56
  2. {opensandbox_cli-1.1.1 → opensandbox_cli-1.1.2.dev2026102001}/README.md +22 -54
  3. {opensandbox_cli-1.1.1 → opensandbox_cli-1.1.2.dev2026102001}/pyproject.toml +2 -3
  4. {opensandbox_cli-1.1.1 → opensandbox_cli-1.1.2.dev2026102001}/src/opensandbox_cli/commands/diagnostics.py +18 -0
  5. {opensandbox_cli-1.1.1 → opensandbox_cli-1.1.2.dev2026102001}/src/opensandbox_cli/commands/sandbox.py +2 -2
  6. {opensandbox_cli-1.1.1 → opensandbox_cli-1.1.2.dev2026102001}/src/opensandbox_cli/commands/skills.py +6 -3
  7. {opensandbox_cli-1.1.1 → opensandbox_cli-1.1.2.dev2026102001}/src/opensandbox_cli/config.py +48 -15
  8. {opensandbox_cli-1.1.1 → opensandbox_cli-1.1.2.dev2026102001}/src/opensandbox_cli/main.py +38 -0
  9. {opensandbox_cli-1.1.1 → opensandbox_cli-1.1.2.dev2026102001}/src/opensandbox_cli/output.py +21 -1
  10. {opensandbox_cli-1.1.1 → opensandbox_cli-1.1.2.dev2026102001}/src/opensandbox_cli/skills/opensandbox-command-execution.md +3 -6
  11. {opensandbox_cli-1.1.1 → opensandbox_cli-1.1.2.dev2026102001}/src/opensandbox_cli/skills/opensandbox-credential-vault.md +8 -19
  12. {opensandbox_cli-1.1.1 → opensandbox_cli-1.1.2.dev2026102001}/src/opensandbox_cli/skills/opensandbox-file-operations.md +4 -8
  13. {opensandbox_cli-1.1.1 → opensandbox_cli-1.1.2.dev2026102001}/src/opensandbox_cli/skills/opensandbox-network-egress.md +2 -4
  14. {opensandbox_cli-1.1.1 → opensandbox_cli-1.1.2.dev2026102001}/src/opensandbox_cli/skills/opensandbox-sandbox-lifecycle.md +1 -1
  15. {opensandbox_cli-1.1.1 → opensandbox_cli-1.1.2.dev2026102001}/src/opensandbox_cli/skills/opensandbox-sandbox-troubleshooting.md +6 -0
  16. {opensandbox_cli-1.1.1 → opensandbox_cli-1.1.2.dev2026102001}/.gitignore +0 -0
  17. {opensandbox_cli-1.1.1 → opensandbox_cli-1.1.2.dev2026102001}/LICENSE +0 -0
  18. {opensandbox_cli-1.1.1 → opensandbox_cli-1.1.2.dev2026102001}/src/opensandbox_cli/__init__.py +0 -0
  19. {opensandbox_cli-1.1.1 → opensandbox_cli-1.1.2.dev2026102001}/src/opensandbox_cli/__main__.py +0 -0
  20. {opensandbox_cli-1.1.1 → opensandbox_cli-1.1.2.dev2026102001}/src/opensandbox_cli/client.py +0 -0
  21. {opensandbox_cli-1.1.1 → opensandbox_cli-1.1.2.dev2026102001}/src/opensandbox_cli/commands/__init__.py +0 -0
  22. {opensandbox_cli-1.1.1 → opensandbox_cli-1.1.2.dev2026102001}/src/opensandbox_cli/commands/command.py +0 -0
  23. {opensandbox_cli-1.1.1 → opensandbox_cli-1.1.2.dev2026102001}/src/opensandbox_cli/commands/config_cmd.py +0 -0
  24. {opensandbox_cli-1.1.1 → opensandbox_cli-1.1.2.dev2026102001}/src/opensandbox_cli/commands/credential_vault.py +0 -0
  25. {opensandbox_cli-1.1.1 → opensandbox_cli-1.1.2.dev2026102001}/src/opensandbox_cli/commands/devops.py +0 -0
  26. {opensandbox_cli-1.1.1 → opensandbox_cli-1.1.2.dev2026102001}/src/opensandbox_cli/commands/egress.py +0 -0
  27. {opensandbox_cli-1.1.1 → opensandbox_cli-1.1.2.dev2026102001}/src/opensandbox_cli/commands/file.py +0 -0
  28. {opensandbox_cli-1.1.1 → opensandbox_cli-1.1.2.dev2026102001}/src/opensandbox_cli/commands/snapshot.py +0 -0
  29. {opensandbox_cli-1.1.1 → opensandbox_cli-1.1.2.dev2026102001}/src/opensandbox_cli/commands/template.py +0 -0
  30. {opensandbox_cli-1.1.1 → opensandbox_cli-1.1.2.dev2026102001}/src/opensandbox_cli/py.typed +0 -0
  31. {opensandbox_cli-1.1.1 → opensandbox_cli-1.1.2.dev2026102001}/src/opensandbox_cli/skill_registry.py +0 -0
  32. {opensandbox_cli-1.1.1 → opensandbox_cli-1.1.2.dev2026102001}/src/opensandbox_cli/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: opensandbox-cli
3
- Version: 1.1.1
3
+ Version: 1.1.2.dev2026102001
4
4
  Summary: OpenSandbox CLI - Command-line interface for managing sandboxes
5
5
  Project-URL: Homepage, https://open-sandbox.ai
6
6
  Project-URL: Repository, https://github.com/opensandbox-group/OpenSandbox
@@ -224,7 +224,7 @@ Classifier: Topic :: Software Development :: Libraries
224
224
  Classifier: Typing :: Typed
225
225
  Requires-Python: >=3.10
226
226
  Requires-Dist: click<9.0,>=8.1.0
227
- Requires-Dist: opensandbox<2.0.0,>=1.1.1
227
+ Requires-Dist: opensandbox==1.1.2.dev20261020
228
228
  Requires-Dist: pyyaml<7.0,>=6.0
229
229
  Requires-Dist: rich<14.0,>=13.0.0
230
230
  Requires-Dist: tomli>=2.0.0; python_version < '3.11'
@@ -395,11 +395,7 @@ Create from a template (golden image):
395
395
  osb sandbox create --template <template-id> --timeout 30m -o json
396
396
  ```
397
397
 
398
- Template mode fixes the workload shape on the server: `--image`, `--env`,
399
- `--resource`, `--entrypoint`, `--volumes-file`, and `--credential-proxy` cannot
400
- be combined with `--template`. Only `--timeout` (required), `--metadata`,
401
- `--extension`, and `--network-policy-file` may accompany it. Only templates in
402
- the `Succeeded` phase can be used. See [Manage templates](#manage-templates).
398
+ Template mode fixes the workload shape on the server: `--image`, `--env`, `--resource`, `--entrypoint`, `--volumes-file`, and `--credential-proxy` cannot be combined with `--template`. Only `--timeout` (required), `--metadata`, `--extension`, and `--network-policy-file` may accompany it. Only templates in the `Succeeded` phase can be used. See [Manage templates](#manage-templates).
403
399
 
404
400
  Create from a snapshot:
405
401
 
@@ -415,8 +411,7 @@ Create from a request file (`-f`, like `kubectl apply -f`):
415
411
  osb sandbox create -f sandbox-request.json -o json
416
412
  ```
417
413
 
418
- `sandbox-request.json` uses the public `CreateSandboxRequest` JSON format
419
- (camelCase keys):
414
+ `sandbox-request.json` uses the public `CreateSandboxRequest` JSON format (camelCase keys):
420
415
 
421
416
  ```json
422
417
  {
@@ -431,23 +426,13 @@ osb sandbox create -f sandbox-request.json -o json
431
426
  }
432
427
  ```
433
428
 
434
- - Exactly one of `image` (string or `{ "uri": ..., "auth": { "username": ..., "password": ... } }`),
435
- `templateId`, or `snapshotId` is required; `timeout` is integer seconds, or
436
- `null` for manual cleanup.
437
- - Supported fields: `image`, `templateId`, `snapshotId`, `platform`, `timeout`,
438
- `resourceLimits`, `resourceRequests`, `env`, `metadata`, `extensions`,
439
- `entrypoint`, `networkPolicy`, `credentialProxy`, `secureAccess`, `volumes`,
440
- `lifecycle`.
441
- - `-f` cannot be combined with request-building flags; `--skip-health-check`,
442
- `--ready-timeout`, and `-o` still apply. In template mode the file may only
443
- set `timeout`, `metadata`, `extensions`, and `networkPolicy` alongside
444
- `templateId` (a finite `timeout` is required).
429
+ - Exactly one of `image` (string or `{ "uri": ..., "auth": { "username": ..., "password": ... } }`), `templateId`, or `snapshotId` is required; `timeout` is integer seconds, or `null` for manual cleanup.
430
+ - Supported fields: `image`, `templateId`, `snapshotId`, `platform`, `timeout`, `resourceLimits`, `resourceRequests`, `env`, `metadata`, `extensions`, `entrypoint`, `networkPolicy`, `credentialProxy`, `secureAccess`, `volumes`, `lifecycle`.
431
+ - `-f` cannot be combined with request-building flags; `--skip-health-check`, `--ready-timeout`, and `-o` still apply. In template mode the file may only set `timeout`, `metadata`, `extensions`, and `networkPolicy` alongside `templateId` (a finite `timeout` is required).
445
432
 
446
433
  ### Manage templates
447
434
 
448
- Templates are golden-image builds; the build runs asynchronously, so poll
449
- `template get` until the status phase is `Succeeded`. Template management
450
- requires a Kubernetes-backed runtime.
435
+ Templates are golden-image builds; the build runs asynchronously, so poll `template get` until the status phase is `Succeeded`. Template management requires a Kubernetes-backed runtime.
451
436
 
452
437
  ```bash
453
438
  osb template create --image python:3.12 --publish s3://bucket/publish -o json
@@ -456,11 +441,7 @@ osb template list -o json
456
441
  osb template delete <template-id> -o json
457
442
  ```
458
443
 
459
- `template create` also accepts `--resource cpu=1 memory=512Mi disk=2Gi`,
460
- `--entrypoint` (repeat per argv item), `--env KEY=VALUE`, `--metadata
461
- KEY=VALUE`, `--format native|overlaybd`, `--readiness-probe`, and
462
- `--warmup-seconds`. Or pass a request file in the public `CreateTemplateRequest`
463
- JSON format:
444
+ `template create` also accepts `--resource cpu=1 memory=512Mi disk=2Gi`, `--entrypoint` (repeat per argv item), `--env KEY=VALUE`, `--metadata KEY=VALUE`, `--format native|overlaybd`, `--readiness-probe`, and `--warmup-seconds`. Or pass a request file in the public `CreateTemplateRequest` JSON format:
464
445
 
465
446
  ```bash
466
447
  osb template create -f template-request.json -o json
@@ -499,9 +480,7 @@ osb sandbox metrics <sandbox-id> --watch -o raw
499
480
 
500
481
  ### Pause a sandbox
501
482
 
502
- Pause is asynchronous. `Pause request accepted` confirms that the server accepted
503
- the request, not that the sandbox has reached the `Paused` state. Poll the sandbox
504
- until the transition finishes:
483
+ Pause is asynchronous. `Pause request accepted` confirms that the server accepted the request, not that the sandbox has reached the `Paused` state. Poll the sandbox until the transition finishes:
505
484
 
506
485
  ```bash
507
486
  osb sandbox pause <sandbox-id>
@@ -530,16 +509,9 @@ osb command status <sandbox-id> <execution-id> -o json
530
509
  osb command logs <sandbox-id> <execution-id> -o json
531
510
  ```
532
511
 
533
- Background start errors or a stream ending before startup confirmation make the
534
- CLI exit non-zero without success metadata. After a successful start, use
535
- `command status` and `command logs` to check the process's eventual result.
512
+ Background start errors or a stream ending before startup confirmation make the CLI exit non-zero without success metadata. After a successful start, use `command status` and `command logs` to check the process's eventual result.
536
513
 
537
- By default the payload after `--` is joined into one shell command string, so
538
- pipelines, redirection, and `$VAR` expansion work as in a terminal. Add `--argv`
539
- to pass the arguments to the executable as a literal argv list (no shell) when
540
- values such as `$HOME`, quotes, embedded spaces, or empty strings must reach the
541
- process unchanged. `--argv` needs a sandbox image whose execd accepts argv
542
- requests:
514
+ By default the payload after `--` is joined into one shell command string, so pipelines, redirection, and `$VAR` expansion work as in a terminal. Add `--argv` to pass the arguments to the executable as a literal argv list (no shell) when values such as `$HOME`, quotes, embedded spaces, or empty strings must reach the process unchanged. `--argv` needs a sandbox image whose execd accepts argv requests:
543
515
 
544
516
  ```bash
545
517
  osb command run <sandbox-id> -o raw --argv -- python3 -c "import sys; print(sys.argv[1:])" "a b" '$HOME' "x'y" ""
@@ -566,10 +538,7 @@ osb file replace <sandbox-id> /workspace/app.py --old old --new new -o json
566
538
  osb file chmod <sandbox-id> /workspace/script.sh --mode 755 -o json
567
539
  ```
568
540
 
569
- Downloads replace regular files only on success; a failed or interrupted
570
- download preserves any existing regular file. Existing devices and named pipes
571
- receive data directly. Stdout aliases (such as `/dev/stdout`) stream only the file
572
- bytes, without a success message. See the [CLI guide](../docs/cli/index.md#work-with-files).
541
+ Downloads replace regular files only on success; a failed or interrupted download preserves any existing regular file. Existing devices and named pipes receive data directly. Stdout aliases (such as `/dev/stdout`) stream only the file bytes, without a success message. See the [CLI guide](../docs/cli/index.md#work-with-files).
573
542
 
574
543
  ### Manage runtime egress policy
575
544
 
@@ -593,9 +562,7 @@ osb command run <sandbox-id> -o raw -- curl -I https://pypi.org
593
562
 
594
563
  ### Manage Credential Vault
595
564
 
596
- Credential Vault operations call the sandbox egress sidecar through the Python SDK.
597
- Create the sandbox with `--credential-proxy` and an explicit network policy before
598
- writing vault state.
565
+ Credential Vault operations call the sandbox egress sidecar through the Python SDK. Create the sandbox with `--credential-proxy` and an explicit network policy before writing vault state.
599
566
 
600
567
  ```bash
601
568
  osb credential-vault create <sandbox-id> --file vault.yaml -o json
@@ -606,14 +573,14 @@ osb credential-vault binding list <sandbox-id> -o json
606
573
  osb credential-vault delete <sandbox-id> -o json
607
574
  ```
608
575
 
609
- Use `--file -` to read a JSON/YAML payload from stdin. Do not pass plaintext
610
- credential values as command-line flags; keep them in the payload stream or file.
576
+ Use `--file -` to read a JSON/YAML payload from stdin. Do not pass plaintext credential values as command-line flags; keep them in the payload stream or file.
611
577
 
612
578
  ### Collect diagnostics
613
579
 
614
- Use the stable diagnostics commands for API-backed log and event descriptors.
580
+ Use context to identify the runtime backend, then collect log and event descriptors.
615
581
 
616
582
  ```bash
583
+ osb diagnostics context <sandbox-id> -o json
617
584
  osb diagnostics events <sandbox-id> --scope runtime -o raw
618
585
  osb diagnostics events <sandbox-id> --scope all -o raw
619
586
  osb diagnostics logs <sandbox-id> --scope container -o raw
@@ -622,7 +589,9 @@ osb diagnostics events <sandbox-id> --scope runtime -o json
622
589
  osb diagnostics logs <sandbox-id> --scope container -o yaml
623
590
  ```
624
591
 
625
- `--scope` is required for stable diagnostics. The built-in server supports
592
+ `context` returns backend routing information without connecting to execd and
593
+ supports table, JSON, and YAML output. `--scope` is required for logs/events.
594
+ The built-in server supports
626
595
  `container` and `all` for logs, and `runtime` and `all` for events. It returns
627
596
  `DIAGNOSTICS_SCOPE_UNSUPPORTED` for unavailable scopes, including lifecycle events.
628
597
  Best-effort scopes may include a `warnings` field when the backend can only
@@ -632,8 +601,7 @@ temporary URL. Structured CLI output follows the SDK/Python field style, for
632
601
  example `content_url`, `content_length`, and `expires_at`. Older server builds
633
602
  may still return `DIAGNOSTICS_NOT_IMPLEMENTED` for scoped diagnostics.
634
603
 
635
- Legacy DevOps diagnostics remain experimental. Prefer `osb diagnostics logs/events`
636
- for stable API-backed log and event collection.
604
+ Legacy DevOps diagnostics remain experimental. Prefer `osb diagnostics logs/events` for stable API-backed log and event collection.
637
605
 
638
606
  ```bash
639
607
  osb devops inspect <sandbox-id> -o raw
@@ -701,12 +669,12 @@ Supported targets:
701
669
 
702
670
  | Target | Install location |
703
671
  | --- | --- |
704
- | `claude` | `./.claude/skills/` or `~/.claude/skills/` |
672
+ | `claude` | `./.claude/skills/<name>/SKILL.md` or `~/.claude/skills/<name>/SKILL.md` |
705
673
  | `cursor` | `./.cursor/rules/` or `~/.cursor/rules/` |
706
674
  | `codex` | `./.codex/skills/<name>/SKILL.md` or `~/.codex/skills/<name>/SKILL.md` |
707
- | `copilot` | `./.github/copilot-instructions.md` or `~/.github/copilot-instructions.md` |
675
+ | `copilot` | `./.github/copilot-instructions.md` or `~/.copilot/copilot-instructions.md` |
708
676
  | `windsurf` | `./.windsurfrules` or `~/.windsurfrules` |
709
- | `cline` | `./.clinerules` or `~/.clinerules` |
677
+ | `cline` | `./.clinerules` or `~/.cline/rules/opensandbox-<name>.md` |
710
678
  | `opencode` | `./.agents/skills/<name>/SKILL.md` or `~/.agents/skills/<name>/SKILL.md` |
711
679
 
712
680
  Common flows:
@@ -162,11 +162,7 @@ Create from a template (golden image):
162
162
  osb sandbox create --template <template-id> --timeout 30m -o json
163
163
  ```
164
164
 
165
- Template mode fixes the workload shape on the server: `--image`, `--env`,
166
- `--resource`, `--entrypoint`, `--volumes-file`, and `--credential-proxy` cannot
167
- be combined with `--template`. Only `--timeout` (required), `--metadata`,
168
- `--extension`, and `--network-policy-file` may accompany it. Only templates in
169
- the `Succeeded` phase can be used. See [Manage templates](#manage-templates).
165
+ Template mode fixes the workload shape on the server: `--image`, `--env`, `--resource`, `--entrypoint`, `--volumes-file`, and `--credential-proxy` cannot be combined with `--template`. Only `--timeout` (required), `--metadata`, `--extension`, and `--network-policy-file` may accompany it. Only templates in the `Succeeded` phase can be used. See [Manage templates](#manage-templates).
170
166
 
171
167
  Create from a snapshot:
172
168
 
@@ -182,8 +178,7 @@ Create from a request file (`-f`, like `kubectl apply -f`):
182
178
  osb sandbox create -f sandbox-request.json -o json
183
179
  ```
184
180
 
185
- `sandbox-request.json` uses the public `CreateSandboxRequest` JSON format
186
- (camelCase keys):
181
+ `sandbox-request.json` uses the public `CreateSandboxRequest` JSON format (camelCase keys):
187
182
 
188
183
  ```json
189
184
  {
@@ -198,23 +193,13 @@ osb sandbox create -f sandbox-request.json -o json
198
193
  }
199
194
  ```
200
195
 
201
- - Exactly one of `image` (string or `{ "uri": ..., "auth": { "username": ..., "password": ... } }`),
202
- `templateId`, or `snapshotId` is required; `timeout` is integer seconds, or
203
- `null` for manual cleanup.
204
- - Supported fields: `image`, `templateId`, `snapshotId`, `platform`, `timeout`,
205
- `resourceLimits`, `resourceRequests`, `env`, `metadata`, `extensions`,
206
- `entrypoint`, `networkPolicy`, `credentialProxy`, `secureAccess`, `volumes`,
207
- `lifecycle`.
208
- - `-f` cannot be combined with request-building flags; `--skip-health-check`,
209
- `--ready-timeout`, and `-o` still apply. In template mode the file may only
210
- set `timeout`, `metadata`, `extensions`, and `networkPolicy` alongside
211
- `templateId` (a finite `timeout` is required).
196
+ - Exactly one of `image` (string or `{ "uri": ..., "auth": { "username": ..., "password": ... } }`), `templateId`, or `snapshotId` is required; `timeout` is integer seconds, or `null` for manual cleanup.
197
+ - Supported fields: `image`, `templateId`, `snapshotId`, `platform`, `timeout`, `resourceLimits`, `resourceRequests`, `env`, `metadata`, `extensions`, `entrypoint`, `networkPolicy`, `credentialProxy`, `secureAccess`, `volumes`, `lifecycle`.
198
+ - `-f` cannot be combined with request-building flags; `--skip-health-check`, `--ready-timeout`, and `-o` still apply. In template mode the file may only set `timeout`, `metadata`, `extensions`, and `networkPolicy` alongside `templateId` (a finite `timeout` is required).
212
199
 
213
200
  ### Manage templates
214
201
 
215
- Templates are golden-image builds; the build runs asynchronously, so poll
216
- `template get` until the status phase is `Succeeded`. Template management
217
- requires a Kubernetes-backed runtime.
202
+ Templates are golden-image builds; the build runs asynchronously, so poll `template get` until the status phase is `Succeeded`. Template management requires a Kubernetes-backed runtime.
218
203
 
219
204
  ```bash
220
205
  osb template create --image python:3.12 --publish s3://bucket/publish -o json
@@ -223,11 +208,7 @@ osb template list -o json
223
208
  osb template delete <template-id> -o json
224
209
  ```
225
210
 
226
- `template create` also accepts `--resource cpu=1 memory=512Mi disk=2Gi`,
227
- `--entrypoint` (repeat per argv item), `--env KEY=VALUE`, `--metadata
228
- KEY=VALUE`, `--format native|overlaybd`, `--readiness-probe`, and
229
- `--warmup-seconds`. Or pass a request file in the public `CreateTemplateRequest`
230
- JSON format:
211
+ `template create` also accepts `--resource cpu=1 memory=512Mi disk=2Gi`, `--entrypoint` (repeat per argv item), `--env KEY=VALUE`, `--metadata KEY=VALUE`, `--format native|overlaybd`, `--readiness-probe`, and `--warmup-seconds`. Or pass a request file in the public `CreateTemplateRequest` JSON format:
231
212
 
232
213
  ```bash
233
214
  osb template create -f template-request.json -o json
@@ -266,9 +247,7 @@ osb sandbox metrics <sandbox-id> --watch -o raw
266
247
 
267
248
  ### Pause a sandbox
268
249
 
269
- Pause is asynchronous. `Pause request accepted` confirms that the server accepted
270
- the request, not that the sandbox has reached the `Paused` state. Poll the sandbox
271
- until the transition finishes:
250
+ Pause is asynchronous. `Pause request accepted` confirms that the server accepted the request, not that the sandbox has reached the `Paused` state. Poll the sandbox until the transition finishes:
272
251
 
273
252
  ```bash
274
253
  osb sandbox pause <sandbox-id>
@@ -297,16 +276,9 @@ osb command status <sandbox-id> <execution-id> -o json
297
276
  osb command logs <sandbox-id> <execution-id> -o json
298
277
  ```
299
278
 
300
- Background start errors or a stream ending before startup confirmation make the
301
- CLI exit non-zero without success metadata. After a successful start, use
302
- `command status` and `command logs` to check the process's eventual result.
279
+ Background start errors or a stream ending before startup confirmation make the CLI exit non-zero without success metadata. After a successful start, use `command status` and `command logs` to check the process's eventual result.
303
280
 
304
- By default the payload after `--` is joined into one shell command string, so
305
- pipelines, redirection, and `$VAR` expansion work as in a terminal. Add `--argv`
306
- to pass the arguments to the executable as a literal argv list (no shell) when
307
- values such as `$HOME`, quotes, embedded spaces, or empty strings must reach the
308
- process unchanged. `--argv` needs a sandbox image whose execd accepts argv
309
- requests:
281
+ By default the payload after `--` is joined into one shell command string, so pipelines, redirection, and `$VAR` expansion work as in a terminal. Add `--argv` to pass the arguments to the executable as a literal argv list (no shell) when values such as `$HOME`, quotes, embedded spaces, or empty strings must reach the process unchanged. `--argv` needs a sandbox image whose execd accepts argv requests:
310
282
 
311
283
  ```bash
312
284
  osb command run <sandbox-id> -o raw --argv -- python3 -c "import sys; print(sys.argv[1:])" "a b" '$HOME' "x'y" ""
@@ -333,10 +305,7 @@ osb file replace <sandbox-id> /workspace/app.py --old old --new new -o json
333
305
  osb file chmod <sandbox-id> /workspace/script.sh --mode 755 -o json
334
306
  ```
335
307
 
336
- Downloads replace regular files only on success; a failed or interrupted
337
- download preserves any existing regular file. Existing devices and named pipes
338
- receive data directly. Stdout aliases (such as `/dev/stdout`) stream only the file
339
- bytes, without a success message. See the [CLI guide](../docs/cli/index.md#work-with-files).
308
+ Downloads replace regular files only on success; a failed or interrupted download preserves any existing regular file. Existing devices and named pipes receive data directly. Stdout aliases (such as `/dev/stdout`) stream only the file bytes, without a success message. See the [CLI guide](../docs/cli/index.md#work-with-files).
340
309
 
341
310
  ### Manage runtime egress policy
342
311
 
@@ -360,9 +329,7 @@ osb command run <sandbox-id> -o raw -- curl -I https://pypi.org
360
329
 
361
330
  ### Manage Credential Vault
362
331
 
363
- Credential Vault operations call the sandbox egress sidecar through the Python SDK.
364
- Create the sandbox with `--credential-proxy` and an explicit network policy before
365
- writing vault state.
332
+ Credential Vault operations call the sandbox egress sidecar through the Python SDK. Create the sandbox with `--credential-proxy` and an explicit network policy before writing vault state.
366
333
 
367
334
  ```bash
368
335
  osb credential-vault create <sandbox-id> --file vault.yaml -o json
@@ -373,14 +340,14 @@ osb credential-vault binding list <sandbox-id> -o json
373
340
  osb credential-vault delete <sandbox-id> -o json
374
341
  ```
375
342
 
376
- Use `--file -` to read a JSON/YAML payload from stdin. Do not pass plaintext
377
- credential values as command-line flags; keep them in the payload stream or file.
343
+ Use `--file -` to read a JSON/YAML payload from stdin. Do not pass plaintext credential values as command-line flags; keep them in the payload stream or file.
378
344
 
379
345
  ### Collect diagnostics
380
346
 
381
- Use the stable diagnostics commands for API-backed log and event descriptors.
347
+ Use context to identify the runtime backend, then collect log and event descriptors.
382
348
 
383
349
  ```bash
350
+ osb diagnostics context <sandbox-id> -o json
384
351
  osb diagnostics events <sandbox-id> --scope runtime -o raw
385
352
  osb diagnostics events <sandbox-id> --scope all -o raw
386
353
  osb diagnostics logs <sandbox-id> --scope container -o raw
@@ -389,7 +356,9 @@ osb diagnostics events <sandbox-id> --scope runtime -o json
389
356
  osb diagnostics logs <sandbox-id> --scope container -o yaml
390
357
  ```
391
358
 
392
- `--scope` is required for stable diagnostics. The built-in server supports
359
+ `context` returns backend routing information without connecting to execd and
360
+ supports table, JSON, and YAML output. `--scope` is required for logs/events.
361
+ The built-in server supports
393
362
  `container` and `all` for logs, and `runtime` and `all` for events. It returns
394
363
  `DIAGNOSTICS_SCOPE_UNSUPPORTED` for unavailable scopes, including lifecycle events.
395
364
  Best-effort scopes may include a `warnings` field when the backend can only
@@ -399,8 +368,7 @@ temporary URL. Structured CLI output follows the SDK/Python field style, for
399
368
  example `content_url`, `content_length`, and `expires_at`. Older server builds
400
369
  may still return `DIAGNOSTICS_NOT_IMPLEMENTED` for scoped diagnostics.
401
370
 
402
- Legacy DevOps diagnostics remain experimental. Prefer `osb diagnostics logs/events`
403
- for stable API-backed log and event collection.
371
+ Legacy DevOps diagnostics remain experimental. Prefer `osb diagnostics logs/events` for stable API-backed log and event collection.
404
372
 
405
373
  ```bash
406
374
  osb devops inspect <sandbox-id> -o raw
@@ -468,12 +436,12 @@ Supported targets:
468
436
 
469
437
  | Target | Install location |
470
438
  | --- | --- |
471
- | `claude` | `./.claude/skills/` or `~/.claude/skills/` |
439
+ | `claude` | `./.claude/skills/<name>/SKILL.md` or `~/.claude/skills/<name>/SKILL.md` |
472
440
  | `cursor` | `./.cursor/rules/` or `~/.cursor/rules/` |
473
441
  | `codex` | `./.codex/skills/<name>/SKILL.md` or `~/.codex/skills/<name>/SKILL.md` |
474
- | `copilot` | `./.github/copilot-instructions.md` or `~/.github/copilot-instructions.md` |
442
+ | `copilot` | `./.github/copilot-instructions.md` or `~/.copilot/copilot-instructions.md` |
475
443
  | `windsurf` | `./.windsurfrules` or `~/.windsurfrules` |
476
- | `cline` | `./.clinerules` or `~/.clinerules` |
444
+ | `cline` | `./.clinerules` or `~/.cline/rules/opensandbox-<name>.md` |
477
445
  | `opencode` | `./.agents/skills/<name>/SKILL.md` or `~/.agents/skills/<name>/SKILL.md` |
478
446
 
479
447
  Common flows:
@@ -42,9 +42,8 @@ classifiers = [
42
42
  "Typing :: Typed",
43
43
  ]
44
44
  dependencies = [
45
- # `command run --argv` passes a list to commands.run; the SDK accepts a
46
- # list there from 0.1.17. Release the CLI only after that SDK is published.
47
- "opensandbox>=1.1.1,<2.0.0",
45
+ # Keep this test CLI on the matching published SDK development build.
46
+ "opensandbox==1.1.2.dev20261020",
48
47
  "click>=8.1.0,<9.0",
49
48
  "rich>=13.0.0,<14.0",
50
49
  "pyyaml>=6.0,<7.0",
@@ -64,6 +64,24 @@ def diagnostics_group(ctx: click.Context) -> None:
64
64
  click.echo(ctx.get_help())
65
65
 
66
66
 
67
+ @diagnostics_group.command("context")
68
+ @click.argument("sandbox_id")
69
+ @output_option("table", "json", "yaml")
70
+ @click.pass_obj
71
+ @handle_errors
72
+ def diagnostics_context(
73
+ obj: ClientContext,
74
+ sandbox_id: str,
75
+ output_format: str | None,
76
+ ) -> None:
77
+ """Identify the sandbox backend and its diagnostic identifiers."""
78
+ context = obj.get_manager().get_diagnostic_context(sandbox_id)
79
+ output = prepare_output(
80
+ obj, output_format, allowed=("table", "json", "yaml"), fallback="table"
81
+ )
82
+ output.print_dict(context.model_dump(mode="json"), title="Diagnostic Context")
83
+
84
+
67
85
  @diagnostics_group.command("logs")
68
86
  @click.argument("sandbox_id")
69
87
  @click.option(
@@ -823,14 +823,14 @@ def sandbox_get(obj: ClientContext, sandbox_id: str, output_format: str | None)
823
823
  def sandbox_kill(
824
824
  obj: ClientContext, sandbox_ids: tuple[str, ...], output_format: str | None
825
825
  ) -> None:
826
- """Terminate one or more sandboxes."""
826
+ """Request deletion of one or more sandboxes; cleanup may continue."""
827
827
  prepare_output(obj, output_format, allowed=("table", "json", "yaml"), fallback="table")
828
828
  mgr = obj.get_manager()
829
829
  rows: list[dict[str, str]] = []
830
830
  for sid in sandbox_ids:
831
831
  with obj.output.spinner(f"Killing sandbox {sid}..."):
832
832
  mgr.kill_sandbox(sid)
833
- rows.append({"sandbox_id": sid, "status": "terminated"})
833
+ rows.append({"sandbox_id": sid, "status": "deletion_requested"})
834
834
  obj.output.print_rows(rows, columns=["sandbox_id", "status"], title="Sandboxes")
835
835
 
836
836
 
@@ -69,11 +69,13 @@ _TARGETS = cast(dict[str, TargetConfig], {
69
69
  "project": {
70
70
  "strategy": "copy",
71
71
  "dest_dir": Path(".claude") / "skills",
72
+ "dest_file_template": "{slug}/SKILL.md",
72
73
  "preserve_frontmatter": True,
73
74
  },
74
75
  "global": {
75
76
  "strategy": "copy",
76
77
  "dest_dir": Path.home() / ".claude" / "skills",
78
+ "dest_file_template": "{slug}/SKILL.md",
77
79
  "preserve_frontmatter": True,
78
80
  }
79
81
  },
@@ -122,7 +124,7 @@ _TARGETS = cast(dict[str, TargetConfig], {
122
124
  },
123
125
  "global": {
124
126
  "strategy": "append",
125
- "dest_file": Path.home() / ".github" / "copilot-instructions.md",
127
+ "dest_file": Path.home() / ".copilot" / "copilot-instructions.md",
126
128
  "preserve_frontmatter": False,
127
129
  }
128
130
  },
@@ -151,8 +153,9 @@ _TARGETS = cast(dict[str, TargetConfig], {
151
153
  "preserve_frontmatter": False,
152
154
  },
153
155
  "global": {
154
- "strategy": "append",
155
- "dest_file": Path.home() / ".clinerules",
156
+ "strategy": "copy",
157
+ "dest_dir": Path.home() / ".cline" / "rules",
158
+ "dest_file_template": "opensandbox-{slug}.md",
156
159
  "preserve_frontmatter": False,
157
160
  }
158
161
  },
@@ -85,6 +85,12 @@ def resolve_config(
85
85
  ) -> dict[str, Any]:
86
86
  """Merge config from all sources and return a flat dict.
87
87
 
88
+ Values that come from the config file are coerced to the type declared for
89
+ each key. A file may legitimately encode a value with a different TOML type
90
+ than the CLI expects (for example an all-digit ``connection.api_key`` or a
91
+ ``defaults.timeout`` given in seconds), and callers downstream rely on the
92
+ declared type.
93
+
88
94
  Keys returned:
89
95
  - api_key, domain, protocol, request_timeout (int seconds), use_server_proxy (bool)
90
96
  - default_image, default_timeout (str like "10m")
@@ -97,27 +103,27 @@ def resolve_config(
97
103
  return {
98
104
  "api_key": cli_api_key
99
105
  or os.getenv("OPEN_SANDBOX_API_KEY")
100
- or conn.get("api_key"),
106
+ or _str_or_none(conn.get("api_key")),
101
107
  "domain": cli_domain
102
108
  or os.getenv("OPEN_SANDBOX_DOMAIN")
103
- or conn.get("domain"),
109
+ or _str_or_none(conn.get("domain")),
104
110
  "protocol": cli_protocol
105
111
  or os.getenv("OPEN_SANDBOX_PROTOCOL")
106
- or conn.get("protocol")
112
+ or _str_or_none(conn.get("protocol"))
107
113
  or "http",
108
114
  "request_timeout": cli_timeout
109
115
  or _int_or_none(os.getenv("OPEN_SANDBOX_REQUEST_TIMEOUT"))
110
- or conn.get("request_timeout")
116
+ or _int_or_none(conn.get("request_timeout"))
111
117
  or 30,
112
118
  "use_server_proxy": _coalesce(
113
119
  cli_use_server_proxy,
114
120
  _bool_or_none(os.getenv("OPEN_SANDBOX_USE_SERVER_PROXY")),
115
- conn.get("use_server_proxy"),
121
+ _bool_or_none(conn.get("use_server_proxy")),
116
122
  False,
117
123
  ),
118
- "color": output_cfg.get("color", True),
119
- "default_image": defaults.get("image"),
120
- "default_timeout": defaults.get("timeout"),
124
+ "color": _coalesce(_bool_or_none(output_cfg.get("color")), True),
125
+ "default_image": _str_or_none(defaults.get("image")),
126
+ "default_timeout": _str_or_none(defaults.get("timeout")),
121
127
  }
122
128
 
123
129
 
@@ -133,18 +139,34 @@ def init_config_file(config_path: Path | None = None, *, force: bool = False) ->
133
139
  return path
134
140
 
135
141
 
136
- def _int_or_none(value: str | None) -> int | None:
137
- if value is None:
138
- return None
139
- try:
140
- return int(value)
141
- except ValueError:
142
+ def _int_or_none(value: Any) -> int | None:
143
+ """Coerce a configured value to ``int``, or ``None`` when it is not numeric."""
144
+ if value is None or isinstance(value, bool):
142
145
  return None
146
+ if isinstance(value, int):
147
+ return value
148
+ if isinstance(value, (str, float)):
149
+ try:
150
+ return int(value)
151
+ except (TypeError, ValueError, OverflowError):
152
+ return None
153
+ return None
154
+
143
155
 
156
+ def _bool_or_none(value: Any) -> bool | None:
157
+ """Coerce a configured value to ``bool``, or ``None`` when it is not boolean.
144
158
 
145
- def _bool_or_none(value: str | None) -> bool | None:
159
+ ``0``/``1`` are accepted so hand-edited files that write a TOML integer for a
160
+ boolean key keep resolving instead of silently falling back to the default.
161
+ """
146
162
  if value is None:
147
163
  return None
164
+ if isinstance(value, bool):
165
+ return value
166
+ if isinstance(value, (int, float)):
167
+ return bool(value) if value in (0, 1) else None
168
+ if not isinstance(value, str):
169
+ return None
148
170
  normalized = value.strip().lower()
149
171
  if normalized in ("1", "true", "yes", "on"):
150
172
  return True
@@ -153,6 +175,17 @@ def _bool_or_none(value: str | None) -> bool | None:
153
175
  return None
154
176
 
155
177
 
178
+ def _str_or_none(value: Any) -> str | None:
179
+ """Coerce a configured value to ``str``, or ``None`` when it has no text form."""
180
+ if value is None or isinstance(value, bool):
181
+ return None
182
+ if isinstance(value, str):
183
+ return value
184
+ if isinstance(value, (int, float)):
185
+ return str(value)
186
+ return None
187
+
188
+
156
189
  def _coalesce(*values: Any) -> Any:
157
190
  for value in values:
158
191
  if value is not None:
@@ -16,6 +16,7 @@
16
16
 
17
17
  from __future__ import annotations
18
18
 
19
+ import sys
19
20
  from pathlib import Path
20
21
 
21
22
  import click
@@ -48,9 +49,46 @@ BANNER = r"""[bold cyan]
48
49
  """
49
50
 
50
51
 
52
+ # A representative icon: any encoding that cannot render it would raise on output.
53
+ _OUTPUT_PROBE = "\u2705"
54
+
55
+
56
+ def _configure_output_streams() -> None:
57
+ """Keep CLI output writable when the active encoding cannot represent it.
58
+
59
+ Click and Rich print emoji and box-drawing characters. On a legacy Windows
60
+ code page (cp936, cp1252, ...) or a POSIX ``C`` locale writing them raises
61
+ ``UnicodeEncodeError``, which aborts the command - even after the work is
62
+ done - so streams whose encoding cannot represent that output escape the
63
+ characters instead of raising.
64
+
65
+ Escaping (``backslashreplace``) keeps payload writes - raw command and
66
+ file data, machine-readable output - recoverable; ``errors="replace"``
67
+ would silently corrupt them into ``?``. CPython uses the same handler for
68
+ Windows stderr.
69
+ """
70
+ for stream in (sys.stdout, sys.stderr):
71
+ reconfigure = getattr(stream, "reconfigure", None)
72
+ encoding = getattr(stream, "encoding", None)
73
+ if reconfigure is None or encoding is None:
74
+ continue
75
+ try:
76
+ _OUTPUT_PROBE.encode(encoding)
77
+ except (LookupError, UnicodeEncodeError):
78
+ try:
79
+ reconfigure(errors="backslashreplace")
80
+ except (OSError, ValueError): # pragma: no cover - detached stream
81
+ pass
82
+
83
+
51
84
  class BannerGroup(click.Group):
52
85
  """Custom Click group that shows a banner before help text."""
53
86
 
87
+ def parse_args(self, ctx: click.Context, args: list[str]) -> list[str]:
88
+ """Configure output streams before Click renders help or runs a command."""
89
+ _configure_output_streams()
90
+ return super().parse_args(ctx, args)
91
+
54
92
  def format_help(self, ctx: click.Context, formatter: click.HelpFormatter) -> None:
55
93
  console = Console(stderr=False)
56
94
  console.print(BANNER.format(version=__version__))
@@ -59,6 +59,18 @@ _DIM_COLUMNS = {"created_at", "expires_at", "modified_at", "updated_at"}
59
59
  _ID_COLUMNS = {"id", "sandbox_id", "execution_id", "context_id"}
60
60
 
61
61
 
62
+ def _stdout_can_encode(text: str) -> bool:
63
+ """Return whether stdout can represent ``text`` without substitution."""
64
+ encoding = getattr(sys.stdout, "encoding", None)
65
+ if encoding is None:
66
+ return True
67
+ try:
68
+ text.encode(encoding)
69
+ except (LookupError, UnicodeEncodeError):
70
+ return False
71
+ return True
72
+
73
+
62
74
  def _style_value(col: str, value: str) -> Text:
63
75
  """Apply contextual styling to a cell value."""
64
76
  lower = value.lower()
@@ -248,7 +260,15 @@ class OutputFormatter:
248
260
  "PyYAML is not installed. Use --output json instead.", fg="red", err=True
249
261
  )
250
262
  sys.exit(1)
251
- click.echo(yaml.dump(data, default_flow_style=False, allow_unicode=True).rstrip())
263
+ text = yaml.dump(data, default_flow_style=False, allow_unicode=True).rstrip()
264
+ if not _stdout_can_encode(text):
265
+ # Keep machine-readable output valid on limited code pages: escape
266
+ # non-ASCII into round-trippable sequences instead of letting the
267
+ # stream replace characters with "?".
268
+ text = yaml.dump(
269
+ data, default_flow_style=False, allow_unicode=False
270
+ ).rstrip()
271
+ click.echo(text)
252
272
 
253
273
  def _print_kv_table(self, data: dict[str, Any], *, title: str | None = None) -> None:
254
274
  table = Table(
@@ -50,12 +50,9 @@ osb command session run <sandbox-id> <session-id> -o raw -- sh -lc 'pwd'
50
50
 
51
51
  Treat these as three distinct execution paths:
52
52
 
53
- - `osb command run` without `--background`
54
- Use for foreground one-shot commands when the result should stream directly to the terminal
55
- - `osb command run --background`
56
- Use when the user needs an execution ID, later status checks, or log retrieval
57
- - `osb command session ...`
58
- Use when shell state must persist across commands, such as exported variables or a working directory
53
+ - `osb command run` without `--background` Use for foreground one-shot commands when the result should stream directly to the terminal
54
+ - `osb command run --background` Use when the user needs an execution ID, later status checks, or log retrieval
55
+ - `osb command session ...` Use when shell state must persist across commands, such as exported variables or a working directory
59
56
 
60
57
  ## Golden Paths
61
58
 
@@ -5,36 +5,28 @@ description: Use OpenSandbox Credential Vault commands to manage sandbox-local o
5
5
 
6
6
  # OpenSandbox Credential Vault
7
7
 
8
- Manage outbound credential injection with `osb credential-vault`. Treat vault
9
- changes as a controlled runtime workflow: confirm the sandbox was created with
10
- Credential Proxy, write secrets only through payload files or stdin, inspect
11
- sanitized state, then verify actual outbound behavior from inside the sandbox.
8
+ Manage outbound credential injection with `osb credential-vault`. Treat vault changes as a controlled runtime workflow: confirm the sandbox was created with Credential Proxy, write secrets only through payload files or stdin, inspect sanitized state, then verify actual outbound behavior from inside the sandbox.
12
9
 
13
10
  ## When To Use
14
11
 
15
12
  - the user needs real credentials injected into outbound HTTP or HTTPS requests
16
13
  - the user wants to create, inspect, patch, or delete Credential Vault state
17
14
  - the user needs to add, replace, or remove runtime credentials or bindings
18
- - the user wants to keep plaintext credentials out of sandbox env vars, files,
19
- shell history, and command arguments
15
+ - the user wants to keep plaintext credentials out of sandbox env vars, files, shell history, and command arguments
20
16
 
21
17
  ## Prerequisites
22
18
 
23
- Credential Vault requires an egress sidecar with transparent MITM enabled. Create
24
- the sandbox with both a network policy and Credential Proxy:
19
+ Credential Vault requires an egress sidecar with transparent MITM enabled. Create the sandbox with both a network policy and Credential Proxy:
25
20
 
26
21
  ```bash
27
22
  osb sandbox create --image python:3.12 --network-policy-file network-policy.json --credential-proxy -o json
28
23
  ```
29
24
 
30
- If `credential-vault create`, `patch`, or `delete` returns a precondition error,
31
- the sandbox was likely not created with Credential Proxy, the egress API auth
32
- token is missing, mitmproxy is not ready, or insecure upstream TLS mode is set.
25
+ If `credential-vault create`, `patch`, or `delete` returns a precondition error, the sandbox was likely not created with Credential Proxy, the egress API auth token is missing, mitmproxy is not ready, or insecure upstream TLS mode is set.
33
26
 
34
27
  ## Payload Rules
35
28
 
36
- Use JSON or YAML payload files, or `--file -` for stdin. Do not put real secret
37
- values in command-line flags.
29
+ Use JSON or YAML payload files, or `--file -` for stdin. Do not put real secret values in command-line flags.
38
30
 
39
31
  Create payload shape:
40
32
 
@@ -107,8 +99,7 @@ credentials:
107
99
 
108
100
  ## Inspect Metadata
109
101
 
110
- Vault read APIs return sanitized metadata only. Plaintext credential values are
111
- write-only and should not appear in command output.
102
+ Vault read APIs return sanitized metadata only. Plaintext credential values are write-only and should not appear in command output.
112
103
 
113
104
  ```bash
114
105
  osb credential-vault credential get <sandbox-id> api-token -o json
@@ -117,8 +108,7 @@ osb credential-vault binding get <sandbox-id> api -o json
117
108
 
118
109
  ## Cleanup
119
110
 
120
- Delete all sandbox-local vault state when credential injection is no longer
121
- needed:
111
+ Delete all sandbox-local vault state when credential injection is no longer needed:
122
112
 
123
113
  ```bash
124
114
  osb credential-vault delete <sandbox-id> -o json
@@ -132,5 +122,4 @@ When helping a user:
132
122
  2. Confirm the sandbox was created with `--credential-proxy` and network policy.
133
123
  3. Keep secret material in payload files or stdin, never in CLI flags.
134
124
  4. Use `get` or `list` to inspect sanitized state.
135
- 5. Verify behavior with `osb command run ... -o raw -- curl ...` when injection
136
- behavior matters.
125
+ 5. Verify behavior with `osb command run ... -o raw -- curl ...` when injection behavior matters.
@@ -40,14 +40,10 @@ osb config show -o json
40
40
 
41
41
  Treat these as distinct categories:
42
42
 
43
- - sandbox-only content operations
44
- `cat`, `write`, `replace`, `mv`, `mkdir`, `rm`, `rmdir`, `info`, `chmod`
45
- - host-to-sandbox transfer
46
- `upload`
47
- - sandbox-to-host transfer
48
- `download`
49
- - discovery before modification
50
- `search`, then `info`
43
+ - sandbox-only content operations `cat`, `write`, `replace`, `mv`, `mkdir`, `rm`, `rmdir`, `info`, `chmod`
44
+ - host-to-sandbox transfer `upload`
45
+ - sandbox-to-host transfer `download`
46
+ - discovery before modification `search`, then `info`
51
47
 
52
48
  If the path is uncertain, search first. If the file boundary crosses between host and sandbox, use `upload` or `download` instead of `write` or `cat`.
53
49
 
@@ -56,10 +56,8 @@ osb config show -o json
56
56
 
57
57
  Runtime egress policy consists of:
58
58
 
59
- - `defaultAction`
60
- The fallback action when no rule matches
61
- - `egress`
62
- An ordered list of allow or deny rules
59
+ - `defaultAction` The fallback action when no rule matches
60
+ - `egress` An ordered list of allow or deny rules
63
61
 
64
62
  Important semantics:
65
63
 
@@ -267,7 +267,7 @@ Rules:
267
267
  - use `renew` before long-running work instead of waiting for expiry
268
268
  - use `pause` only when the workload can tolerate suspension
269
269
  - treat the pause result as request acceptance and poll `sandbox get` until the state is `Paused` or `Failed`
270
- - use `kill` when cleanup is the real goal; do not leave orphaned sandboxes behind
270
+ - `kill` returns `deletion_requested` when accepted; poll `sandbox get` until `404` to confirm resource removal. Pool `Noop` preserves processes even after removal.
271
271
 
272
272
  ## Runtime Notes
273
273
 
@@ -66,11 +66,17 @@ Use this order by default:
66
66
 
67
67
  ```bash
68
68
  osb sandbox get <sandbox-id> -o json
69
+ osb diagnostics context <sandbox-id> -o json
69
70
  osb sandbox health <sandbox-id> -o json
70
71
  osb diagnostics events <sandbox-id> --scope runtime -o raw
71
72
  osb diagnostics logs <sandbox-id> --scope container -o raw
72
73
  ```
73
74
 
75
+ Use context's `backend_type` and `backend_identifiers` to select backend-specific
76
+ investigation. Context works before execd is ready. Fast Sandbox supports runtime
77
+ events but not container logs; skip log collection for that backend. If context
78
+ is unavailable on an older server, continue with sandbox state and supported diagnostics.
79
+
74
80
  Then drill down only where the stable diagnostics point:
75
81
 
76
82
  ```bash