opensandbox-cli 1.1.0__tar.gz → 1.1.2.dev20261020__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.
- {opensandbox_cli-1.1.0 → opensandbox_cli-1.1.2.dev20261020}/PKG-INFO +102 -27
- {opensandbox_cli-1.1.0 → opensandbox_cli-1.1.2.dev20261020}/README.md +99 -25
- {opensandbox_cli-1.1.0 → opensandbox_cli-1.1.2.dev20261020}/pyproject.toml +2 -1
- {opensandbox_cli-1.1.0 → opensandbox_cli-1.1.2.dev20261020}/src/opensandbox_cli/client.py +0 -5
- {opensandbox_cli-1.1.0 → opensandbox_cli-1.1.2.dev20261020}/src/opensandbox_cli/commands/command.py +47 -61
- {opensandbox_cli-1.1.0 → opensandbox_cli-1.1.2.dev20261020}/src/opensandbox_cli/commands/config_cmd.py +46 -57
- {opensandbox_cli-1.1.0 → opensandbox_cli-1.1.2.dev20261020}/src/opensandbox_cli/commands/devops.py +0 -9
- {opensandbox_cli-1.1.0 → opensandbox_cli-1.1.2.dev20261020}/src/opensandbox_cli/commands/diagnostics.py +19 -10
- {opensandbox_cli-1.1.0 → opensandbox_cli-1.1.2.dev20261020}/src/opensandbox_cli/commands/file.py +0 -24
- opensandbox_cli-1.1.2.dev20261020/src/opensandbox_cli/commands/sandbox.py +1057 -0
- {opensandbox_cli-1.1.0 → opensandbox_cli-1.1.2.dev20261020}/src/opensandbox_cli/commands/skills.py +6 -6
- opensandbox_cli-1.1.2.dev20261020/src/opensandbox_cli/commands/snapshot.py +170 -0
- opensandbox_cli-1.1.2.dev20261020/src/opensandbox_cli/commands/template.py +324 -0
- {opensandbox_cli-1.1.0 → opensandbox_cli-1.1.2.dev20261020}/src/opensandbox_cli/config.py +48 -15
- {opensandbox_cli-1.1.0 → opensandbox_cli-1.1.2.dev20261020}/src/opensandbox_cli/main.py +42 -5
- {opensandbox_cli-1.1.0 → opensandbox_cli-1.1.2.dev20261020}/src/opensandbox_cli/output.py +49 -87
- {opensandbox_cli-1.1.0 → opensandbox_cli-1.1.2.dev20261020}/src/opensandbox_cli/skill_registry.py +6 -4
- {opensandbox_cli-1.1.0 → opensandbox_cli-1.1.2.dev20261020}/src/opensandbox_cli/skills/opensandbox-command-execution.md +5 -7
- {opensandbox_cli-1.1.0 → opensandbox_cli-1.1.2.dev20261020}/src/opensandbox_cli/skills/opensandbox-credential-vault.md +8 -19
- {opensandbox_cli-1.1.0 → opensandbox_cli-1.1.2.dev20261020}/src/opensandbox_cli/skills/opensandbox-file-operations.md +4 -8
- {opensandbox_cli-1.1.0 → opensandbox_cli-1.1.2.dev20261020}/src/opensandbox_cli/skills/opensandbox-network-egress.md +2 -4
- {opensandbox_cli-1.1.0 → opensandbox_cli-1.1.2.dev20261020}/src/opensandbox_cli/skills/opensandbox-sandbox-lifecycle.md +61 -2
- {opensandbox_cli-1.1.0 → opensandbox_cli-1.1.2.dev20261020}/src/opensandbox_cli/skills/opensandbox-sandbox-troubleshooting.md +6 -0
- {opensandbox_cli-1.1.0 → opensandbox_cli-1.1.2.dev20261020}/src/opensandbox_cli/utils.py +37 -40
- opensandbox_cli-1.1.0/src/opensandbox_cli/commands/sandbox.py +0 -594
- {opensandbox_cli-1.1.0 → opensandbox_cli-1.1.2.dev20261020}/.gitignore +0 -0
- {opensandbox_cli-1.1.0 → opensandbox_cli-1.1.2.dev20261020}/LICENSE +0 -0
- {opensandbox_cli-1.1.0 → opensandbox_cli-1.1.2.dev20261020}/src/opensandbox_cli/__init__.py +0 -0
- {opensandbox_cli-1.1.0 → opensandbox_cli-1.1.2.dev20261020}/src/opensandbox_cli/__main__.py +0 -0
- {opensandbox_cli-1.1.0 → opensandbox_cli-1.1.2.dev20261020}/src/opensandbox_cli/commands/__init__.py +0 -0
- {opensandbox_cli-1.1.0 → opensandbox_cli-1.1.2.dev20261020}/src/opensandbox_cli/commands/credential_vault.py +0 -0
- {opensandbox_cli-1.1.0 → opensandbox_cli-1.1.2.dev20261020}/src/opensandbox_cli/commands/egress.py +0 -0
- {opensandbox_cli-1.1.0 → opensandbox_cli-1.1.2.dev20261020}/src/opensandbox_cli/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: opensandbox-cli
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.2.dev20261020
|
|
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,10 +224,11 @@ 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.
|
|
227
|
+
Requires-Dist: opensandbox<2.0.0,>=1.1.1
|
|
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'
|
|
231
|
+
Requires-Dist: tomlkit<1.0,>=0.13.0
|
|
231
232
|
Description-Content-Type: text/markdown
|
|
232
233
|
|
|
233
234
|
# OpenSandbox CLI
|
|
@@ -235,6 +236,7 @@ Description-Content-Type: text/markdown
|
|
|
235
236
|
`osb` is the command-line interface for OpenSandbox. It is built for the common day-to-day flows:
|
|
236
237
|
|
|
237
238
|
- create and manage sandboxes
|
|
239
|
+
- build golden-image templates and create sandboxes from templates or snapshots
|
|
238
240
|
- run commands inside a sandbox
|
|
239
241
|
- read and modify sandbox files
|
|
240
242
|
- inspect runtime egress policy
|
|
@@ -387,6 +389,84 @@ Create with Credential Vault proxy enabled:
|
|
|
387
389
|
osb sandbox create --image python:3.12 --network-policy-file network-policy.json --credential-proxy -o json
|
|
388
390
|
```
|
|
389
391
|
|
|
392
|
+
Create from a template (golden image):
|
|
393
|
+
|
|
394
|
+
```bash
|
|
395
|
+
osb sandbox create --template <template-id> --timeout 30m -o json
|
|
396
|
+
```
|
|
397
|
+
|
|
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).
|
|
399
|
+
|
|
400
|
+
Create from a snapshot:
|
|
401
|
+
|
|
402
|
+
```bash
|
|
403
|
+
osb sandbox create --snapshot-id <snapshot-id> --timeout 30m -o json
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
`--image`, `--template`, and `--snapshot-id` are mutually exclusive.
|
|
407
|
+
|
|
408
|
+
Create from a request file (`-f`, like `kubectl apply -f`):
|
|
409
|
+
|
|
410
|
+
```bash
|
|
411
|
+
osb sandbox create -f sandbox-request.json -o json
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
`sandbox-request.json` uses the public `CreateSandboxRequest` JSON format (camelCase keys):
|
|
415
|
+
|
|
416
|
+
```json
|
|
417
|
+
{
|
|
418
|
+
"image": "python:3.12",
|
|
419
|
+
"timeout": 1800,
|
|
420
|
+
"resourceLimits": { "cpu": "1", "memory": "2Gi" },
|
|
421
|
+
"entrypoint": ["python", "-m", "http.server"],
|
|
422
|
+
"networkPolicy": {
|
|
423
|
+
"defaultAction": "deny",
|
|
424
|
+
"egress": [{ "action": "allow", "target": "pypi.org" }]
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
```
|
|
428
|
+
|
|
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).
|
|
432
|
+
|
|
433
|
+
### Manage templates
|
|
434
|
+
|
|
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.
|
|
436
|
+
|
|
437
|
+
```bash
|
|
438
|
+
osb template create --image python:3.12 --publish s3://bucket/publish -o json
|
|
439
|
+
osb template get <template-id> -o json
|
|
440
|
+
osb template list -o json
|
|
441
|
+
osb template delete <template-id> -o json
|
|
442
|
+
```
|
|
443
|
+
|
|
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:
|
|
445
|
+
|
|
446
|
+
```bash
|
|
447
|
+
osb template create -f template-request.json -o json
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
```json
|
|
451
|
+
{
|
|
452
|
+
"image": "python:3.12",
|
|
453
|
+
"publish": "s3://bucket/publish",
|
|
454
|
+
"resourceLimits": { "cpu": "1", "memory": "512Mi" },
|
|
455
|
+
"readiness": { "probe": "tcp://127.0.0.1:44772", "warmupSeconds": 60 }
|
|
456
|
+
}
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
### Manage snapshots
|
|
460
|
+
|
|
461
|
+
Snapshots capture a sandbox's state and can back new sandboxes.
|
|
462
|
+
|
|
463
|
+
```bash
|
|
464
|
+
osb snapshot create <sandbox-id> --name golden -o json
|
|
465
|
+
osb snapshot get <snapshot-id> -o json
|
|
466
|
+
osb snapshot list --sandbox-id <sandbox-id> -o json
|
|
467
|
+
osb snapshot delete <snapshot-id> -o json
|
|
468
|
+
```
|
|
469
|
+
|
|
390
470
|
### List and inspect sandboxes
|
|
391
471
|
|
|
392
472
|
```bash
|
|
@@ -400,9 +480,7 @@ osb sandbox metrics <sandbox-id> --watch -o raw
|
|
|
400
480
|
|
|
401
481
|
### Pause a sandbox
|
|
402
482
|
|
|
403
|
-
Pause is asynchronous. `Pause request accepted` confirms that the server accepted
|
|
404
|
-
the request, not that the sandbox has reached the `Paused` state. Poll the sandbox
|
|
405
|
-
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:
|
|
406
484
|
|
|
407
485
|
```bash
|
|
408
486
|
osb sandbox pause <sandbox-id>
|
|
@@ -431,12 +509,9 @@ osb command status <sandbox-id> <execution-id> -o json
|
|
|
431
509
|
osb command logs <sandbox-id> <execution-id> -o json
|
|
432
510
|
```
|
|
433
511
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
to pass the arguments to the executable as a literal argv list (no shell) when
|
|
437
|
-
values such as `$HOME`, quotes, embedded spaces, or empty strings must reach the
|
|
438
|
-
process unchanged. `--argv` needs a sandbox image whose execd accepts argv
|
|
439
|
-
requests:
|
|
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.
|
|
513
|
+
|
|
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:
|
|
440
515
|
|
|
441
516
|
```bash
|
|
442
517
|
osb command run <sandbox-id> -o raw --argv -- python3 -c "import sys; print(sys.argv[1:])" "a b" '$HOME' "x'y" ""
|
|
@@ -463,10 +538,7 @@ osb file replace <sandbox-id> /workspace/app.py --old old --new new -o json
|
|
|
463
538
|
osb file chmod <sandbox-id> /workspace/script.sh --mode 755 -o json
|
|
464
539
|
```
|
|
465
540
|
|
|
466
|
-
Downloads replace regular files only on success; a failed or interrupted
|
|
467
|
-
download preserves any existing regular file. Existing devices and named pipes
|
|
468
|
-
receive data directly. Stdout aliases (such as `/dev/stdout`) stream only the file
|
|
469
|
-
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).
|
|
470
542
|
|
|
471
543
|
### Manage runtime egress policy
|
|
472
544
|
|
|
@@ -490,9 +562,7 @@ osb command run <sandbox-id> -o raw -- curl -I https://pypi.org
|
|
|
490
562
|
|
|
491
563
|
### Manage Credential Vault
|
|
492
564
|
|
|
493
|
-
Credential Vault operations call the sandbox egress sidecar through the Python SDK.
|
|
494
|
-
Create the sandbox with `--credential-proxy` and an explicit network policy before
|
|
495
|
-
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.
|
|
496
566
|
|
|
497
567
|
```bash
|
|
498
568
|
osb credential-vault create <sandbox-id> --file vault.yaml -o json
|
|
@@ -503,14 +573,14 @@ osb credential-vault binding list <sandbox-id> -o json
|
|
|
503
573
|
osb credential-vault delete <sandbox-id> -o json
|
|
504
574
|
```
|
|
505
575
|
|
|
506
|
-
Use `--file -` to read a JSON/YAML payload from stdin. Do not pass plaintext
|
|
507
|
-
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.
|
|
508
577
|
|
|
509
578
|
### Collect diagnostics
|
|
510
579
|
|
|
511
|
-
Use the
|
|
580
|
+
Use context to identify the runtime backend, then collect log and event descriptors.
|
|
512
581
|
|
|
513
582
|
```bash
|
|
583
|
+
osb diagnostics context <sandbox-id> -o json
|
|
514
584
|
osb diagnostics events <sandbox-id> --scope runtime -o raw
|
|
515
585
|
osb diagnostics events <sandbox-id> --scope all -o raw
|
|
516
586
|
osb diagnostics logs <sandbox-id> --scope container -o raw
|
|
@@ -519,7 +589,9 @@ osb diagnostics events <sandbox-id> --scope runtime -o json
|
|
|
519
589
|
osb diagnostics logs <sandbox-id> --scope container -o yaml
|
|
520
590
|
```
|
|
521
591
|
|
|
522
|
-
|
|
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
|
|
523
595
|
`container` and `all` for logs, and `runtime` and `all` for events. It returns
|
|
524
596
|
`DIAGNOSTICS_SCOPE_UNSUPPORTED` for unavailable scopes, including lifecycle events.
|
|
525
597
|
Best-effort scopes may include a `warnings` field when the backend can only
|
|
@@ -529,8 +601,7 @@ temporary URL. Structured CLI output follows the SDK/Python field style, for
|
|
|
529
601
|
example `content_url`, `content_length`, and `expires_at`. Older server builds
|
|
530
602
|
may still return `DIAGNOSTICS_NOT_IMPLEMENTED` for scoped diagnostics.
|
|
531
603
|
|
|
532
|
-
Legacy DevOps diagnostics remain experimental. Prefer `osb diagnostics logs/events`
|
|
533
|
-
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.
|
|
534
605
|
|
|
535
606
|
```bash
|
|
536
607
|
osb devops inspect <sandbox-id> -o raw
|
|
@@ -561,6 +632,8 @@ Not every command supports every format. Use `--help` on the specific command wh
|
|
|
561
632
|
The main command groups are:
|
|
562
633
|
|
|
563
634
|
- `osb sandbox`: lifecycle management
|
|
635
|
+
- `osb template`: golden-image template builds
|
|
636
|
+
- `osb snapshot`: snapshot management
|
|
564
637
|
- `osb command`: command execution and persistent sessions
|
|
565
638
|
- `osb file`: file and directory operations
|
|
566
639
|
- `osb egress`: runtime egress policy
|
|
@@ -573,6 +646,8 @@ Explore them directly:
|
|
|
573
646
|
|
|
574
647
|
```bash
|
|
575
648
|
osb sandbox --help
|
|
649
|
+
osb template --help
|
|
650
|
+
osb snapshot --help
|
|
576
651
|
osb command --help
|
|
577
652
|
osb file --help
|
|
578
653
|
osb skills --help
|
|
@@ -594,12 +669,12 @@ Supported targets:
|
|
|
594
669
|
|
|
595
670
|
| Target | Install location |
|
|
596
671
|
| --- | --- |
|
|
597
|
-
| `claude` | `./.claude/skills
|
|
672
|
+
| `claude` | `./.claude/skills/<name>/SKILL.md` or `~/.claude/skills/<name>/SKILL.md` |
|
|
598
673
|
| `cursor` | `./.cursor/rules/` or `~/.cursor/rules/` |
|
|
599
674
|
| `codex` | `./.codex/skills/<name>/SKILL.md` or `~/.codex/skills/<name>/SKILL.md` |
|
|
600
|
-
| `copilot` | `./.github/copilot-instructions.md` or `~/.
|
|
675
|
+
| `copilot` | `./.github/copilot-instructions.md` or `~/.copilot/copilot-instructions.md` |
|
|
601
676
|
| `windsurf` | `./.windsurfrules` or `~/.windsurfrules` |
|
|
602
|
-
| `cline` | `./.clinerules` or `~/.
|
|
677
|
+
| `cline` | `./.clinerules` or `~/.cline/rules/opensandbox-<name>.md` |
|
|
603
678
|
| `opencode` | `./.agents/skills/<name>/SKILL.md` or `~/.agents/skills/<name>/SKILL.md` |
|
|
604
679
|
|
|
605
680
|
Common flows:
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
`osb` is the command-line interface for OpenSandbox. It is built for the common day-to-day flows:
|
|
4
4
|
|
|
5
5
|
- create and manage sandboxes
|
|
6
|
+
- build golden-image templates and create sandboxes from templates or snapshots
|
|
6
7
|
- run commands inside a sandbox
|
|
7
8
|
- read and modify sandbox files
|
|
8
9
|
- inspect runtime egress policy
|
|
@@ -155,6 +156,84 @@ Create with Credential Vault proxy enabled:
|
|
|
155
156
|
osb sandbox create --image python:3.12 --network-policy-file network-policy.json --credential-proxy -o json
|
|
156
157
|
```
|
|
157
158
|
|
|
159
|
+
Create from a template (golden image):
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
osb sandbox create --template <template-id> --timeout 30m -o json
|
|
163
|
+
```
|
|
164
|
+
|
|
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).
|
|
166
|
+
|
|
167
|
+
Create from a snapshot:
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
osb sandbox create --snapshot-id <snapshot-id> --timeout 30m -o json
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
`--image`, `--template`, and `--snapshot-id` are mutually exclusive.
|
|
174
|
+
|
|
175
|
+
Create from a request file (`-f`, like `kubectl apply -f`):
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
osb sandbox create -f sandbox-request.json -o json
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
`sandbox-request.json` uses the public `CreateSandboxRequest` JSON format (camelCase keys):
|
|
182
|
+
|
|
183
|
+
```json
|
|
184
|
+
{
|
|
185
|
+
"image": "python:3.12",
|
|
186
|
+
"timeout": 1800,
|
|
187
|
+
"resourceLimits": { "cpu": "1", "memory": "2Gi" },
|
|
188
|
+
"entrypoint": ["python", "-m", "http.server"],
|
|
189
|
+
"networkPolicy": {
|
|
190
|
+
"defaultAction": "deny",
|
|
191
|
+
"egress": [{ "action": "allow", "target": "pypi.org" }]
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
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).
|
|
199
|
+
|
|
200
|
+
### Manage templates
|
|
201
|
+
|
|
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.
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
osb template create --image python:3.12 --publish s3://bucket/publish -o json
|
|
206
|
+
osb template get <template-id> -o json
|
|
207
|
+
osb template list -o json
|
|
208
|
+
osb template delete <template-id> -o json
|
|
209
|
+
```
|
|
210
|
+
|
|
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:
|
|
212
|
+
|
|
213
|
+
```bash
|
|
214
|
+
osb template create -f template-request.json -o json
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
```json
|
|
218
|
+
{
|
|
219
|
+
"image": "python:3.12",
|
|
220
|
+
"publish": "s3://bucket/publish",
|
|
221
|
+
"resourceLimits": { "cpu": "1", "memory": "512Mi" },
|
|
222
|
+
"readiness": { "probe": "tcp://127.0.0.1:44772", "warmupSeconds": 60 }
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Manage snapshots
|
|
227
|
+
|
|
228
|
+
Snapshots capture a sandbox's state and can back new sandboxes.
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
osb snapshot create <sandbox-id> --name golden -o json
|
|
232
|
+
osb snapshot get <snapshot-id> -o json
|
|
233
|
+
osb snapshot list --sandbox-id <sandbox-id> -o json
|
|
234
|
+
osb snapshot delete <snapshot-id> -o json
|
|
235
|
+
```
|
|
236
|
+
|
|
158
237
|
### List and inspect sandboxes
|
|
159
238
|
|
|
160
239
|
```bash
|
|
@@ -168,9 +247,7 @@ osb sandbox metrics <sandbox-id> --watch -o raw
|
|
|
168
247
|
|
|
169
248
|
### Pause a sandbox
|
|
170
249
|
|
|
171
|
-
Pause is asynchronous. `Pause request accepted` confirms that the server accepted
|
|
172
|
-
the request, not that the sandbox has reached the `Paused` state. Poll the sandbox
|
|
173
|
-
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:
|
|
174
251
|
|
|
175
252
|
```bash
|
|
176
253
|
osb sandbox pause <sandbox-id>
|
|
@@ -199,12 +276,9 @@ osb command status <sandbox-id> <execution-id> -o json
|
|
|
199
276
|
osb command logs <sandbox-id> <execution-id> -o json
|
|
200
277
|
```
|
|
201
278
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
to pass the arguments to the executable as a literal argv list (no shell) when
|
|
205
|
-
values such as `$HOME`, quotes, embedded spaces, or empty strings must reach the
|
|
206
|
-
process unchanged. `--argv` needs a sandbox image whose execd accepts argv
|
|
207
|
-
requests:
|
|
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.
|
|
280
|
+
|
|
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:
|
|
208
282
|
|
|
209
283
|
```bash
|
|
210
284
|
osb command run <sandbox-id> -o raw --argv -- python3 -c "import sys; print(sys.argv[1:])" "a b" '$HOME' "x'y" ""
|
|
@@ -231,10 +305,7 @@ osb file replace <sandbox-id> /workspace/app.py --old old --new new -o json
|
|
|
231
305
|
osb file chmod <sandbox-id> /workspace/script.sh --mode 755 -o json
|
|
232
306
|
```
|
|
233
307
|
|
|
234
|
-
Downloads replace regular files only on success; a failed or interrupted
|
|
235
|
-
download preserves any existing regular file. Existing devices and named pipes
|
|
236
|
-
receive data directly. Stdout aliases (such as `/dev/stdout`) stream only the file
|
|
237
|
-
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).
|
|
238
309
|
|
|
239
310
|
### Manage runtime egress policy
|
|
240
311
|
|
|
@@ -258,9 +329,7 @@ osb command run <sandbox-id> -o raw -- curl -I https://pypi.org
|
|
|
258
329
|
|
|
259
330
|
### Manage Credential Vault
|
|
260
331
|
|
|
261
|
-
Credential Vault operations call the sandbox egress sidecar through the Python SDK.
|
|
262
|
-
Create the sandbox with `--credential-proxy` and an explicit network policy before
|
|
263
|
-
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.
|
|
264
333
|
|
|
265
334
|
```bash
|
|
266
335
|
osb credential-vault create <sandbox-id> --file vault.yaml -o json
|
|
@@ -271,14 +340,14 @@ osb credential-vault binding list <sandbox-id> -o json
|
|
|
271
340
|
osb credential-vault delete <sandbox-id> -o json
|
|
272
341
|
```
|
|
273
342
|
|
|
274
|
-
Use `--file -` to read a JSON/YAML payload from stdin. Do not pass plaintext
|
|
275
|
-
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.
|
|
276
344
|
|
|
277
345
|
### Collect diagnostics
|
|
278
346
|
|
|
279
|
-
Use the
|
|
347
|
+
Use context to identify the runtime backend, then collect log and event descriptors.
|
|
280
348
|
|
|
281
349
|
```bash
|
|
350
|
+
osb diagnostics context <sandbox-id> -o json
|
|
282
351
|
osb diagnostics events <sandbox-id> --scope runtime -o raw
|
|
283
352
|
osb diagnostics events <sandbox-id> --scope all -o raw
|
|
284
353
|
osb diagnostics logs <sandbox-id> --scope container -o raw
|
|
@@ -287,7 +356,9 @@ osb diagnostics events <sandbox-id> --scope runtime -o json
|
|
|
287
356
|
osb diagnostics logs <sandbox-id> --scope container -o yaml
|
|
288
357
|
```
|
|
289
358
|
|
|
290
|
-
|
|
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
|
|
291
362
|
`container` and `all` for logs, and `runtime` and `all` for events. It returns
|
|
292
363
|
`DIAGNOSTICS_SCOPE_UNSUPPORTED` for unavailable scopes, including lifecycle events.
|
|
293
364
|
Best-effort scopes may include a `warnings` field when the backend can only
|
|
@@ -297,8 +368,7 @@ temporary URL. Structured CLI output follows the SDK/Python field style, for
|
|
|
297
368
|
example `content_url`, `content_length`, and `expires_at`. Older server builds
|
|
298
369
|
may still return `DIAGNOSTICS_NOT_IMPLEMENTED` for scoped diagnostics.
|
|
299
370
|
|
|
300
|
-
Legacy DevOps diagnostics remain experimental. Prefer `osb diagnostics logs/events`
|
|
301
|
-
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.
|
|
302
372
|
|
|
303
373
|
```bash
|
|
304
374
|
osb devops inspect <sandbox-id> -o raw
|
|
@@ -329,6 +399,8 @@ Not every command supports every format. Use `--help` on the specific command wh
|
|
|
329
399
|
The main command groups are:
|
|
330
400
|
|
|
331
401
|
- `osb sandbox`: lifecycle management
|
|
402
|
+
- `osb template`: golden-image template builds
|
|
403
|
+
- `osb snapshot`: snapshot management
|
|
332
404
|
- `osb command`: command execution and persistent sessions
|
|
333
405
|
- `osb file`: file and directory operations
|
|
334
406
|
- `osb egress`: runtime egress policy
|
|
@@ -341,6 +413,8 @@ Explore them directly:
|
|
|
341
413
|
|
|
342
414
|
```bash
|
|
343
415
|
osb sandbox --help
|
|
416
|
+
osb template --help
|
|
417
|
+
osb snapshot --help
|
|
344
418
|
osb command --help
|
|
345
419
|
osb file --help
|
|
346
420
|
osb skills --help
|
|
@@ -362,12 +436,12 @@ Supported targets:
|
|
|
362
436
|
|
|
363
437
|
| Target | Install location |
|
|
364
438
|
| --- | --- |
|
|
365
|
-
| `claude` | `./.claude/skills
|
|
439
|
+
| `claude` | `./.claude/skills/<name>/SKILL.md` or `~/.claude/skills/<name>/SKILL.md` |
|
|
366
440
|
| `cursor` | `./.cursor/rules/` or `~/.cursor/rules/` |
|
|
367
441
|
| `codex` | `./.codex/skills/<name>/SKILL.md` or `~/.codex/skills/<name>/SKILL.md` |
|
|
368
|
-
| `copilot` | `./.github/copilot-instructions.md` or `~/.
|
|
442
|
+
| `copilot` | `./.github/copilot-instructions.md` or `~/.copilot/copilot-instructions.md` |
|
|
369
443
|
| `windsurf` | `./.windsurfrules` or `~/.windsurfrules` |
|
|
370
|
-
| `cline` | `./.clinerules` or `~/.
|
|
444
|
+
| `cline` | `./.clinerules` or `~/.cline/rules/opensandbox-<name>.md` |
|
|
371
445
|
| `opencode` | `./.agents/skills/<name>/SKILL.md` or `~/.agents/skills/<name>/SKILL.md` |
|
|
372
446
|
|
|
373
447
|
Common flows:
|
|
@@ -44,10 +44,11 @@ classifiers = [
|
|
|
44
44
|
dependencies = [
|
|
45
45
|
# `command run --argv` passes a list to commands.run; the SDK accepts a
|
|
46
46
|
# list there from 0.1.17. Release the CLI only after that SDK is published.
|
|
47
|
-
"opensandbox>=1.1.
|
|
47
|
+
"opensandbox>=1.1.1,<2.0.0",
|
|
48
48
|
"click>=8.1.0,<9.0",
|
|
49
49
|
"rich>=13.0.0,<14.0",
|
|
50
50
|
"pyyaml>=6.0,<7.0",
|
|
51
|
+
"tomlkit>=0.13.0,<1.0",
|
|
51
52
|
"tomli>=2.0.0; python_version < '3.11'",
|
|
52
53
|
]
|
|
53
54
|
|
|
@@ -90,15 +90,10 @@ class ClientContext:
|
|
|
90
90
|
self._manager = SandboxManagerSync.create(self.connection_config)
|
|
91
91
|
return self._manager
|
|
92
92
|
|
|
93
|
-
def resolve_sandbox_id(self, sandbox_id: str) -> str:
|
|
94
|
-
"""Return the sandbox ID exactly as provided by the user."""
|
|
95
|
-
return sandbox_id
|
|
96
|
-
|
|
97
93
|
def connect_sandbox(
|
|
98
94
|
self, sandbox_id: str, *, skip_health_check: bool = True
|
|
99
95
|
) -> SandboxSync:
|
|
100
96
|
"""Connect to an existing sandbox by ID."""
|
|
101
|
-
sandbox_id = self.resolve_sandbox_id(sandbox_id)
|
|
102
97
|
return SandboxSync.connect(
|
|
103
98
|
sandbox_id,
|
|
104
99
|
connection_config=self.connection_config,
|
{opensandbox_cli-1.1.0 → opensandbox_cli-1.1.2.dev20261020}/src/opensandbox_cli/commands/command.py
RENAMED
|
@@ -18,10 +18,11 @@ from __future__ import annotations
|
|
|
18
18
|
|
|
19
19
|
import shlex
|
|
20
20
|
import sys
|
|
21
|
+
from collections.abc import Callable
|
|
21
22
|
from datetime import timedelta
|
|
22
23
|
|
|
23
24
|
import click
|
|
24
|
-
from opensandbox.models.execd import OutputMessage, RunCommandOpts
|
|
25
|
+
from opensandbox.models.execd import Execution, OutputMessage, RunCommandOpts
|
|
25
26
|
from opensandbox.models.execd_sync import ExecutionHandlersSync
|
|
26
27
|
|
|
27
28
|
from opensandbox_cli.client import ClientContext
|
|
@@ -41,7 +42,29 @@ def command_group(ctx: click.Context) -> None:
|
|
|
41
42
|
click.echo(ctx.get_help())
|
|
42
43
|
|
|
43
44
|
|
|
44
|
-
|
|
45
|
+
def _stream_handlers() -> tuple[ExecutionHandlersSync, Callable[[], None]]:
|
|
46
|
+
"""Create stdout/stderr streaming handlers and a trailing-newline flusher."""
|
|
47
|
+
last_text = ""
|
|
48
|
+
|
|
49
|
+
def on_stdout(msg: OutputMessage) -> None:
|
|
50
|
+
nonlocal last_text
|
|
51
|
+
last_text = msg.text
|
|
52
|
+
sys.stdout.write(msg.text)
|
|
53
|
+
sys.stdout.flush()
|
|
54
|
+
|
|
55
|
+
def on_stderr(msg: OutputMessage) -> None:
|
|
56
|
+
nonlocal last_text
|
|
57
|
+
last_text = msg.text
|
|
58
|
+
sys.stderr.write(msg.text)
|
|
59
|
+
sys.stderr.flush()
|
|
60
|
+
|
|
61
|
+
def flush_newline() -> None:
|
|
62
|
+
if last_text and not last_text.endswith("\n"):
|
|
63
|
+
sys.stdout.write("\n")
|
|
64
|
+
sys.stdout.flush()
|
|
65
|
+
|
|
66
|
+
return ExecutionHandlersSync(on_stdout=on_stdout, on_stderr=on_stderr), flush_newline
|
|
67
|
+
|
|
45
68
|
|
|
46
69
|
def _run_command(
|
|
47
70
|
obj: ClientContext,
|
|
@@ -55,15 +78,10 @@ def _run_command(
|
|
|
55
78
|
) -> None:
|
|
56
79
|
"""Shared implementation for ``command run``.
|
|
57
80
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
Payload contract:
|
|
63
|
-
- default: join the arguments into one shell command string (works against
|
|
64
|
-
every deployed execd)
|
|
65
|
-
- ``--argv``: send the arguments as a literal argv list, no shell; needs an
|
|
66
|
-
execd that accepts the ``argv`` request field
|
|
81
|
+
Foreground mode streams output and allows only ``-o raw``; background mode
|
|
82
|
+
returns a tracked execution object and allows structured output. The payload
|
|
83
|
+
is joined into one shell command string unless ``argv`` sends it as a
|
|
84
|
+
literal argv list (needs an argv-capable execd).
|
|
67
85
|
"""
|
|
68
86
|
allowed = ("table", "json", "yaml") if background else ("raw",)
|
|
69
87
|
fallback = "table" if background else "raw"
|
|
@@ -84,6 +102,7 @@ def _run_command(
|
|
|
84
102
|
|
|
85
103
|
if background:
|
|
86
104
|
execution = sandbox.commands.run(payload, opts=opts)
|
|
105
|
+
_handle_execution_error(obj, execution, background=True)
|
|
87
106
|
obj.output.success_panel(
|
|
88
107
|
{
|
|
89
108
|
"execution_id": execution.id,
|
|
@@ -94,42 +113,33 @@ def _run_command(
|
|
|
94
113
|
)
|
|
95
114
|
return
|
|
96
115
|
|
|
97
|
-
|
|
98
|
-
last_text = ""
|
|
99
|
-
|
|
100
|
-
def on_stdout(msg: OutputMessage) -> None:
|
|
101
|
-
nonlocal last_text
|
|
102
|
-
last_text = msg.text
|
|
103
|
-
sys.stdout.write(msg.text)
|
|
104
|
-
sys.stdout.flush()
|
|
105
|
-
|
|
106
|
-
def on_stderr(msg: OutputMessage) -> None:
|
|
107
|
-
nonlocal last_text
|
|
108
|
-
last_text = msg.text
|
|
109
|
-
sys.stderr.write(msg.text)
|
|
110
|
-
sys.stderr.flush()
|
|
111
|
-
|
|
112
|
-
handlers = ExecutionHandlersSync(on_stdout=on_stdout, on_stderr=on_stderr)
|
|
116
|
+
handlers, flush_newline = _stream_handlers()
|
|
113
117
|
execution = sandbox.commands.run(payload, opts=opts, handlers=handlers)
|
|
114
|
-
|
|
115
|
-
# Ensure terminal prompt starts on a new line
|
|
116
|
-
if last_text and not last_text.endswith("\n"):
|
|
117
|
-
sys.stdout.write("\n")
|
|
118
|
-
sys.stdout.flush()
|
|
119
|
-
|
|
118
|
+
flush_newline()
|
|
120
119
|
_handle_execution_error(obj, execution)
|
|
121
120
|
finally:
|
|
122
121
|
sandbox.close()
|
|
123
122
|
|
|
124
123
|
|
|
125
|
-
def _handle_execution_error(
|
|
126
|
-
|
|
124
|
+
def _handle_execution_error(
|
|
125
|
+
obj: ClientContext, execution: Execution, *, background: bool = False
|
|
126
|
+
) -> None:
|
|
127
|
+
"""Exit non-zero on execution errors or missing completion/startup confirmation."""
|
|
127
128
|
if execution.error:
|
|
128
129
|
obj.output.error_panel(
|
|
129
130
|
f"{execution.error.name}: {execution.error.value}",
|
|
130
131
|
title="Execution Error",
|
|
131
132
|
)
|
|
132
133
|
sys.exit(1)
|
|
134
|
+
if execution.complete is None:
|
|
135
|
+
confirmation = (
|
|
136
|
+
"background command startup was confirmed" if background else "the command finished"
|
|
137
|
+
)
|
|
138
|
+
obj.output.error_panel(
|
|
139
|
+
f"The output stream ended before {confirmation}, so its result is unknown.",
|
|
140
|
+
title="Execution Incomplete",
|
|
141
|
+
)
|
|
142
|
+
sys.exit(1)
|
|
133
143
|
|
|
134
144
|
|
|
135
145
|
@command_group.command(
|
|
@@ -185,8 +195,6 @@ def command_run(
|
|
|
185
195
|
)
|
|
186
196
|
|
|
187
197
|
|
|
188
|
-
# ---- status ---------------------------------------------------------------
|
|
189
|
-
|
|
190
198
|
@command_group.command("status")
|
|
191
199
|
@click.argument("sandbox_id")
|
|
192
200
|
@click.argument("execution_id")
|
|
@@ -209,8 +217,6 @@ def command_status(
|
|
|
209
217
|
sandbox.close()
|
|
210
218
|
|
|
211
219
|
|
|
212
|
-
# ---- logs -----------------------------------------------------------------
|
|
213
|
-
|
|
214
220
|
@command_group.command("logs")
|
|
215
221
|
@click.argument("sandbox_id")
|
|
216
222
|
@click.argument("execution_id")
|
|
@@ -242,8 +248,6 @@ def command_logs(
|
|
|
242
248
|
sandbox.close()
|
|
243
249
|
|
|
244
250
|
|
|
245
|
-
# ---- interrupt ------------------------------------------------------------
|
|
246
|
-
|
|
247
251
|
@command_group.command("interrupt")
|
|
248
252
|
@click.argument("sandbox_id")
|
|
249
253
|
@click.argument("execution_id")
|
|
@@ -330,21 +334,7 @@ def session_run(
|
|
|
330
334
|
cmd_str = " ".join(shlex.quote(arg) for arg in command)
|
|
331
335
|
sandbox = obj.connect_sandbox(sandbox_id)
|
|
332
336
|
try:
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
def on_stdout(msg: OutputMessage) -> None:
|
|
336
|
-
nonlocal last_text
|
|
337
|
-
last_text = msg.text
|
|
338
|
-
sys.stdout.write(msg.text)
|
|
339
|
-
sys.stdout.flush()
|
|
340
|
-
|
|
341
|
-
def on_stderr(msg: OutputMessage) -> None:
|
|
342
|
-
nonlocal last_text
|
|
343
|
-
last_text = msg.text
|
|
344
|
-
sys.stderr.write(msg.text)
|
|
345
|
-
sys.stderr.flush()
|
|
346
|
-
|
|
347
|
-
handlers = ExecutionHandlersSync(on_stdout=on_stdout, on_stderr=on_stderr)
|
|
337
|
+
handlers, flush_newline = _stream_handlers()
|
|
348
338
|
execution = sandbox.commands.run_in_session(
|
|
349
339
|
session_id,
|
|
350
340
|
cmd_str,
|
|
@@ -352,11 +342,7 @@ def session_run(
|
|
|
352
342
|
timeout=timeout,
|
|
353
343
|
handlers=handlers,
|
|
354
344
|
)
|
|
355
|
-
|
|
356
|
-
if last_text and not last_text.endswith("\n"):
|
|
357
|
-
sys.stdout.write("\n")
|
|
358
|
-
sys.stdout.flush()
|
|
359
|
-
|
|
345
|
+
flush_newline()
|
|
360
346
|
_handle_execution_error(obj, execution)
|
|
361
347
|
finally:
|
|
362
348
|
sandbox.close()
|