ghostdock 0.0.14 → 0.0.16

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.
@@ -118,17 +118,19 @@ MCP tool: `images_add`.
118
118
 
119
119
  ### `ghostdock images add-compose`
120
120
 
121
- Add Compose files to the organization’s image catalog. Must include docker-compose.yml/yaml or compose.yml/yaml; ghostdock.* service labels become its manifest. Fails if the name is taken.
121
+ Add a Compose project to the organization’s image catalog. Give its files, or a reference: a Compose project published as an OCI artifact (oci://…), used through include: with an optional override. ghostdock.* service labels (of the files, or of the override) become its manifest. Fails if the name is taken.
122
122
 
123
123
  ```
124
- ghostdock images add-compose --name <string> --files <object> [flags]
124
+ ghostdock images add-compose --name <string> [flags]
125
125
  ```
126
126
 
127
127
  | Argument | Description |
128
128
  | --- | --- |
129
- | `--name <string>` | Image name, unique in the organization. Required. |
130
- | `--files <object>` | Compose files by file name. Must include docker-compose.yml/yaml or compose.yml/yaml. (JSON object) Required. |
131
129
  | `--org <string>` | Organization id or slug, from organizations list. Ids survive renames; slugs change when the organization is renamed. Defaults to GHOSTDOCK_ORG, then `organizations use`. |
130
+ | `--name <string>` | Image name, unique in the organization. Required. |
131
+ | `--files <object>` | Compose files by file name. Must include docker-compose.yml/yaml or compose.yml/yaml. (JSON object) |
132
+ | `--reference <string>` | A published Compose project instead of files, e.g. oci://docker.io/ghostdockhq/vm-compose:ubuntu-26.04. |
133
+ | `--override <string>` | With reference: a Compose file merged into it (services, environment, labels). |
132
134
 
133
135
  MCP tool: `images_add_compose`.
134
136
 
@@ -194,7 +196,7 @@ MCP tool: `images_remove`.
194
196
 
195
197
  ### `ghostdock images update-compose`
196
198
 
197
- Replace a catalog Compose image’s files and/or name. Re-parses its labels.
199
+ Replace a catalog Compose image’s files, reference, override and/or name. Re-parses its labels.
198
200
 
199
201
  ```
200
202
  ghostdock images update-compose <image-id> [flags]
@@ -205,6 +207,8 @@ ghostdock images update-compose <image-id> [flags]
205
207
  | `<image-id>` | Catalog image id, from images list or images get. Required. |
206
208
  | `--org <string>` | Organization id or slug, from organizations list. Ids survive renames; slugs change when the organization is renamed. Defaults to GHOSTDOCK_ORG, then `organizations use`. |
207
209
  | `--files <object>` | New Compose files by file name. Omit to keep the current files. (JSON object) |
210
+ | `--reference <string>` | A new reference (oci://…): the image becomes a Compose reference. |
211
+ | `--override <string>` | A reference’s new override; null removes it. |
208
212
  | `--name <string>` | New image name. Omit to keep it. |
209
213
 
210
214
  MCP tool: `images_update_compose`.
@@ -751,6 +755,7 @@ ghostdock sandboxes commands get <command-id> --sandbox-id <string> --session-id
751
755
  | `--sandbox-id <string>` | Sandbox name (e.g. quiet-harbor) or id, from sandboxes list or sandboxes create. Names are unique within the organization. Required. |
752
756
  | `--container <string>` | Container to work in, by name, from sandboxes containers list. Default: the sandbox’s default container, else the one running container. With several running and no default the call fails and lists them. |
753
757
  | `--session-id <string>` | Session id returned by sandboxes commands run together with the commandId. Required. |
758
+ | `--start` | Start the container first when it is not running, without asking. |
754
759
 
755
760
  MCP tool: `sandboxes_commands_get`.
756
761
 
@@ -769,6 +774,7 @@ ghostdock sandboxes commands kill <command-id> --sandbox-id <string> --session-i
769
774
  | `--sandbox-id <string>` | Sandbox name (e.g. quiet-harbor) or id, from sandboxes list or sandboxes create. Names are unique within the organization. Required. |
770
775
  | `--container <string>` | Container to work in, by name, from sandboxes containers list. Default: the sandbox’s default container, else the one running container. With several running and no default the call fails and lists them. |
771
776
  | `--session-id <string>` | Session id returned by sandboxes commands run together with the commandId. Required. |
777
+ | `--start` | Start the container first when it is not running, without asking. |
772
778
 
773
779
  MCP tool: `sandboxes_commands_kill`.
774
780
 
@@ -788,6 +794,7 @@ ghostdock sandboxes commands logs <command-id> --sandbox-id <string> --session-i
788
794
  | `--container <string>` | Container to work in, by name, from sandboxes containers list. Default: the sandbox’s default container, else the one running container. With several running and no default the call fails and lists them. |
789
795
  | `--session-id <string>` | Session id returned by sandboxes commands run together with the commandId. Required. |
790
796
  | `--offset <integer>` | Character offset to continue reading from. Default 0. |
797
+ | `--start` | Start the container first when it is not running, without asking. |
791
798
 
792
799
  MCP tool: `sandboxes_commands_logs`.
793
800
 
@@ -808,6 +815,7 @@ ghostdock sandboxes commands run <sandbox-id> --command <string> [flags]
808
815
  | `--session-id <string>` | Session from sandboxes shells create, to keep cwd and environment from earlier commands. Omit for a one-off session. |
809
816
  | `--cwd <string>` | Directory to run the command in. |
810
817
  | `--wait-seconds <number>` | Seconds to wait before returning handles to poll: 1 to 120, default 120. |
818
+ | `--start` | Start the container first when it is not running, without asking. |
811
819
 
812
820
  MCP tool: `sandboxes_commands_run`.
813
821
 
@@ -830,6 +838,26 @@ ghostdock sandboxes containers list <sandbox-id> [flags]
830
838
 
831
839
  MCP tool: `sandboxes_containers_list`.
832
840
 
841
+ ### `ghostdock sandboxes containers logs`
842
+
843
+ Read a container’s output, as docker logs does. Oldest first, each line with its stream (stdout, stderr) and time. The last tail lines (default 200), or with after only the lines after that time: pass the last line’s time to follow. truncated says lines were left out. A stopped sandbox has none (it is not started for this).
844
+
845
+ ```
846
+ ghostdock sandboxes containers logs <sandbox-id> <container> [flags]
847
+ ```
848
+
849
+ | Argument | Description |
850
+ | --- | --- |
851
+ | `<sandbox-id>` | Sandbox name (e.g. quiet-harbor) or id, from sandboxes list or sandboxes create. Names are unique within the organization. Required. |
852
+ | `<container>` | Container, by name. Required. |
853
+ | `--org <string>` | Organization id or slug, from organizations list. Ids survive renames; slugs change when the organization is renamed. Defaults to GHOSTDOCK_ORG, then `organizations use`. |
854
+ | `--tail <integer>` | The last N lines (at most 5000). |
855
+ | `--after <string>` | Only lines after this time: the time of the last line already read. |
856
+ | `--follow` | Keep printing new lines until Ctrl-C. |
857
+ | `--timestamps` | Put each line's time in front of it. |
858
+
859
+ MCP tool: `sandboxes_containers_logs`.
860
+
833
861
  ### `ghostdock sandboxes containers protection`
834
862
 
835
863
  Switch a container’s deletion protection on or off. A protected container cannot be removed or recreated (docker rm, compose down) until its protection is off. Starts the sandbox when needed.
@@ -847,6 +875,54 @@ ghostdock sandboxes containers protection <sandbox-id> <container> --protected <
847
875
 
848
876
  MCP tool: `sandboxes_containers_protection`.
849
877
 
878
+ ### `ghostdock sandboxes containers restart`
879
+
880
+ Restart a container, as docker restart does (stopping as docker stop does). Starts the sandbox when needed.
881
+
882
+ ```
883
+ ghostdock sandboxes containers restart <sandbox-id> <container> [flags]
884
+ ```
885
+
886
+ | Argument | Description |
887
+ | --- | --- |
888
+ | `<sandbox-id>` | Sandbox name (e.g. quiet-harbor) or id, from sandboxes list or sandboxes create. Names are unique within the organization. Required. |
889
+ | `<container>` | Container, by name. Required. |
890
+ | `--org <string>` | Organization id or slug, from organizations list. Ids survive renames; slugs change when the organization is renamed. Defaults to GHOSTDOCK_ORG, then `organizations use`. |
891
+
892
+ MCP tool: `sandboxes_containers_restart`.
893
+
894
+ ### `ghostdock sandboxes containers start`
895
+
896
+ Start a stopped container, as docker start does. A running container stays as it is. Starts the sandbox when needed.
897
+
898
+ ```
899
+ ghostdock sandboxes containers start <sandbox-id> <container> [flags]
900
+ ```
901
+
902
+ | Argument | Description |
903
+ | --- | --- |
904
+ | `<sandbox-id>` | Sandbox name (e.g. quiet-harbor) or id, from sandboxes list or sandboxes create. Names are unique within the organization. Required. |
905
+ | `<container>` | Container, by name. Required. |
906
+ | `--org <string>` | Organization id or slug, from organizations list. Ids survive renames; slugs change when the organization is renamed. Defaults to GHOSTDOCK_ORG, then `organizations use`. |
907
+
908
+ MCP tool: `sandboxes_containers_start`.
909
+
910
+ ### `ghostdock sandboxes containers stop`
911
+
912
+ Stop a container, as docker stop does. It gets its own stop signal, then a kill after its own stop timeout. It stays stopped while the sandbox sleeps and wakes, as Docker keeps a stopped container stopped, unless its restart policy is always. On a stopped sandbox it does nothing (note sandbox_stopped). The container main runs the sandbox: its stop is refused (main_is_the_sandbox), pause the sandbox instead. Deletion protection does not block it. note says when it was the default container or the last one running: then SSH, commands and files have nowhere to land until one runs.
913
+
914
+ ```
915
+ ghostdock sandboxes containers stop <sandbox-id> <container> [flags]
916
+ ```
917
+
918
+ | Argument | Description |
919
+ | --- | --- |
920
+ | `<sandbox-id>` | Sandbox name (e.g. quiet-harbor) or id, from sandboxes list or sandboxes create. Names are unique within the organization. Required. |
921
+ | `<container>` | Container, by name. Required. |
922
+ | `--org <string>` | Organization id or slug, from organizations list. Ids survive renames; slugs change when the organization is renamed. Defaults to GHOSTDOCK_ORG, then `organizations use`. |
923
+
924
+ MCP tool: `sandboxes_containers_stop`.
925
+
850
926
  ### `ghostdock sandboxes create`
851
927
 
852
928
  Create a sandbox from a snapshot, OCI image, Dockerfile or Compose files. Inline content only: for a directory or ZIP (Dockerfile COPY/ADD, Compose build files) use `ghostdock sandboxes build`. Snapshots and catalog images can require environment entries, URL ports and MCP names (requiredEnvironment / requiredUrls / requiredMcps); a missing environment entry fails with its names (missingEnvironment), and removing a required URL or MCP fails with them (missingUrls / missingMcps). Returns once the sandbox is running, with `boot`: how long the sandbox (VM) and, when one was started, its container took to start, each in ms. Idle-stop and auto-delete apply (defaults 20 minutes / 14 days). Read the sandboxes skill (`ghostdock skills get sandboxes`) for sources, manifests, lifecycle and providers.
@@ -859,7 +935,7 @@ ghostdock sandboxes create --source <object> [flags]
859
935
  | --- | --- |
860
936
  | `--org <string>` | Organization id or slug, from organizations list. Ids survive renames; slugs change when the organization is renamed. Defaults to GHOSTDOCK_ORG, then `organizations use`. |
861
937
  | `--display-name <string>` | Display name for the sandbox. Optional. |
862
- | `--source <object>` | What the sandbox boots from. Required. "image" boots an org catalog row by id ({ id } from images list / images get — images add OCI bookmarks, images add-dockerfile, and images add-compose rows) or any OCI ref ({ ref }); "dockerfile" builds inline Dockerfile text only (ghostdock.* LABEL lines parsed for manifest; this tool has no build directory, so COPY/ADD of local files fails — use the CLI `ghostdock sandboxes build --kind dockerfile` with a directory or ZIP); "compose" boots Docker-in-Docker from the given inline files (must include docker-compose.yml/yaml or compose.yml/yaml; ghostdock.* service labels parsed for manifest). A Compose project that needs extra files is not accepted here — use `ghostdock sandboxes build --kind compose`. Image, dockerfile, and compose sources never appear in snapshots list. Published static sites use sites publish (CDN), not a snapshot. (JSON object) Required. |
938
+ | `--source <object>` | What the sandbox boots from. Required. "image" boots an org catalog row by id ({ id } from images list / images get — images add OCI bookmarks, images add-dockerfile, and images add-compose rows) or any OCI ref ({ ref }); "dockerfile" builds inline Dockerfile text only (ghostdock.* LABEL lines parsed for manifest; this tool has no build directory, so COPY/ADD of local files fails — use the CLI `ghostdock sandboxes build --kind dockerfile` with a directory or ZIP); "compose" boots Docker-in-Docker from the given inline files (must include docker-compose.yml/yaml or compose.yml/yaml; ghostdock.* service labels parsed for manifest). A Compose project that needs extra files is not accepted here — use `ghostdock sandboxes build --kind compose`. "compose" also takes a published Compose project instead of files ({ reference: "oci://…", override? }): it runs through include:, the override merged into it. Image, dockerfile, and compose sources never appear in snapshots list. Published static sites use sites publish (CDN), not a snapshot. (JSON object) Required. |
863
939
  | `--resources <object>` | Compute sizing. For image/dockerfile/compose: layered on platform defaults when omitted. For snapshot: optional override applied via resize after create. (JSON object) |
864
940
  | `--auto-stop-interval-minutes <number>` | Minutes of inactivity before the platform worker idle-stops the sandbox. -1 means always on. 0 is rejected. Omit for the platform default. Provider-native timers are optional backup. |
865
941
  | `--auto-delete-interval-minutes <number>` | Minutes the sandbox stays stopped before the platform worker deletes the record. 0 deletes it when it stops. -1 keeps it until a person deletes it. Omit for the platform default. Provider-native timers are optional backup. |
@@ -885,6 +961,7 @@ ghostdock sandboxes default-container <sandbox-id> [flags]
885
961
  | `--org <string>` | Organization id or slug, from organizations list. Ids survive renames; slugs change when the organization is renamed. Defaults to GHOSTDOCK_ORG, then `organizations use`. |
886
962
  | `--container <string>` | Container to make the default, by name. The running sandbox must have it. |
887
963
  | `--clear` | Clear the default. |
964
+ | `--start` | Start the container first when it is not running, without asking. |
888
965
 
889
966
  MCP tool: `sandboxes_default_container`.
890
967
 
@@ -995,6 +1072,7 @@ ghostdock sandboxes files list <sandbox-id> [flags]
995
1072
  | `--org <string>` | Organization id or slug, from organizations list. Ids survive renames; slugs change when the organization is renamed. Defaults to GHOSTDOCK_ORG, then `organizations use`. |
996
1073
  | `--container <string>` | Container to work in, by name, from sandboxes containers list. Default: the sandbox’s default container, else the one running container. With several running and no default the call fails and lists them. |
997
1074
  | `--path <string>` | Directory to list. Absolute or relative to /. Defaults to /workspace. |
1075
+ | `--start` | Start the container first when it is not running, without asking. |
998
1076
 
999
1077
  MCP tool: `sandboxes_files_list`.
1000
1078
 
@@ -1012,6 +1090,7 @@ ghostdock sandboxes files read <path> --sandbox-id <string> [flags]
1012
1090
  | `--org <string>` | Organization id or slug, from organizations list. Ids survive renames; slugs change when the organization is renamed. Defaults to GHOSTDOCK_ORG, then `organizations use`. |
1013
1091
  | `--sandbox-id <string>` | Sandbox name (e.g. quiet-harbor) or id, from sandboxes list or sandboxes create. Names are unique within the organization. Required. |
1014
1092
  | `--container <string>` | Container to work in, by name, from sandboxes containers list. Default: the sandbox’s default container, else the one running container. With several running and no default the call fails and lists them. |
1093
+ | `--start` | Start the container first when it is not running, without asking. |
1015
1094
 
1016
1095
  MCP tool: `sandboxes_files_read`.
1017
1096
 
@@ -1027,6 +1106,8 @@ ghostdock sandboxes files upload <sandbox-id> [path] [flags]
1027
1106
  | --- | --- |
1028
1107
  | `<sandbox-id>` | Sandbox name or id. Required. |
1029
1108
  | `--org <value>` | Organization id or slug, from `ghostdock organizations list` (default: GHOSTDOCK_ORG, then `ghostdock organizations use`). |
1109
+ | `--container <value>` | Container the files land in, by name, from `ghostdock sandboxes containers list`. Default: the sandbox's default container, else the one running container. |
1110
+ | `--start` | Start the container first when it is not running, without asking. |
1030
1111
  | `--zip <value>` | Local ZIP file to upload. |
1031
1112
  | `<path>` | File or directory to upload (additional paths are allowed). |
1032
1113
 
@@ -1185,6 +1266,7 @@ ghostdock sandboxes shells close <session-id> --sandbox-id <string> [flags]
1185
1266
  | `--org <string>` | Organization id or slug, from organizations list. Ids survive renames; slugs change when the organization is renamed. Defaults to GHOSTDOCK_ORG, then `organizations use`. |
1186
1267
  | `--sandbox-id <string>` | Sandbox name (e.g. quiet-harbor) or id, from sandboxes list or sandboxes create. Names are unique within the organization. Required. |
1187
1268
  | `--container <string>` | Container to work in, by name, from sandboxes containers list. Default: the sandbox’s default container, else the one running container. With several running and no default the call fails and lists them. |
1269
+ | `--start` | Start the container first when it is not running, without asking. |
1188
1270
 
1189
1271
  MCP tool: `sandboxes_shells_close`.
1190
1272
 
@@ -1202,6 +1284,7 @@ ghostdock sandboxes shells create <sandbox-id> [flags]
1202
1284
  | `--org <string>` | Organization id or slug, from organizations list. Ids survive renames; slugs change when the organization is renamed. Defaults to GHOSTDOCK_ORG, then `organizations use`. |
1203
1285
  | `--container <string>` | Container to work in, by name, from sandboxes containers list. Default: the sandbox’s default container, else the one running container. With several running and no default the call fails and lists them. |
1204
1286
  | `--session-id <string>` | Id for the new session: a slug; user-cmd, image-cmd and compose are reserved. Omit to generate one. |
1287
+ | `--start` | Start the container first when it is not running, without asking. |
1205
1288
 
1206
1289
  MCP tool: `sandboxes_shells_create`.
1207
1290
 
@@ -1222,6 +1305,7 @@ ghostdock sandboxes ssh get <sandbox-id> [flags]
1222
1305
  | `<sandbox-id>` | Sandbox name (e.g. quiet-harbor) or id, from sandboxes list or sandboxes create. Names are unique within the organization. Required. |
1223
1306
  | `--org <string>` | Organization id or slug, from organizations list. Ids survive renames; slugs change when the organization is renamed. Defaults to GHOSTDOCK_ORG, then `organizations use`. |
1224
1307
  | `--container <string>` | Container to work in, by name, from sandboxes containers list. Default: the sandbox’s default container, else the one running container. With several running and no default the call fails and lists them. |
1308
+ | `--start` | Start the container first when it is not running, without asking. |
1225
1309
 
1226
1310
  MCP tool: `sandboxes_ssh_get`.
1227
1311
 
@@ -1270,6 +1354,40 @@ ghostdock sandboxes stop <sandbox-id> [flags]
1270
1354
 
1271
1355
  MCP tool: `sandboxes_stop`.
1272
1356
 
1357
+ ### `ghostdock sandboxes storage`
1358
+
1359
+ ### `ghostdock sandboxes storage delete`
1360
+
1361
+ Delete a named volume or a top-level sandbox folder, with everything in it. Refused while a container mounts it (in any state: a stopped one counts, so stopping does not help), mounts a folder above it (a bind of /), or a volume keeps its data in the folder: remove those first.
1362
+
1363
+ ```
1364
+ ghostdock sandboxes storage delete <sandbox-id> --kind <string> --name <string> [flags]
1365
+ ```
1366
+
1367
+ | Argument | Description |
1368
+ | --- | --- |
1369
+ | `<sandbox-id>` | Sandbox name (e.g. quiet-harbor) or id, from sandboxes list or sandboxes create. Names are unique within the organization. Required. |
1370
+ | `--org <string>` | Organization id or slug, from organizations list. Ids survive renames; slugs change when the organization is renamed. Defaults to GHOSTDOCK_ORG, then `organizations use`. |
1371
+ | `--kind <string>` | volume or folder. Required. |
1372
+ | `--name <string>` | The volume’s name, or the folder’s path in the sandbox (/srv, not /srv/app). Required. |
1373
+
1374
+ MCP tool: `sandboxes_storage_delete`.
1375
+
1376
+ ### `ghostdock sandboxes storage list`
1377
+
1378
+ List a sandbox’s volumes and sandbox folders. The sandbox folders are where bind mounts land. Each with its size and sizeState (measured; measuring: ask again in a few seconds; tooBig: too big to measure cheaply; outside: a volume bound outside the sandbox folders, not measured nor browsable) and the containers using it, plus every container’s mounts. truncated says entries were left out. A stopped sandbox lists none (it is not started for this).
1379
+
1380
+ ```
1381
+ ghostdock sandboxes storage list <sandbox-id> [flags]
1382
+ ```
1383
+
1384
+ | Argument | Description |
1385
+ | --- | --- |
1386
+ | `<sandbox-id>` | Sandbox name (e.g. quiet-harbor) or id, from sandboxes list or sandboxes create. Names are unique within the organization. Required. |
1387
+ | `--org <string>` | Organization id or slug, from organizations list. Ids survive renames; slugs change when the organization is renamed. Defaults to GHOSTDOCK_ORG, then `organizations use`. |
1388
+
1389
+ MCP tool: `sandboxes_storage_list`.
1390
+
1273
1391
  ### `ghostdock sandboxes tools`
1274
1392
 
1275
1393
  Tools of the MCP servers inside a sandbox.
package/dist/cli.md CHANGED
@@ -451,11 +451,16 @@ multiple files, or a large ZIP, use the dedicated multipart upload command inste
451
451
  ghostdock sandboxes files upload quiet-harbor ./dist
452
452
  ghostdock sandboxes files upload quiet-harbor ./index.html ./assets
453
453
  ghostdock sandboxes files upload quiet-harbor --zip ./site.zip
454
+ ghostdock sandboxes files upload quiet-harbor ./dist --container web --start
454
455
  ```
455
456
 
456
457
  Each positional path may be a file or directory. Directory contents are uploaded recursively,
457
458
  preserving their relative paths; a file is uploaded by its basename. A `sandboxes files upload` call needs
458
459
  at least one path or `--zip`. Uploading to a stopped sandbox resumes it automatically.
460
+ The files land in a container as the other `files` commands do (see
461
+ [Containers in a sandbox](#containers-in-a-sandbox)): `--container <name>`, else the default,
462
+ else the one running, and the CLI asks which one when it cannot tell. A stopped target container
463
+ is started after a "Start it?" at the keyboard, or at once with `--start`.
459
464
 
460
465
  ### `ghostdock env`
461
466
 
@@ -631,12 +636,12 @@ or read [`cli-reference.md`](cli-reference.md) for every command's full descript
631
636
  | Command | What it does |
632
637
  | --- | --- |
633
638
  | `ghostdock images add --ref <string> [flags]` | Bookmark an OCI image ref in the organization’s image catalog. |
634
- | `ghostdock images add-compose --name <string> --files <object> [flags]` | Add Compose files to the organization’s image catalog. |
639
+ | `ghostdock images add-compose --name <string> [flags]` | Add a Compose project to the organization’s image catalog. |
635
640
  | `ghostdock images add-dockerfile --name <string> --dockerfile <string> [flags]` | Add a Dockerfile to the organization’s image catalog. |
636
641
  | `ghostdock images get <image-id> [flags]` | Get one catalog image with its spec, manifest and requirements. |
637
642
  | `ghostdock images list [flags]` | List the images an organization can boot sandboxes from. |
638
643
  | `ghostdock images remove <image-id> [flags]` | Remove an image from the organization’s catalog. |
639
- | `ghostdock images update-compose <image-id> [flags]` | Replace a catalog Compose image’s files and/or name. |
644
+ | `ghostdock images update-compose <image-id> [flags]` | Replace a catalog Compose image’s files, reference, override and/or name. |
640
645
  | `ghostdock images update-dockerfile <image-id> --dockerfile <string> [flags]` | Replace a catalog Dockerfile, optionally renaming it. |
641
646
 
642
647
  #### login
@@ -715,7 +720,11 @@ or read [`cli-reference.md`](cli-reference.md) for every command's full descript
715
720
  | `ghostdock sandboxes commands logs <command-id> --sandbox-id <string> --session-id <string> [flags]` | Get a page of a shell command’s stdout and stderr. |
716
721
  | `ghostdock sandboxes commands run <sandbox-id> --command <string> [flags]` | Run a shell command in a sandbox. |
717
722
  | `ghostdock sandboxes containers list <sandbox-id> [flags]` | List the containers of a sandbox, running or not. |
723
+ | `ghostdock sandboxes containers logs <sandbox-id> <container> [flags]` | Read a container’s output, as docker logs does. |
718
724
  | `ghostdock sandboxes containers protection <sandbox-id> <container> --protected <value> [flags]` | Switch a container’s deletion protection on or off. |
725
+ | `ghostdock sandboxes containers restart <sandbox-id> <container> [flags]` | Restart a container, as docker restart does (stopping as docker stop does). |
726
+ | `ghostdock sandboxes containers start <sandbox-id> <container> [flags]` | Start a stopped container, as docker start does. |
727
+ | `ghostdock sandboxes containers stop <sandbox-id> <container> [flags]` | Stop a container, as docker stop does. |
719
728
  | `ghostdock sandboxes create --source <object> [flags]` | Create a sandbox from a snapshot, OCI image, Dockerfile or Compose files. |
720
729
  | `ghostdock sandboxes default-container <sandbox-id> [flags]` | Show, set or clear the sandbox’s default container. |
721
730
  | `ghostdock sandboxes delete <sandbox-id> [flags]` | Permanently delete a sandbox and its files. |
@@ -740,6 +749,8 @@ or read [`cli-reference.md`](cli-reference.md) for every command's full descript
740
749
  | `ghostdock sandboxes ssh revoke <sandbox-id> [flags]` | Revoke a sandbox’s active SSH access. |
741
750
  | `ghostdock sandboxes status <sandbox-id> [flags]` | Get a sandbox’s status and lifecycle deadlines without waking it. |
742
751
  | `ghostdock sandboxes stop <sandbox-id> [flags]` | Stop a running sandbox without deleting its files. |
752
+ | `ghostdock sandboxes storage delete <sandbox-id> --kind <string> --name <string> [flags]` | Delete a named volume or a top-level sandbox folder, with everything in it. |
753
+ | `ghostdock sandboxes storage list <sandbox-id> [flags]` | List a sandbox’s volumes and sandbox folders. |
743
754
  | `ghostdock sandboxes tools call <name> --sandbox-id <string> --arguments <object> [flags]` | Call a tool on an MCP server inside a sandbox. |
744
755
  | `ghostdock sandboxes tools list <sandbox-id> [flags]` | List the tools of the MCP servers a sandbox exposes. |
745
756
  | `ghostdock sandboxes update <sandbox-id> [flags]` | Change a sandbox’s display name, idle-stop or auto-delete interval. |
package/dist/index.js CHANGED
@@ -718,9 +718,33 @@ var GENERATED_TOOL_CATALOG = [
718
718
  "files"
719
719
  ],
720
720
  additionalProperties: false
721
+ },
722
+ {
723
+ type: "object",
724
+ properties: {
725
+ type: {
726
+ type: "string",
727
+ const: "compose"
728
+ },
729
+ reference: {
730
+ type: "string",
731
+ minLength: 1,
732
+ maxLength: 512,
733
+ pattern: "^oci:\\/\\/(?:[a-z0-9]+(?:[.-][a-z0-9]+)*(?::\\d{1,5})?\\/)?[a-z0-9]+(?:(?:[._]|__|-+)[a-z0-9]+)*(?:\\/[a-z0-9]+(?:(?:[._]|__|-+)[a-z0-9]+)*)*(?::[A-Za-z0-9_][A-Za-z0-9_.-]{0,127})?(?:@sha256:[a-f0-9]{64})?$"
734
+ },
735
+ override: {
736
+ type: "string",
737
+ maxLength: 262144
738
+ }
739
+ },
740
+ required: [
741
+ "type",
742
+ "reference"
743
+ ],
744
+ additionalProperties: false
721
745
  }
722
746
  ],
723
- description: 'What the sandbox boots from. Required. "image" boots an org catalog row by id ({ id } from images_list / images_get — images_add OCI bookmarks, images_add_dockerfile, and images_add_compose rows) or any OCI ref ({ ref }); "dockerfile" builds inline Dockerfile text only (ghostdock.* LABEL lines parsed for manifest; this tool has no build directory, so COPY/ADD of local files fails — use the CLI `ghostdock sandboxes build --kind dockerfile` with a directory or ZIP); "compose" boots Docker-in-Docker from the given inline files (must include docker-compose.yml/yaml or compose.yml/yaml; ghostdock.* service labels parsed for manifest). A Compose project that needs extra files is not accepted here — use `ghostdock sandboxes build --kind compose`. Image, dockerfile, and compose sources never appear in snapshots_list. Published static sites use sites_publish (CDN), not a snapshot.'
747
+ description: 'What the sandbox boots from. Required. "image" boots an org catalog row by id ({ id } from images_list / images_get — images_add OCI bookmarks, images_add_dockerfile, and images_add_compose rows) or any OCI ref ({ ref }); "dockerfile" builds inline Dockerfile text only (ghostdock.* LABEL lines parsed for manifest; this tool has no build directory, so COPY/ADD of local files fails — use the CLI `ghostdock sandboxes build --kind dockerfile` with a directory or ZIP); "compose" boots Docker-in-Docker from the given inline files (must include docker-compose.yml/yaml or compose.yml/yaml; ghostdock.* service labels parsed for manifest). A Compose project that needs extra files is not accepted here — use `ghostdock sandboxes build --kind compose`. "compose" also takes a published Compose project instead of files ({ reference: "oci://…", override? }): it runs through include:, the override merged into it. Image, dockerfile, and compose sources never appear in snapshots_list. Published static sites use sites_publish (CDN), not a snapshot.'
724
748
  },
725
749
  resources: {
726
750
  type: "object",
@@ -4454,11 +4478,16 @@ var GENERATED_TOOL_CATALOG = [
4454
4478
  "images",
4455
4479
  "add-compose"
4456
4480
  ],
4457
- description: "Add Compose files to the organization’s image catalog. Must include docker-compose.yml/yaml or compose.yml/yaml; ghostdock.* service labels become its manifest. Fails if the name is taken.",
4481
+ description: "Add a Compose project to the organization’s image catalog. Give its files, or a reference: a Compose project published as an OCI artifact (oci://…), used through include: with an optional override. ghostdock.* service labels (of the files, or of the override) become its manifest. Fails if the name is taken.",
4458
4482
  inputSchema: {
4459
4483
  $schema: "https://json-schema.org/draft/2020-12/schema",
4460
4484
  type: "object",
4461
4485
  properties: {
4486
+ organization: {
4487
+ type: "string",
4488
+ minLength: 1,
4489
+ description: "Organization id or slug, from organizations_list. Ids survive renames; slugs change when the organization is renamed."
4490
+ },
4462
4491
  name: {
4463
4492
  type: "string",
4464
4493
  minLength: 1,
@@ -4466,6 +4495,7 @@ var GENERATED_TOOL_CATALOG = [
4466
4495
  description: "Image name, unique in the organization."
4467
4496
  },
4468
4497
  files: {
4498
+ description: "Compose files by file name. Must include docker-compose.yml/yaml or compose.yml/yaml.",
4469
4499
  type: "object",
4470
4500
  propertyNames: {
4471
4501
  type: "string"
@@ -4474,19 +4504,24 @@ var GENERATED_TOOL_CATALOG = [
4474
4504
  type: "string",
4475
4505
  minLength: 1,
4476
4506
  maxLength: 512000
4477
- },
4478
- description: "Compose files by file name. Must include docker-compose.yml/yaml or compose.yml/yaml."
4507
+ }
4479
4508
  },
4480
- organization: {
4509
+ reference: {
4510
+ description: "A published Compose project instead of files, e.g. oci://docker.io/ghostdockhq/vm-compose:ubuntu-26.04.",
4481
4511
  type: "string",
4482
4512
  minLength: 1,
4483
- description: "Organization id or slug, from organizations_list. Ids survive renames; slugs change when the organization is renamed."
4513
+ maxLength: 512,
4514
+ pattern: "^oci:\\/\\/(?:[a-z0-9]+(?:[.-][a-z0-9]+)*(?::\\d{1,5})?\\/)?[a-z0-9]+(?:(?:[._]|__|-+)[a-z0-9]+)*(?:\\/[a-z0-9]+(?:(?:[._]|__|-+)[a-z0-9]+)*)*(?::[A-Za-z0-9_][A-Za-z0-9_.-]{0,127})?(?:@sha256:[a-f0-9]{64})?$"
4515
+ },
4516
+ override: {
4517
+ description: "With reference: a Compose file merged into it (services, environment, labels).",
4518
+ type: "string",
4519
+ maxLength: 262144
4484
4520
  }
4485
4521
  },
4486
4522
  required: [
4487
- "name",
4488
- "files",
4489
- "organization"
4523
+ "organization",
4524
+ "name"
4490
4525
  ],
4491
4526
  additionalProperties: false
4492
4527
  },
@@ -4509,7 +4544,7 @@ var GENERATED_TOOL_CATALOG = [
4509
4544
  cliArgs: [
4510
4545
  "imageId"
4511
4546
  ],
4512
- description: "Replace a catalog Compose image’s files and/or name. Re-parses its labels.",
4547
+ description: "Replace a catalog Compose image’s files, reference, override and/or name. Re-parses its labels.",
4513
4548
  inputSchema: {
4514
4549
  $schema: "https://json-schema.org/draft/2020-12/schema",
4515
4550
  type: "object",
@@ -4536,6 +4571,25 @@ var GENERATED_TOOL_CATALOG = [
4536
4571
  maxLength: 512000
4537
4572
  }
4538
4573
  },
4574
+ reference: {
4575
+ description: "A new reference (oci://…): the image becomes a Compose reference.",
4576
+ type: "string",
4577
+ minLength: 1,
4578
+ maxLength: 512,
4579
+ pattern: "^oci:\\/\\/(?:[a-z0-9]+(?:[.-][a-z0-9]+)*(?::\\d{1,5})?\\/)?[a-z0-9]+(?:(?:[._]|__|-+)[a-z0-9]+)*(?:\\/[a-z0-9]+(?:(?:[._]|__|-+)[a-z0-9]+)*)*(?::[A-Za-z0-9_][A-Za-z0-9_.-]{0,127})?(?:@sha256:[a-f0-9]{64})?$"
4580
+ },
4581
+ override: {
4582
+ description: "A reference’s new override; null removes it.",
4583
+ anyOf: [
4584
+ {
4585
+ type: "string",
4586
+ maxLength: 262144
4587
+ },
4588
+ {
4589
+ type: "null"
4590
+ }
4591
+ ]
4592
+ },
4539
4593
  name: {
4540
4594
  description: "New image name. Omit to keep it.",
4541
4595
  type: "string",
@@ -6159,6 +6213,320 @@ var GENERATED_TOOL_CATALOG = [
6159
6213
  additionalProperties: {}
6160
6214
  }
6161
6215
  },
6216
+ {
6217
+ id: "sandboxes_containers_start",
6218
+ name: "sandboxes_containers_start",
6219
+ cli: [
6220
+ "sandboxes",
6221
+ "containers",
6222
+ "start"
6223
+ ],
6224
+ cliArgs: [
6225
+ "sandboxId",
6226
+ "container"
6227
+ ],
6228
+ description: "Start a stopped container, as docker start does. A running container stays as it is. Starts the sandbox when needed.",
6229
+ inputSchema: {
6230
+ $schema: "https://json-schema.org/draft/2020-12/schema",
6231
+ type: "object",
6232
+ properties: {
6233
+ organization: {
6234
+ type: "string",
6235
+ minLength: 1,
6236
+ description: "Organization id or slug, from organizations_list. Ids survive renames; slugs change when the organization is renamed."
6237
+ },
6238
+ sandboxId: {
6239
+ type: "string",
6240
+ minLength: 1,
6241
+ description: "Sandbox name (e.g. quiet-harbor) or id, from sandboxes_list or sandboxes_create. Names are unique within the organization."
6242
+ },
6243
+ container: {
6244
+ type: "string",
6245
+ minLength: 1,
6246
+ maxLength: 255,
6247
+ pattern: "^[a-zA-Z0-9][a-zA-Z0-9_.-]*$",
6248
+ description: "Container, by name."
6249
+ }
6250
+ },
6251
+ required: [
6252
+ "organization",
6253
+ "sandboxId",
6254
+ "container"
6255
+ ],
6256
+ additionalProperties: false
6257
+ },
6258
+ outputSchema: {
6259
+ $schema: "https://json-schema.org/draft/2020-12/schema",
6260
+ type: "object",
6261
+ propertyNames: {
6262
+ type: "string"
6263
+ },
6264
+ additionalProperties: {}
6265
+ }
6266
+ },
6267
+ {
6268
+ id: "sandboxes_containers_stop",
6269
+ name: "sandboxes_containers_stop",
6270
+ cli: [
6271
+ "sandboxes",
6272
+ "containers",
6273
+ "stop"
6274
+ ],
6275
+ cliArgs: [
6276
+ "sandboxId",
6277
+ "container"
6278
+ ],
6279
+ description: "Stop a container, as docker stop does. It gets its own stop signal, then a kill after its own stop timeout. It stays stopped while the sandbox sleeps and wakes, as Docker keeps a stopped container stopped, unless its restart policy is always. On a stopped sandbox it does nothing (note sandbox_stopped). The container main runs the sandbox: its stop is refused (main_is_the_sandbox), pause the sandbox instead. Deletion protection does not block it. note says when it was the default container or the last one running: then SSH, commands and files have nowhere to land until one runs.",
6280
+ inputSchema: {
6281
+ $schema: "https://json-schema.org/draft/2020-12/schema",
6282
+ type: "object",
6283
+ properties: {
6284
+ organization: {
6285
+ type: "string",
6286
+ minLength: 1,
6287
+ description: "Organization id or slug, from organizations_list. Ids survive renames; slugs change when the organization is renamed."
6288
+ },
6289
+ sandboxId: {
6290
+ type: "string",
6291
+ minLength: 1,
6292
+ description: "Sandbox name (e.g. quiet-harbor) or id, from sandboxes_list or sandboxes_create. Names are unique within the organization."
6293
+ },
6294
+ container: {
6295
+ type: "string",
6296
+ minLength: 1,
6297
+ maxLength: 255,
6298
+ pattern: "^[a-zA-Z0-9][a-zA-Z0-9_.-]*$",
6299
+ description: "Container, by name."
6300
+ }
6301
+ },
6302
+ required: [
6303
+ "organization",
6304
+ "sandboxId",
6305
+ "container"
6306
+ ],
6307
+ additionalProperties: false
6308
+ },
6309
+ outputSchema: {
6310
+ $schema: "https://json-schema.org/draft/2020-12/schema",
6311
+ type: "object",
6312
+ propertyNames: {
6313
+ type: "string"
6314
+ },
6315
+ additionalProperties: {}
6316
+ }
6317
+ },
6318
+ {
6319
+ id: "sandboxes_containers_restart",
6320
+ name: "sandboxes_containers_restart",
6321
+ cli: [
6322
+ "sandboxes",
6323
+ "containers",
6324
+ "restart"
6325
+ ],
6326
+ cliArgs: [
6327
+ "sandboxId",
6328
+ "container"
6329
+ ],
6330
+ description: "Restart a container, as docker restart does (stopping as docker stop does). Starts the sandbox when needed.",
6331
+ inputSchema: {
6332
+ $schema: "https://json-schema.org/draft/2020-12/schema",
6333
+ type: "object",
6334
+ properties: {
6335
+ organization: {
6336
+ type: "string",
6337
+ minLength: 1,
6338
+ description: "Organization id or slug, from organizations_list. Ids survive renames; slugs change when the organization is renamed."
6339
+ },
6340
+ sandboxId: {
6341
+ type: "string",
6342
+ minLength: 1,
6343
+ description: "Sandbox name (e.g. quiet-harbor) or id, from sandboxes_list or sandboxes_create. Names are unique within the organization."
6344
+ },
6345
+ container: {
6346
+ type: "string",
6347
+ minLength: 1,
6348
+ maxLength: 255,
6349
+ pattern: "^[a-zA-Z0-9][a-zA-Z0-9_.-]*$",
6350
+ description: "Container, by name."
6351
+ }
6352
+ },
6353
+ required: [
6354
+ "organization",
6355
+ "sandboxId",
6356
+ "container"
6357
+ ],
6358
+ additionalProperties: false
6359
+ },
6360
+ outputSchema: {
6361
+ $schema: "https://json-schema.org/draft/2020-12/schema",
6362
+ type: "object",
6363
+ propertyNames: {
6364
+ type: "string"
6365
+ },
6366
+ additionalProperties: {}
6367
+ }
6368
+ },
6369
+ {
6370
+ id: "sandboxes_containers_logs",
6371
+ name: "sandboxes_containers_logs",
6372
+ cli: [
6373
+ "sandboxes",
6374
+ "containers",
6375
+ "logs"
6376
+ ],
6377
+ cliArgs: [
6378
+ "sandboxId",
6379
+ "container"
6380
+ ],
6381
+ description: "Read a container’s output, as docker logs does. Oldest first, each line with its stream (stdout, stderr) and time. The last tail lines (default 200), or with after only the lines after that time: pass the last line’s time to follow. truncated says lines were left out. A stopped sandbox has none (it is not started for this).",
6382
+ inputSchema: {
6383
+ $schema: "https://json-schema.org/draft/2020-12/schema",
6384
+ type: "object",
6385
+ properties: {
6386
+ organization: {
6387
+ type: "string",
6388
+ minLength: 1,
6389
+ description: "Organization id or slug, from organizations_list. Ids survive renames; slugs change when the organization is renamed."
6390
+ },
6391
+ sandboxId: {
6392
+ type: "string",
6393
+ minLength: 1,
6394
+ description: "Sandbox name (e.g. quiet-harbor) or id, from sandboxes_list or sandboxes_create. Names are unique within the organization."
6395
+ },
6396
+ container: {
6397
+ type: "string",
6398
+ minLength: 1,
6399
+ maxLength: 255,
6400
+ pattern: "^[a-zA-Z0-9][a-zA-Z0-9_.-]*$",
6401
+ description: "Container, by name."
6402
+ },
6403
+ tail: {
6404
+ description: "The last N lines (at most 5000).",
6405
+ type: "integer",
6406
+ minimum: 1,
6407
+ maximum: 5000
6408
+ },
6409
+ after: {
6410
+ description: "Only lines after this time: the time of the last line already read.",
6411
+ type: "string",
6412
+ pattern: "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d{1,9})?(Z|[+-]\\d{2}:\\d{2})$"
6413
+ }
6414
+ },
6415
+ required: [
6416
+ "organization",
6417
+ "sandboxId",
6418
+ "container"
6419
+ ],
6420
+ additionalProperties: false
6421
+ },
6422
+ outputSchema: {
6423
+ $schema: "https://json-schema.org/draft/2020-12/schema",
6424
+ type: "object",
6425
+ propertyNames: {
6426
+ type: "string"
6427
+ },
6428
+ additionalProperties: {}
6429
+ }
6430
+ },
6431
+ {
6432
+ id: "sandboxes_storage_list",
6433
+ name: "sandboxes_storage_list",
6434
+ cli: [
6435
+ "sandboxes",
6436
+ "storage",
6437
+ "list"
6438
+ ],
6439
+ cliArgs: [
6440
+ "sandboxId"
6441
+ ],
6442
+ description: "List a sandbox’s volumes and sandbox folders. The sandbox folders are where bind mounts land. Each with its size and sizeState (measured; measuring: ask again in a few seconds; tooBig: too big to measure cheaply; outside: a volume bound outside the sandbox folders, not measured nor browsable) and the containers using it, plus every container’s mounts. truncated says entries were left out. A stopped sandbox lists none (it is not started for this).",
6443
+ inputSchema: {
6444
+ $schema: "https://json-schema.org/draft/2020-12/schema",
6445
+ type: "object",
6446
+ properties: {
6447
+ organization: {
6448
+ type: "string",
6449
+ minLength: 1,
6450
+ description: "Organization id or slug, from organizations_list. Ids survive renames; slugs change when the organization is renamed."
6451
+ },
6452
+ sandboxId: {
6453
+ type: "string",
6454
+ minLength: 1,
6455
+ description: "Sandbox name (e.g. quiet-harbor) or id, from sandboxes_list or sandboxes_create. Names are unique within the organization."
6456
+ }
6457
+ },
6458
+ required: [
6459
+ "organization",
6460
+ "sandboxId"
6461
+ ],
6462
+ additionalProperties: false
6463
+ },
6464
+ outputSchema: {
6465
+ $schema: "https://json-schema.org/draft/2020-12/schema",
6466
+ type: "object",
6467
+ propertyNames: {
6468
+ type: "string"
6469
+ },
6470
+ additionalProperties: {}
6471
+ }
6472
+ },
6473
+ {
6474
+ id: "sandboxes_storage_delete",
6475
+ name: "sandboxes_storage_delete",
6476
+ cli: [
6477
+ "sandboxes",
6478
+ "storage",
6479
+ "delete"
6480
+ ],
6481
+ cliArgs: [
6482
+ "sandboxId"
6483
+ ],
6484
+ description: "Delete a named volume or a top-level sandbox folder, with everything in it. Refused while a container mounts it (in any state: a stopped one counts, so stopping does not help), mounts a folder above it (a bind of /), or a volume keeps its data in the folder: remove those first.",
6485
+ inputSchema: {
6486
+ $schema: "https://json-schema.org/draft/2020-12/schema",
6487
+ type: "object",
6488
+ properties: {
6489
+ organization: {
6490
+ type: "string",
6491
+ minLength: 1,
6492
+ description: "Organization id or slug, from organizations_list. Ids survive renames; slugs change when the organization is renamed."
6493
+ },
6494
+ sandboxId: {
6495
+ type: "string",
6496
+ minLength: 1,
6497
+ description: "Sandbox name (e.g. quiet-harbor) or id, from sandboxes_list or sandboxes_create. Names are unique within the organization."
6498
+ },
6499
+ kind: {
6500
+ type: "string",
6501
+ enum: [
6502
+ "volume",
6503
+ "folder"
6504
+ ],
6505
+ description: "volume or folder."
6506
+ },
6507
+ name: {
6508
+ type: "string",
6509
+ minLength: 1,
6510
+ description: "The volume’s name, or the folder’s path in the sandbox (/srv, not /srv/app)."
6511
+ }
6512
+ },
6513
+ required: [
6514
+ "organization",
6515
+ "sandboxId",
6516
+ "kind",
6517
+ "name"
6518
+ ],
6519
+ additionalProperties: false
6520
+ },
6521
+ outputSchema: {
6522
+ $schema: "https://json-schema.org/draft/2020-12/schema",
6523
+ type: "object",
6524
+ propertyNames: {
6525
+ type: "string"
6526
+ },
6527
+ additionalProperties: {}
6528
+ }
6529
+ },
6162
6530
  {
6163
6531
  id: "sandboxes_commands_run",
6164
6532
  name: "sandboxes_commands_run",
@@ -8808,7 +9176,7 @@ function getCliName(argv = process.argv) {
8808
9176
  return CLI_NAMES.find((name) => name === invoked) ?? "ghostdock";
8809
9177
  }
8810
9178
  // package.json
8811
- var version = "0.0.14";
9179
+ var version = "0.0.16";
8812
9180
  var package_default = {
8813
9181
  name: "ghostdock",
8814
9182
  description: "Ghostdock CLI — device login and agent tool invocation from the command line",
@@ -9415,6 +9783,15 @@ var envCommand = defineCommand({
9415
9783
 
9416
9784
  // src/container-picker.ts
9417
9785
  import { createInterface } from "node:readline/promises";
9786
+ function notRunningContainerOf(error) {
9787
+ if (!(error instanceof ApiError) || !error.data || typeof error.data !== "object")
9788
+ return null;
9789
+ const { code, params } = error.data;
9790
+ if (code !== "container_not_running")
9791
+ return null;
9792
+ const container = params && typeof params === "object" ? params : {};
9793
+ return typeof container.container === "string" && container.container ? container.container : null;
9794
+ }
9418
9795
  function containerQuestionOf(error) {
9419
9796
  if (!(error instanceof ApiError) || !error.data || typeof error.data !== "object")
9420
9797
  return null;
@@ -9460,8 +9837,21 @@ var confirmDefaultOnTerminal = async (container) => {
9460
9837
  prompt.close();
9461
9838
  }
9462
9839
  };
9840
+ var confirmStartOnTerminal = async (container) => {
9841
+ const prompt = createInterface({ input: process.stdin, output: process.stderr });
9842
+ try {
9843
+ const answer = (await prompt.question(`Container ${container} is not running. Start it? [Y/n] `)).trim();
9844
+ return answer === "" || /^y(es)?$/i.test(answer);
9845
+ } finally {
9846
+ prompt.close();
9847
+ }
9848
+ };
9463
9849
  function defaultContainerPickers() {
9464
- return process.stdin.isTTY && process.stderr.isTTY ? { choose: chooseContainerOnTerminal, confirmDefault: confirmDefaultOnTerminal } : undefined;
9850
+ return process.stdin.isTTY && process.stderr.isTTY ? {
9851
+ choose: chooseContainerOnTerminal,
9852
+ confirmDefault: confirmDefaultOnTerminal,
9853
+ confirmStart: confirmStartOnTerminal
9854
+ } : undefined;
9465
9855
  }
9466
9856
 
9467
9857
  // src/progress.ts
@@ -9524,6 +9914,130 @@ async function withProgress(message, work, options) {
9524
9914
  }
9525
9915
  }
9526
9916
 
9917
+ // src/container-flow.ts
9918
+ async function withContainerPick(call, send, pickers, notice) {
9919
+ try {
9920
+ return await send();
9921
+ } catch (err) {
9922
+ const question = containerQuestionOf(err);
9923
+ if (!pickers || !question || !call.canPick)
9924
+ throw err;
9925
+ const container = await pickers.choose(question);
9926
+ if (!container)
9927
+ throw err;
9928
+ call.setContainer(container);
9929
+ const result = await send();
9930
+ if (call.sandboxId !== undefined && await pickers.confirmDefault(container)) {
9931
+ try {
9932
+ await authorizedRequest("/api/v1/tools/sandboxes_default_container", {
9933
+ method: "POST",
9934
+ body: JSON.stringify({
9935
+ organization: call.organization,
9936
+ sandboxId: call.sandboxId,
9937
+ container
9938
+ })
9939
+ });
9940
+ notice(eventLine("done", `Default container: ${container}.`));
9941
+ } catch (err) {
9942
+ notice(eventLine("failed", `The default container was not set: ${err instanceof Error ? err.message : String(err)}`));
9943
+ }
9944
+ }
9945
+ return result;
9946
+ }
9947
+ }
9948
+ async function withContainerStart(call, send, start, pickers, notice) {
9949
+ try {
9950
+ return await send();
9951
+ } catch (err) {
9952
+ const container = notRunningContainerOf(err);
9953
+ if (!container || call.sandboxId === undefined)
9954
+ throw err;
9955
+ if (!start && !(pickers && await pickers.confirmStart(container)))
9956
+ throw err;
9957
+ await withProgress("Starting container…", () => authorizedRequest("/api/v1/tools/sandboxes_containers_start", {
9958
+ method: "POST",
9959
+ body: JSON.stringify({
9960
+ organization: call.organization,
9961
+ sandboxId: call.sandboxId,
9962
+ container
9963
+ })
9964
+ }));
9965
+ notice(eventLine("done", `Started ${container}.`));
9966
+ return await send();
9967
+ }
9968
+ }
9969
+
9970
+ // src/container-logs.ts
9971
+ var FOLLOW_INTERVAL_MS = 2000;
9972
+ function defaultLogsDeps(follow) {
9973
+ let stop = false;
9974
+ if (follow) {
9975
+ process.once("SIGINT", () => {
9976
+ stop = true;
9977
+ });
9978
+ }
9979
+ return {
9980
+ request: (input) => authorizedRequest("/api/v1/tools/sandboxes_containers_logs", {
9981
+ method: "POST",
9982
+ body: JSON.stringify(input)
9983
+ }),
9984
+ out: (line) => process.stdout.write(`${line}
9985
+ `),
9986
+ err: (line) => process.stderr.write(`${line}
9987
+ `),
9988
+ sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
9989
+ stopped: () => stop
9990
+ };
9991
+ }
9992
+ async function runContainerLogs(input, options, deps = defaultLogsDeps(options.follow)) {
9993
+ const json = isJsonOutput();
9994
+ const color = colorEnabled(process.stdout);
9995
+ const print = (line) => {
9996
+ if (json) {
9997
+ deps.out(JSON.stringify(line));
9998
+ return;
9999
+ }
10000
+ const text = options.timestamps && line.time ? `${dim(line.time, color)} ${line.text}` : line.text;
10001
+ (line.stream === "stderr" ? deps.err : deps.out)(text);
10002
+ };
10003
+ let result = await deps.request(input);
10004
+ if (!result.running) {
10005
+ deps.err("The sandbox is stopped: it has no logs to show.");
10006
+ return 1;
10007
+ }
10008
+ let last = null;
10009
+ for (;; ) {
10010
+ for (const line of result.lines) {
10011
+ print(line);
10012
+ if (line.time)
10013
+ last = line.time;
10014
+ }
10015
+ if (!options.follow) {
10016
+ if (result.truncated && json) {
10017
+ deps.out(JSON.stringify({ truncated: true }));
10018
+ } else if (result.truncated) {
10019
+ deps.err(dim("(older lines left out: use --tail for more)", colorEnabled(process.stderr)));
10020
+ }
10021
+ return 0;
10022
+ }
10023
+ if (!result.truncated)
10024
+ await deps.sleep(FOLLOW_INTERVAL_MS);
10025
+ if (deps.stopped())
10026
+ return 0;
10027
+ const { tail: _tail, ...rest } = input;
10028
+ try {
10029
+ result = await deps.request(last ? { ...rest, after: last } : input);
10030
+ } catch (error) {
10031
+ deps.err(red(error instanceof Error ? error.message : String(error), colorEnabled(process.stderr)));
10032
+ return 1;
10033
+ }
10034
+ if (!result.running) {
10035
+ deps.err("The sandbox stopped.");
10036
+ return 0;
10037
+ }
10038
+ }
10039
+ }
10040
+
9527
10041
  // src/human.ts
9528
10042
  var ID_KEYS = [
9529
10043
  "sandboxId",
@@ -9827,10 +10341,10 @@ function buildToolInput(schema, rawArgs) {
9827
10341
 
9828
10342
  // src/commands/invoke.ts
9829
10343
  async function runToolInvoke(tool, rawArgs, log = console.log, error = printError, notice = printNotice, pickers = defaultContainerPickers()) {
10344
+ const properties = tool.inputSchema.properties;
9830
10345
  let input;
9831
10346
  try {
9832
10347
  input = buildToolInput(tool.inputSchema, rawArgs);
9833
- const properties = tool.inputSchema.properties;
9834
10348
  if (properties && "organization" in properties && input.organization === undefined) {
9835
10349
  input.organization = await requireOrganizationRef(undefined, notice);
9836
10350
  }
@@ -9838,59 +10352,64 @@ async function runToolInvoke(tool, rawArgs, log = console.log, error = printErro
9838
10352
  error(err instanceof Error ? err.message : String(err));
9839
10353
  return 1;
9840
10354
  }
10355
+ if (tool.name === "sandboxes_containers_logs") {
10356
+ try {
10357
+ return await runContainerLogs(input, {
10358
+ follow: rawArgs.follow === true,
10359
+ timestamps: rawArgs.timestamps === true
10360
+ });
10361
+ } catch (err) {
10362
+ error(err instanceof Error ? err.message : `${tool.name} failed.`);
10363
+ return 1;
10364
+ }
10365
+ }
10366
+ const call = {
10367
+ organization: typeof input.organization === "string" ? input.organization : undefined,
10368
+ sandboxId: typeof input.sandboxId === "string" ? input.sandboxId : undefined,
10369
+ canPick: !!properties && "container" in properties && !input.container,
10370
+ setContainer: (container) => {
10371
+ input.container = container;
10372
+ }
10373
+ };
9841
10374
  try {
9842
- const request = () => authorizedRequest(`/api/v1/tools/${tool.name}`, {
10375
+ const post = () => authorizedRequest(`/api/v1/tools/${tool.name}`, {
9843
10376
  method: "POST",
9844
10377
  body: JSON.stringify(input)
9845
10378
  });
9846
10379
  const waitMessage = PROGRESS_MESSAGES[tool.name];
9847
- if (!waitMessage) {
9848
- const result = await withContainerPick(tool, input, request, pickers, notice);
9849
- printResult(result, () => renderResult(result, colorEnabled(process.stdout)), log);
9850
- return 0;
9851
- }
9852
- const { value: result, elapsed } = await withContainerPick(tool, input, () => withProgress(waitMessage, request), pickers, notice);
10380
+ let elapsed;
10381
+ const send = async () => {
10382
+ if (!waitMessage)
10383
+ return post();
10384
+ const timed = await withProgress(waitMessage, post);
10385
+ elapsed = timed.elapsed;
10386
+ return timed.value;
10387
+ };
10388
+ const result = await withContainerPick(call, () => withContainerStart(call, send, rawArgs.start === true, pickers, notice), pickers, notice);
9853
10389
  printResult(result, () => {
9854
10390
  const color = colorEnabled(process.stdout);
9855
10391
  const lines = renderResult(result, color);
9856
- return bootLines(result, color).length > 0 ? lines : withElapsed(lines, elapsed);
10392
+ return elapsed === undefined || bootLines(result, color).length > 0 ? lines : withElapsed(lines, elapsed);
9857
10393
  }, log);
10394
+ stopNote(result, notice);
9858
10395
  return 0;
9859
10396
  } catch (err) {
9860
10397
  error(err instanceof Error ? err.message : `${tool.name} failed.`);
9861
10398
  return 1;
9862
10399
  }
9863
10400
  }
9864
- async function withContainerPick(tool, input, send, pickers, notice) {
9865
- try {
9866
- return await send();
9867
- } catch (err) {
9868
- const properties = tool.inputSchema.properties;
9869
- const question = containerQuestionOf(err);
9870
- if (!pickers || !question || !properties || !("container" in properties) || input.container) {
9871
- throw err;
9872
- }
9873
- const container = await pickers.choose(question);
9874
- if (!container)
9875
- throw err;
9876
- input.container = container;
9877
- const result = await send();
9878
- if (typeof input.sandboxId === "string" && await pickers.confirmDefault(container)) {
9879
- try {
9880
- await authorizedRequest("/api/v1/tools/sandboxes_default_container", {
9881
- method: "POST",
9882
- body: JSON.stringify({
9883
- organization: input.organization,
9884
- sandboxId: input.sandboxId,
9885
- container
9886
- })
9887
- });
9888
- notice(eventLine("done", `Default container: ${container}.`));
9889
- } catch (err) {
9890
- notice(eventLine("failed", `The default container was not set: ${err instanceof Error ? err.message : String(err)}`));
9891
- }
9892
- }
9893
- return result;
10401
+ function stopNote(result, notice) {
10402
+ if (!result || typeof result !== "object")
10403
+ return;
10404
+ const { action, note, container } = result;
10405
+ if (action !== "stop" || typeof container !== "string")
10406
+ return;
10407
+ if (note === "default") {
10408
+ notice(`${container} is the sandbox's default container: SSH, commands and files ask which container until it runs again.`);
10409
+ } else if (note === "last_running") {
10410
+ notice("No container is running now: SSH, commands and files have none to land in until one runs.");
10411
+ } else if (note === "sandbox_stopped") {
10412
+ notice(`The sandbox is stopped, so ${container} is too: nothing was done.`);
9894
10413
  }
9895
10414
  }
9896
10415
  var PROGRESS_MESSAGES = {
@@ -9901,6 +10420,9 @@ var PROGRESS_MESSAGES = {
9901
10420
  sandboxes_resize: "Resizing sandbox…",
9902
10421
  sandboxes_resize_disk: "Resizing disk…",
9903
10422
  sandboxes_commands_run: "Running command…",
10423
+ sandboxes_containers_start: "Starting container…",
10424
+ sandboxes_containers_stop: "Stopping container…",
10425
+ sandboxes_containers_restart: "Restarting container…",
9904
10426
  snapshots_create: "Saving snapshot…",
9905
10427
  projects_redeploy: "Redeploying…",
9906
10428
  providers_verify_incus: "Checking the Incus host…"
@@ -9923,6 +10445,25 @@ function cliText(catalog, skills = BUILTIN_SKILL_NAMES) {
9923
10445
  return commands.replace(/\bthe ([a-z][a-z0-9-]*) skill\b/g, (phrase, skill) => skills.includes(skill) ? `the ${skill} skill (\`${getCliName()} skills get ${skill}\`)` : phrase);
9924
10446
  };
9925
10447
  }
10448
+ function cliOnlyArgs(tool) {
10449
+ const schema = tool.inputSchema;
10450
+ const args = {};
10451
+ if (schema.properties && "container" in schema.properties && !schema.required?.includes("container")) {
10452
+ args.start = {
10453
+ type: "boolean",
10454
+ description: "Start the container first when it is not running, without asking."
10455
+ };
10456
+ }
10457
+ if (tool.name === "sandboxes_containers_logs") {
10458
+ args.follow = {
10459
+ type: "boolean",
10460
+ alias: "f",
10461
+ description: "Keep printing new lines until Ctrl-C."
10462
+ };
10463
+ args.timestamps = { type: "boolean", description: "Put each line's time in front of it." };
10464
+ }
10465
+ return args;
10466
+ }
9926
10467
  var generatedToolCommands = new WeakSet;
9927
10468
  function createToolCommands(catalog) {
9928
10469
  const commands = {};
@@ -9933,7 +10474,7 @@ function createToolCommands(catalog) {
9933
10474
  name: tool.cli?.at(-1) ?? tool.name,
9934
10475
  description: toCliText(tool.description)
9935
10476
  },
9936
- args: buildToolArgs(tool.inputSchema, toCliText, tool.cliArgs),
10477
+ args: { ...buildToolArgs(tool.inputSchema, toCliText, tool.cliArgs), ...cliOnlyArgs(tool) },
9937
10478
  async run({ args }) {
9938
10479
  process.exitCode = await runToolInvoke(tool, args);
9939
10480
  }
@@ -12213,7 +12754,7 @@ async function collectPath3(path) {
12213
12754
  await visit(path);
12214
12755
  return files;
12215
12756
  }
12216
- async function runSandboxUpload(options, log = console.log, error = printError) {
12757
+ async function runSandboxUpload(options, log = console.log, error = printError, notice = printNotice, pickers = defaultContainerPickers()) {
12217
12758
  if (options.paths.length === 0 && !options.zip) {
12218
12759
  error("Provide at least one path or --zip.");
12219
12760
  return 1;
@@ -12229,9 +12770,26 @@ async function runSandboxUpload(options, log = console.log, error = printError)
12229
12770
  if (options.zip) {
12230
12771
  form.append("zip", new Blob([await readFile7(options.zip)]), basename5(options.zip));
12231
12772
  }
12232
- const { value: result } = await withProgress("Uploading…", () => authorizedRequest(`/api/v1/sandboxes/${encodeURIComponent(options.sandboxId)}/files?organization=${encodeURIComponent(organization)}`, { method: "POST", body: form }));
12773
+ let container = options.container;
12774
+ const call = {
12775
+ organization,
12776
+ sandboxId: options.sandboxId,
12777
+ canPick: !container,
12778
+ setContainer: (picked) => {
12779
+ container = picked;
12780
+ }
12781
+ };
12782
+ const send = async () => {
12783
+ const query = new URLSearchParams({ organization });
12784
+ if (container)
12785
+ query.set("container", container);
12786
+ const { value } = await withProgress("Uploading…", () => authorizedRequest(`/api/v1/sandboxes/${encodeURIComponent(options.sandboxId)}/files?${query}`, { method: "POST", body: form }));
12787
+ return value;
12788
+ };
12789
+ const result = await withContainerPick(call, () => withContainerStart(call, send, options.start === true, pickers, notice), pickers, notice);
12790
+ const count = `${result.written.length} file${result.written.length === 1 ? "" : "s"}`;
12233
12791
  printResult(result, () => [
12234
- eventLine("done", `Uploaded ${result.written.length} file${result.written.length === 1 ? "" : "s"} to ${options.sandboxId}.`)
12792
+ eventLine("done", result.container ? `Uploaded ${count} to ${result.container} in ${options.sandboxId}.` : `Uploaded ${count} to ${options.sandboxId}.`)
12235
12793
  ], log);
12236
12794
  return 0;
12237
12795
  } catch (err) {
@@ -12255,6 +12813,14 @@ var sandboxUploadCommand = defineCommand({
12255
12813
  required: true
12256
12814
  },
12257
12815
  org: orgArg,
12816
+ container: {
12817
+ type: "string",
12818
+ description: `Container the files land in, by name, from \`${getCliName()} sandboxes containers list\`. Default: the sandbox's default container, else the one running container.`
12819
+ },
12820
+ start: {
12821
+ type: "boolean",
12822
+ description: "Start the container first when it is not running, without asking."
12823
+ },
12258
12824
  zip: {
12259
12825
  type: "string",
12260
12826
  description: "Local ZIP file to upload."
@@ -12270,7 +12836,9 @@ var sandboxUploadCommand = defineCommand({
12270
12836
  sandboxId: args["sandbox-id"],
12271
12837
  org: args.org,
12272
12838
  paths: args._.slice(1),
12273
- zip: args.zip
12839
+ zip: args.zip,
12840
+ container: args.container,
12841
+ start: args.start
12274
12842
  });
12275
12843
  }
12276
12844
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ghostdock",
3
3
  "description": "Ghostdock CLI — device login and agent tool invocation from the command line",
4
- "version": "0.0.14",
4
+ "version": "0.0.16",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "ghostdock": "dist/index.js",