ghostdock 0.0.13 → 0.0.14

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.
@@ -749,6 +749,7 @@ ghostdock sandboxes commands get <command-id> --sandbox-id <string> --session-id
749
749
  | `<command-id>` | Command id returned by sandboxes commands run. Required. |
750
750
  | `--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`. |
751
751
  | `--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
+ | `--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. |
752
753
  | `--session-id <string>` | Session id returned by sandboxes commands run together with the commandId. Required. |
753
754
 
754
755
  MCP tool: `sandboxes_commands_get`.
@@ -766,6 +767,7 @@ ghostdock sandboxes commands kill <command-id> --sandbox-id <string> --session-i
766
767
  | `<command-id>` | Command id returned by sandboxes commands run. Required. |
767
768
  | `--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`. |
768
769
  | `--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
+ | `--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. |
769
771
  | `--session-id <string>` | Session id returned by sandboxes commands run together with the commandId. Required. |
770
772
 
771
773
  MCP tool: `sandboxes_commands_kill`.
@@ -783,6 +785,7 @@ ghostdock sandboxes commands logs <command-id> --sandbox-id <string> --session-i
783
785
  | `<command-id>` | Command id returned by sandboxes commands run. Required. |
784
786
  | `--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`. |
785
787
  | `--sandbox-id <string>` | Sandbox name (e.g. quiet-harbor) or id, from sandboxes list or sandboxes create. Names are unique within the organization. Required. |
788
+ | `--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. |
786
789
  | `--session-id <string>` | Session id returned by sandboxes commands run together with the commandId. Required. |
787
790
  | `--offset <integer>` | Character offset to continue reading from. Default 0. |
788
791
 
@@ -800,6 +803,7 @@ ghostdock sandboxes commands run <sandbox-id> --command <string> [flags]
800
803
  | --- | --- |
801
804
  | `<sandbox-id>` | Sandbox name (e.g. quiet-harbor) or id, from sandboxes list or sandboxes create. Names are unique within the organization. Required. |
802
805
  | `--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`. |
806
+ | `--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. |
803
807
  | `--command <string>` | Shell command to run. Never include secret values: commands are logged. Required. |
804
808
  | `--session-id <string>` | Session from sandboxes shells create, to keep cwd and environment from earlier commands. Omit for a one-off session. |
805
809
  | `--cwd <string>` | Directory to run the command in. |
@@ -807,6 +811,42 @@ ghostdock sandboxes commands run <sandbox-id> --command <string> [flags]
807
811
 
808
812
  MCP tool: `sandboxes_commands_run`.
809
813
 
814
+ ### `ghostdock sandboxes containers`
815
+
816
+ Containers of a sandbox’s Docker engine.
817
+
818
+ ### `ghostdock sandboxes containers list`
819
+
820
+ List the containers of a sandbox, running or not. Each with state, health, restart policy, ports, Compose project and service, deletion protection, ghostdock.* labels, and which one is the default. A stopped sandbox lists none (it is not started for this).
821
+
822
+ ```
823
+ ghostdock sandboxes containers list <sandbox-id> [flags]
824
+ ```
825
+
826
+ | Argument | Description |
827
+ | --- | --- |
828
+ | `<sandbox-id>` | Sandbox name (e.g. quiet-harbor) or id, from sandboxes list or sandboxes create. Names are unique within the organization. Required. |
829
+ | `--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`. |
830
+
831
+ MCP tool: `sandboxes_containers_list`.
832
+
833
+ ### `ghostdock sandboxes containers protection`
834
+
835
+ 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.
836
+
837
+ ```
838
+ ghostdock sandboxes containers protection <sandbox-id> <container> --protected <value> [flags]
839
+ ```
840
+
841
+ | Argument | Description |
842
+ | --- | --- |
843
+ | `<sandbox-id>` | Sandbox name (e.g. quiet-harbor) or id, from sandboxes list or sandboxes create. Names are unique within the organization. Required. |
844
+ | `<container>` | Container, by name. Required. |
845
+ | `--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`. |
846
+ | `--protected` | true: protect it; false: allow removing it. Required. |
847
+
848
+ MCP tool: `sandboxes_containers_protection`.
849
+
810
850
  ### `ghostdock sandboxes create`
811
851
 
812
852
  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.
@@ -831,6 +871,23 @@ ghostdock sandboxes create --source <object> [flags]
831
871
 
832
872
  MCP tool: `sandboxes_create`.
833
873
 
874
+ ### `ghostdock sandboxes default-container`
875
+
876
+ Show, set or clear the sandbox’s default container. SSH, the terminal, files, commands and MCP land there when no container is named. Without container or clear it only shows the setting.
877
+
878
+ ```
879
+ ghostdock sandboxes default-container <sandbox-id> [flags]
880
+ ```
881
+
882
+ | Argument | Description |
883
+ | --- | --- |
884
+ | `<sandbox-id>` | Sandbox name (e.g. quiet-harbor) or id, from sandboxes list or sandboxes create. Names are unique within the organization. Required. |
885
+ | `--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
+ | `--container <string>` | Container to make the default, by name. The running sandbox must have it. |
887
+ | `--clear` | Clear the default. |
888
+
889
+ MCP tool: `sandboxes_default_container`.
890
+
834
891
  ### `ghostdock sandboxes delete`
835
892
 
836
893
  Permanently delete a sandbox and its files.
@@ -936,6 +993,7 @@ ghostdock sandboxes files list <sandbox-id> [flags]
936
993
  | --- | --- |
937
994
  | `<sandbox-id>` | Sandbox name (e.g. quiet-harbor) or id, from sandboxes list or sandboxes create. Names are unique within the organization. Required. |
938
995
  | `--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
+ | `--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. |
939
997
  | `--path <string>` | Directory to list. Absolute or relative to /. Defaults to /workspace. |
940
998
 
941
999
  MCP tool: `sandboxes_files_list`.
@@ -953,6 +1011,7 @@ ghostdock sandboxes files read <path> --sandbox-id <string> [flags]
953
1011
  | `<path>` | File path. Absolute or relative to / (for example /workspace/src/App.vue). Required. |
954
1012
  | `--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`. |
955
1013
  | `--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
+ | `--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. |
956
1015
 
957
1016
  MCP tool: `sandboxes_files_read`.
958
1017
 
@@ -1125,6 +1184,7 @@ ghostdock sandboxes shells close <session-id> --sandbox-id <string> [flags]
1125
1184
  | `<session-id>` | Session id from sandboxes shells create. Required. |
1126
1185
  | `--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`. |
1127
1186
  | `--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
+ | `--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. |
1128
1188
 
1129
1189
  MCP tool: `sandboxes_shells_close`.
1130
1190
 
@@ -1140,6 +1200,7 @@ ghostdock sandboxes shells create <sandbox-id> [flags]
1140
1200
  | --- | --- |
1141
1201
  | `<sandbox-id>` | Sandbox name (e.g. quiet-harbor) or id, from sandboxes list or sandboxes create. Names are unique within the organization. Required. |
1142
1202
  | `--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
+ | `--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. |
1143
1204
  | `--session-id <string>` | Id for the new session: a slug; user-cmd, image-cmd and compose are reserved. Omit to generate one. |
1144
1205
 
1145
1206
  MCP tool: `sandboxes_shells_create`.
@@ -1160,6 +1221,7 @@ ghostdock sandboxes ssh get <sandbox-id> [flags]
1160
1221
  | --- | --- |
1161
1222
  | `<sandbox-id>` | Sandbox name (e.g. quiet-harbor) or id, from sandboxes list or sandboxes create. Names are unique within the organization. Required. |
1162
1223
  | `--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
+ | `--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. |
1163
1225
 
1164
1226
  MCP tool: `sandboxes_ssh_get`.
1165
1227
 
package/dist/cli.md CHANGED
@@ -314,6 +314,25 @@ ghostdock sandboxes create --org org_123 --display-name "Marketing site"
314
314
  ghostdock sandboxes urls create quiet-harbor --port 8080 --auto-wake true --public true
315
315
  ```
316
316
 
317
+ #### Containers in a sandbox
318
+
319
+ On Ghostdock Cloud a sandbox runs one Docker engine; everything in it is an ordinary container.
320
+ `ssh get`, `files`, `commands` and `shells` land in one of them: `--container <name>`, else the
321
+ sandbox's default container, else the one running container. With several running and no default,
322
+ the CLI asks which one at the keyboard and offers to make it the default; without a terminal it
323
+ fails and lists the names. A command runs as `docker exec` does there: as the container's
324
+ `ghostdock.login.user`, else the image's `USER`, else root.
325
+
326
+ ```bash
327
+ ghostdock sandboxes containers list quiet-harbor
328
+ ghostdock sandboxes default-container quiet-harbor --container web # --clear true removes it
329
+ ghostdock sandboxes commands run quiet-harbor --container db --command 'pg_isready'
330
+ ghostdock sandboxes containers protection quiet-harbor workstation --protected false
331
+ ```
332
+
333
+ A shell session lives in its container: pass the same `--container` to every `shells` and
334
+ `commands` call on it.
335
+
317
336
  #### Environment in a sandbox
318
337
 
319
338
  A sandbox exports **nothing** by default (unless you boot from an organization snapshot or catalog
@@ -695,7 +714,10 @@ or read [`cli-reference.md`](cli-reference.md) for every command's full descript
695
714
  | `ghostdock sandboxes commands kill <command-id> --sandbox-id <string> --session-id <string> [flags]` | Interrupt a running shell command. |
696
715
  | `ghostdock sandboxes commands logs <command-id> --sandbox-id <string> --session-id <string> [flags]` | Get a page of a shell command’s stdout and stderr. |
697
716
  | `ghostdock sandboxes commands run <sandbox-id> --command <string> [flags]` | Run a shell command in a sandbox. |
717
+ | `ghostdock sandboxes containers list <sandbox-id> [flags]` | List the containers of a sandbox, running or not. |
718
+ | `ghostdock sandboxes containers protection <sandbox-id> <container> --protected <value> [flags]` | Switch a container’s deletion protection on or off. |
698
719
  | `ghostdock sandboxes create --source <object> [flags]` | Create a sandbox from a snapshot, OCI image, Dockerfile or Compose files. |
720
+ | `ghostdock sandboxes default-container <sandbox-id> [flags]` | Show, set or clear the sandbox’s default container. |
699
721
  | `ghostdock sandboxes delete <sandbox-id> [flags]` | Permanently delete a sandbox and its files. |
700
722
  | `ghostdock sandboxes env delete <name> --sandbox-id <string> [flags]` | Delete one of a sandbox’s own variables or secrets. |
701
723
  | `ghostdock sandboxes env get <sandbox-id> [flags]` | Get a sandbox’s environment: selections, bindings and local entries. |
package/dist/index.js CHANGED
@@ -4625,6 +4625,13 @@ var GENERATED_TOOL_CATALOG = [
4625
4625
  minLength: 1,
4626
4626
  description: "Sandbox name (e.g. quiet-harbor) or id, from sandboxes_list or sandboxes_create. Names are unique within the organization."
4627
4627
  },
4628
+ container: {
4629
+ description: "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.",
4630
+ type: "string",
4631
+ minLength: 1,
4632
+ maxLength: 255,
4633
+ pattern: "^[a-zA-Z0-9][a-zA-Z0-9_.-]*$"
4634
+ },
4628
4635
  files: {
4629
4636
  description: "Small inline files: { path, content } for utf8 text, or { path, contentBase64 } for binary.",
4630
4637
  type: "array",
@@ -4701,6 +4708,13 @@ var GENERATED_TOOL_CATALOG = [
4701
4708
  minLength: 1,
4702
4709
  description: "Sandbox name (e.g. quiet-harbor) or id, from sandboxes_list or sandboxes_create. Names are unique within the organization."
4703
4710
  },
4711
+ container: {
4712
+ description: "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.",
4713
+ type: "string",
4714
+ minLength: 1,
4715
+ maxLength: 255,
4716
+ pattern: "^[a-zA-Z0-9][a-zA-Z0-9_.-]*$"
4717
+ },
4704
4718
  path: {
4705
4719
  description: "Directory to list. Absolute or relative to /. Defaults to /workspace.",
4706
4720
  type: "string",
@@ -4780,6 +4794,13 @@ var GENERATED_TOOL_CATALOG = [
4780
4794
  minLength: 1,
4781
4795
  description: "Sandbox name (e.g. quiet-harbor) or id, from sandboxes_list or sandboxes_create. Names are unique within the organization."
4782
4796
  },
4797
+ container: {
4798
+ description: "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.",
4799
+ type: "string",
4800
+ minLength: 1,
4801
+ maxLength: 255,
4802
+ pattern: "^[a-zA-Z0-9][a-zA-Z0-9_.-]*$"
4803
+ },
4783
4804
  path: {
4784
4805
  type: "string",
4785
4806
  minLength: 1,
@@ -5922,6 +5943,13 @@ var GENERATED_TOOL_CATALOG = [
5922
5943
  type: "string",
5923
5944
  minLength: 1,
5924
5945
  description: "Sandbox name (e.g. quiet-harbor) or id, from sandboxes_list or sandboxes_create. Names are unique within the organization."
5946
+ },
5947
+ container: {
5948
+ description: "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.",
5949
+ type: "string",
5950
+ minLength: 1,
5951
+ maxLength: 255,
5952
+ pattern: "^[a-zA-Z0-9][a-zA-Z0-9_.-]*$"
5925
5953
  }
5926
5954
  },
5927
5955
  required: [
@@ -5981,6 +6009,156 @@ var GENERATED_TOOL_CATALOG = [
5981
6009
  additionalProperties: {}
5982
6010
  }
5983
6011
  },
6012
+ {
6013
+ id: "sandboxes_containers_list",
6014
+ name: "sandboxes_containers_list",
6015
+ cli: [
6016
+ "sandboxes",
6017
+ "containers",
6018
+ "list"
6019
+ ],
6020
+ cliArgs: [
6021
+ "sandboxId"
6022
+ ],
6023
+ description: "List the containers of a sandbox, running or not. Each with state, health, restart policy, ports, Compose project and service, deletion protection, ghostdock.* labels, and which one is the default. A stopped sandbox lists none (it is not started for this).",
6024
+ inputSchema: {
6025
+ $schema: "https://json-schema.org/draft/2020-12/schema",
6026
+ type: "object",
6027
+ properties: {
6028
+ organization: {
6029
+ type: "string",
6030
+ minLength: 1,
6031
+ description: "Organization id or slug, from organizations_list. Ids survive renames; slugs change when the organization is renamed."
6032
+ },
6033
+ sandboxId: {
6034
+ type: "string",
6035
+ minLength: 1,
6036
+ description: "Sandbox name (e.g. quiet-harbor) or id, from sandboxes_list or sandboxes_create. Names are unique within the organization."
6037
+ }
6038
+ },
6039
+ required: [
6040
+ "organization",
6041
+ "sandboxId"
6042
+ ],
6043
+ additionalProperties: false
6044
+ },
6045
+ outputSchema: {
6046
+ $schema: "https://json-schema.org/draft/2020-12/schema",
6047
+ type: "object",
6048
+ propertyNames: {
6049
+ type: "string"
6050
+ },
6051
+ additionalProperties: {}
6052
+ }
6053
+ },
6054
+ {
6055
+ id: "sandboxes_default_container",
6056
+ name: "sandboxes_default_container",
6057
+ cli: [
6058
+ "sandboxes",
6059
+ "default-container"
6060
+ ],
6061
+ cliArgs: [
6062
+ "sandboxId"
6063
+ ],
6064
+ description: "Show, set or clear the sandbox’s default container. SSH, the terminal, files, commands and MCP land there when no container is named. Without container or clear it only shows the setting.",
6065
+ inputSchema: {
6066
+ $schema: "https://json-schema.org/draft/2020-12/schema",
6067
+ type: "object",
6068
+ properties: {
6069
+ organization: {
6070
+ type: "string",
6071
+ minLength: 1,
6072
+ description: "Organization id or slug, from organizations_list. Ids survive renames; slugs change when the organization is renamed."
6073
+ },
6074
+ sandboxId: {
6075
+ type: "string",
6076
+ minLength: 1,
6077
+ description: "Sandbox name (e.g. quiet-harbor) or id, from sandboxes_list or sandboxes_create. Names are unique within the organization."
6078
+ },
6079
+ container: {
6080
+ description: "Container to make the default, by name. The running sandbox must have it.",
6081
+ type: "string",
6082
+ minLength: 1,
6083
+ maxLength: 255,
6084
+ pattern: "^[a-zA-Z0-9][a-zA-Z0-9_.-]*$"
6085
+ },
6086
+ clear: {
6087
+ description: "Clear the default.",
6088
+ type: "boolean"
6089
+ }
6090
+ },
6091
+ required: [
6092
+ "organization",
6093
+ "sandboxId"
6094
+ ],
6095
+ additionalProperties: false
6096
+ },
6097
+ outputSchema: {
6098
+ $schema: "https://json-schema.org/draft/2020-12/schema",
6099
+ type: "object",
6100
+ propertyNames: {
6101
+ type: "string"
6102
+ },
6103
+ additionalProperties: {}
6104
+ }
6105
+ },
6106
+ {
6107
+ id: "sandboxes_containers_protection",
6108
+ name: "sandboxes_containers_protection",
6109
+ cli: [
6110
+ "sandboxes",
6111
+ "containers",
6112
+ "protection"
6113
+ ],
6114
+ cliArgs: [
6115
+ "sandboxId",
6116
+ "container"
6117
+ ],
6118
+ description: "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.",
6119
+ inputSchema: {
6120
+ $schema: "https://json-schema.org/draft/2020-12/schema",
6121
+ type: "object",
6122
+ properties: {
6123
+ organization: {
6124
+ type: "string",
6125
+ minLength: 1,
6126
+ description: "Organization id or slug, from organizations_list. Ids survive renames; slugs change when the organization is renamed."
6127
+ },
6128
+ sandboxId: {
6129
+ type: "string",
6130
+ minLength: 1,
6131
+ description: "Sandbox name (e.g. quiet-harbor) or id, from sandboxes_list or sandboxes_create. Names are unique within the organization."
6132
+ },
6133
+ container: {
6134
+ type: "string",
6135
+ minLength: 1,
6136
+ maxLength: 255,
6137
+ pattern: "^[a-zA-Z0-9][a-zA-Z0-9_.-]*$",
6138
+ description: "Container, by name."
6139
+ },
6140
+ protected: {
6141
+ type: "boolean",
6142
+ description: "true: protect it; false: allow removing it."
6143
+ }
6144
+ },
6145
+ required: [
6146
+ "organization",
6147
+ "sandboxId",
6148
+ "container",
6149
+ "protected"
6150
+ ],
6151
+ additionalProperties: false
6152
+ },
6153
+ outputSchema: {
6154
+ $schema: "https://json-schema.org/draft/2020-12/schema",
6155
+ type: "object",
6156
+ propertyNames: {
6157
+ type: "string"
6158
+ },
6159
+ additionalProperties: {}
6160
+ }
6161
+ },
5984
6162
  {
5985
6163
  id: "sandboxes_commands_run",
5986
6164
  name: "sandboxes_commands_run",
@@ -6007,6 +6185,13 @@ var GENERATED_TOOL_CATALOG = [
6007
6185
  minLength: 1,
6008
6186
  description: "Sandbox name (e.g. quiet-harbor) or id, from sandboxes_list or sandboxes_create. Names are unique within the organization."
6009
6187
  },
6188
+ container: {
6189
+ description: "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.",
6190
+ type: "string",
6191
+ minLength: 1,
6192
+ maxLength: 255,
6193
+ pattern: "^[a-zA-Z0-9][a-zA-Z0-9_.-]*$"
6194
+ },
6010
6195
  command: {
6011
6196
  type: "string",
6012
6197
  minLength: 1,
@@ -6069,6 +6254,13 @@ var GENERATED_TOOL_CATALOG = [
6069
6254
  minLength: 1,
6070
6255
  description: "Sandbox name (e.g. quiet-harbor) or id, from sandboxes_list or sandboxes_create. Names are unique within the organization."
6071
6256
  },
6257
+ container: {
6258
+ description: "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.",
6259
+ type: "string",
6260
+ minLength: 1,
6261
+ maxLength: 255,
6262
+ pattern: "^[a-zA-Z0-9][a-zA-Z0-9_.-]*$"
6263
+ },
6072
6264
  sessionId: {
6073
6265
  type: "string",
6074
6266
  minLength: 1,
@@ -6123,6 +6315,13 @@ var GENERATED_TOOL_CATALOG = [
6123
6315
  minLength: 1,
6124
6316
  description: "Sandbox name (e.g. quiet-harbor) or id, from sandboxes_list or sandboxes_create. Names are unique within the organization."
6125
6317
  },
6318
+ container: {
6319
+ description: "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.",
6320
+ type: "string",
6321
+ minLength: 1,
6322
+ maxLength: 255,
6323
+ pattern: "^[a-zA-Z0-9][a-zA-Z0-9_.-]*$"
6324
+ },
6126
6325
  sessionId: {
6127
6326
  type: "string",
6128
6327
  minLength: 1,
@@ -6183,6 +6382,13 @@ var GENERATED_TOOL_CATALOG = [
6183
6382
  minLength: 1,
6184
6383
  description: "Sandbox name (e.g. quiet-harbor) or id, from sandboxes_list or sandboxes_create. Names are unique within the organization."
6185
6384
  },
6385
+ container: {
6386
+ description: "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.",
6387
+ type: "string",
6388
+ minLength: 1,
6389
+ maxLength: 255,
6390
+ pattern: "^[a-zA-Z0-9][a-zA-Z0-9_.-]*$"
6391
+ },
6186
6392
  sessionId: {
6187
6393
  type: "string",
6188
6394
  minLength: 1,
@@ -6237,6 +6443,13 @@ var GENERATED_TOOL_CATALOG = [
6237
6443
  minLength: 1,
6238
6444
  description: "Sandbox name (e.g. quiet-harbor) or id, from sandboxes_list or sandboxes_create. Names are unique within the organization."
6239
6445
  },
6446
+ container: {
6447
+ description: "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.",
6448
+ type: "string",
6449
+ minLength: 1,
6450
+ maxLength: 255,
6451
+ pattern: "^[a-zA-Z0-9][a-zA-Z0-9_.-]*$"
6452
+ },
6240
6453
  sessionId: {
6241
6454
  description: "Id for the new session: a slug; user-cmd, image-cmd and compose are reserved. Omit to generate one.",
6242
6455
  type: "string",
@@ -6284,6 +6497,13 @@ var GENERATED_TOOL_CATALOG = [
6284
6497
  minLength: 1,
6285
6498
  description: "Sandbox name (e.g. quiet-harbor) or id, from sandboxes_list or sandboxes_create. Names are unique within the organization."
6286
6499
  },
6500
+ container: {
6501
+ description: "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.",
6502
+ type: "string",
6503
+ minLength: 1,
6504
+ maxLength: 255,
6505
+ pattern: "^[a-zA-Z0-9][a-zA-Z0-9_.-]*$"
6506
+ },
6287
6507
  sessionId: {
6288
6508
  type: "string",
6289
6509
  minLength: 1,
@@ -8588,7 +8808,7 @@ function getCliName(argv = process.argv) {
8588
8808
  return CLI_NAMES.find((name) => name === invoked) ?? "ghostdock";
8589
8809
  }
8590
8810
  // package.json
8591
- var version = "0.0.13";
8811
+ var version = "0.0.14";
8592
8812
  var package_default = {
8593
8813
  name: "ghostdock",
8594
8814
  description: "Ghostdock CLI — device login and agent tool invocation from the command line",
@@ -9193,6 +9413,57 @@ var envCommand = defineCommand({
9193
9413
  }
9194
9414
  });
9195
9415
 
9416
+ // src/container-picker.ts
9417
+ import { createInterface } from "node:readline/promises";
9418
+ function containerQuestionOf(error) {
9419
+ if (!(error instanceof ApiError) || !error.data || typeof error.data !== "object")
9420
+ return null;
9421
+ const { code, params } = error.data;
9422
+ if (code !== "container_choice_required" && code !== "container_default_missing")
9423
+ return null;
9424
+ const record = params && typeof params === "object" ? params : {};
9425
+ const running = typeof record.containers === "string" ? record.containers.split(",").map((name) => name.trim()).filter(Boolean) : [];
9426
+ if (running.length === 0)
9427
+ return null;
9428
+ return {
9429
+ running,
9430
+ ...code === "container_default_missing" && typeof record.container === "string" ? { missingDefault: record.container } : {}
9431
+ };
9432
+ }
9433
+ var say = (line) => process.stderr.write(`${line}
9434
+ `);
9435
+ var chooseContainerOnTerminal = async ({ running, missingDefault }) => {
9436
+ if (missingDefault) {
9437
+ say(`
9438
+ The default container ${missingDefault} is gone.`);
9439
+ }
9440
+ say(missingDefault ? "Which container?" : `
9441
+ Several containers are running. Which one?`);
9442
+ running.forEach((name, index) => {
9443
+ say(` ${index + 1}. ${name}`);
9444
+ });
9445
+ const prompt = createInterface({ input: process.stdin, output: process.stderr });
9446
+ try {
9447
+ const answer = (await prompt.question("Number (Enter to cancel): ")).trim();
9448
+ const index = Number.parseInt(answer, 10) - 1;
9449
+ return Number.isInteger(index) && index >= 0 && index < running.length ? running[index] ?? null : null;
9450
+ } finally {
9451
+ prompt.close();
9452
+ }
9453
+ };
9454
+ var confirmDefaultOnTerminal = async (container) => {
9455
+ const prompt = createInterface({ input: process.stdin, output: process.stderr });
9456
+ try {
9457
+ const answer = (await prompt.question(`Make ${container} the default? [y/N] `)).trim();
9458
+ return /^y(es)?$/i.test(answer);
9459
+ } finally {
9460
+ prompt.close();
9461
+ }
9462
+ };
9463
+ function defaultContainerPickers() {
9464
+ return process.stdin.isTTY && process.stderr.isTTY ? { choose: chooseContainerOnTerminal, confirmDefault: confirmDefaultOnTerminal } : undefined;
9465
+ }
9466
+
9196
9467
  // src/progress.ts
9197
9468
  var PROGRESS_FRAMES = ["▂▄▆", "▄▆█", "▆█▆", "█▆▄", "▆▄▂", "▄▂▄"];
9198
9469
  var FRAME_MS = 120;
@@ -9293,10 +9564,10 @@ function symbolForStatus(status) {
9293
9564
  return "failed";
9294
9565
  if (/^(running|active|ready|deployed|published|online|verified|ok)$/.test(s))
9295
9566
  return "ready";
9296
- if (/^(stopped|deleted|unpublished|vanished|archived|revoked|closed|killed)$/.test(s)) {
9567
+ if (/^(stopped|deleted|unpublished|vanished|archived|revoked|closed|killed|exited|dead|paused)$/.test(s)) {
9297
9568
  return "stopped";
9298
9569
  }
9299
- if (/(pending|starting|spawning|stopping|building|progress|awaiting|queued|creating)/.test(s)) {
9570
+ if (/(pending|starting|spawning|stopping|building|progress|awaiting|queued|creating|restarting|removing)/.test(s)) {
9300
9571
  return "progress";
9301
9572
  }
9302
9573
  return "item";
@@ -9311,6 +9582,9 @@ function lineKeys(item) {
9311
9582
  const idKeys = "id" in item ? ID_KEYS.filter((key) => key !== "sandboxId") : ID_KEYS;
9312
9583
  return { id: firstKey(item, idKeys), label: firstKey(item, LABEL_KEYS) };
9313
9584
  }
9585
+ function shortId(id) {
9586
+ return /^[0-9a-f]{64}$/.test(id) ? id.slice(0, 12) : id;
9587
+ }
9314
9588
  function itemLine(item, color) {
9315
9589
  const keys = lineKeys(item);
9316
9590
  const id = keys.id ? item[keys.id] : undefined;
@@ -9319,7 +9593,7 @@ function itemLine(item, color) {
9319
9593
  const url = firstString(item, URL_KEYS);
9320
9594
  const parts = [label ?? id ?? "(unnamed)"];
9321
9595
  if (label && id && id !== label)
9322
- parts.push(dim(id, color));
9596
+ parts.push(dim(shortId(id), color));
9323
9597
  if (status)
9324
9598
  parts.push(status);
9325
9599
  if (url)
@@ -9552,7 +9826,7 @@ function buildToolInput(schema, rawArgs) {
9552
9826
  }
9553
9827
 
9554
9828
  // src/commands/invoke.ts
9555
- async function runToolInvoke(tool, rawArgs, log = console.log, error = printError, notice = printNotice) {
9829
+ async function runToolInvoke(tool, rawArgs, log = console.log, error = printError, notice = printNotice, pickers = defaultContainerPickers()) {
9556
9830
  let input;
9557
9831
  try {
9558
9832
  input = buildToolInput(tool.inputSchema, rawArgs);
@@ -9571,11 +9845,11 @@ async function runToolInvoke(tool, rawArgs, log = console.log, error = printErro
9571
9845
  });
9572
9846
  const waitMessage = PROGRESS_MESSAGES[tool.name];
9573
9847
  if (!waitMessage) {
9574
- const result = await request();
9848
+ const result = await withContainerPick(tool, input, request, pickers, notice);
9575
9849
  printResult(result, () => renderResult(result, colorEnabled(process.stdout)), log);
9576
9850
  return 0;
9577
9851
  }
9578
- const { value: result, elapsed } = await withProgress(waitMessage, request);
9852
+ const { value: result, elapsed } = await withContainerPick(tool, input, () => withProgress(waitMessage, request), pickers, notice);
9579
9853
  printResult(result, () => {
9580
9854
  const color = colorEnabled(process.stdout);
9581
9855
  const lines = renderResult(result, color);
@@ -9587,6 +9861,38 @@ async function runToolInvoke(tool, rawArgs, log = console.log, error = printErro
9587
9861
  return 1;
9588
9862
  }
9589
9863
  }
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;
9894
+ }
9895
+ }
9590
9896
  var PROGRESS_MESSAGES = {
9591
9897
  sandboxes_create: "Creating sandbox…",
9592
9898
  sandboxes_wake: "Waking sandbox…",
@@ -9642,7 +9948,7 @@ function createToolCommands(catalog) {
9642
9948
  import { hostname } from "node:os";
9643
9949
 
9644
9950
  // src/commands/login-organization.ts
9645
- import { createInterface } from "node:readline/promises";
9951
+ import { createInterface as createInterface2 } from "node:readline/promises";
9646
9952
  function label(organization) {
9647
9953
  return `${organization.name} (${organization.slug ?? organization.id})`;
9648
9954
  }
@@ -9652,7 +9958,7 @@ Which organization should commands use by default?`);
9652
9958
  organizations.forEach((organization, index) => {
9653
9959
  console.log(` ${index + 1}. ${label(organization)}`);
9654
9960
  });
9655
- const prompt = createInterface({ input: process.stdin, output: process.stdout });
9961
+ const prompt = createInterface2({ input: process.stdin, output: process.stdout });
9656
9962
  try {
9657
9963
  const answer = (await prompt.question(`Number (Enter to skip): `)).trim();
9658
9964
  const index = Number.parseInt(answer, 10) - 1;
@@ -9927,14 +10233,21 @@ function renderConfig(input) {
9927
10233
  if (!isDnsZone(zone))
9928
10234
  throw new Error(`The server sent a zone that is not a DNS name: ${zone}`);
9929
10235
  }
10236
+ if (!device.address || !isIPv6Address(device.address)) {
10237
+ throw new Error(`The server sent no IPv6 address for this device: ${device.address}`);
10238
+ }
9930
10239
  if (!isIPv6Address(network.gatewayAddress)) {
9931
10240
  throw new Error(`The server sent a gateway that is not an IPv6 address: ${network.gatewayAddress}`);
9932
10241
  }
10242
+ const ipv4 = device.address4 && network.ingress4 ? { address: device.address4, ingress: network.ingress4 } : null;
10243
+ if (ipv4 && (!isIPv4Address(ipv4.address) || !isIPv4Address(ipv4.ingress))) {
10244
+ throw new Error(`The server sent an IPv4 address that is not one: ${ipv4.address}, ${ipv4.ingress}`);
10245
+ }
9933
10246
  const lines = [
9934
10247
  "# Written by `ghostdock network connect`. Run it again rather than editing this file.",
9935
10248
  "[Interface]",
9936
10249
  `PrivateKey = ${input.privateKey}`,
9937
- `Address = ${device.address}/128`,
10250
+ `Address = ${device.address}/128${ipv4 ? `, ${ipv4.address}/32` : ""}`,
9938
10251
  `MTU = ${network.mtu}`
9939
10252
  ];
9940
10253
  if (input.dns === "macos-resolver") {
@@ -9946,7 +10259,7 @@ function renderConfig(input) {
9946
10259
  const domains = zones.map((zone) => `'~${zone}'`).join(" ");
9947
10260
  lines.push(`PostUp = resolvectl dns %i ${network.gatewayAddress} && resolvectl domain %i ${domains}`);
9948
10261
  }
9949
- lines.push("", "[Peer]", `PublicKey = ${gateway.publicKey}`, `Endpoint = ${gateway.endpoint}`, `AllowedIPs = ${network.platformPrefix}`, "PersistentKeepalive = 25", "");
10262
+ lines.push("", "[Peer]", `PublicKey = ${gateway.publicKey}`, `Endpoint = ${gateway.endpoint}`, `AllowedIPs = ${network.prefix}${ipv4 ? `, ${ipv4.ingress}/32` : ""}`, "PersistentKeepalive = 25", "");
9950
10263
  return lines.join(`
9951
10264
  `);
9952
10265
  }
@@ -9955,6 +10268,26 @@ function isDnsZone(zone) {
9955
10268
  const labels = zone.split(".");
9956
10269
  return zone.length <= 253 && labels.length >= 2 && labels.every((label) => DNS_LABEL.test(label));
9957
10270
  }
10271
+ function isIPv4Address(text) {
10272
+ return /^(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}$/.test(text);
10273
+ }
10274
+ function ipv4RouteOwner(platform, output) {
10275
+ if (platform === "darwin") {
10276
+ if (/^\s*destination: default$/m.test(output))
10277
+ return null;
10278
+ return /^\s*interface: (\S+)$/m.exec(output)?.[1] ?? null;
10279
+ }
10280
+ for (const line of output.split(`
10281
+ `).map((l) => l.trim())) {
10282
+ if (!line || /^(default|broadcast)\b/.test(line))
10283
+ continue;
10284
+ return /\bdev (\S+)/.exec(line)?.[1] ?? "another route";
10285
+ }
10286
+ return null;
10287
+ }
10288
+ function ipv4RouteCommand(platform, address) {
10289
+ return platform === "darwin" ? ["route", ["-n", "get", address]] : ["ip", ["-4", "route", "show", "table", "all", "match", address]];
10290
+ }
9958
10291
  function isIPv6Address(text) {
9959
10292
  return /^[0-9a-f:]{2,39}$/.test(text) && text.includes(":");
9960
10293
  }
@@ -9995,6 +10328,11 @@ function defaultDeps() {
9995
10328
  child.on("error", reject);
9996
10329
  child.on("close", (code) => resolve(code ?? 1));
9997
10330
  }),
10331
+ output: (command, args) => {
10332
+ const result = spawnSync(command, args, { encoding: "utf8" });
10333
+ return result.status === 0 ? result.stdout : "";
10334
+ },
10335
+ platform: process.platform,
9998
10336
  has: (command) => spawnSync("sh", ["-c", `command -v ${command}`]).status === 0,
9999
10337
  dir: networkDir(),
10000
10338
  up: () => tunnelUp(),
@@ -10054,7 +10392,7 @@ async function runNetworkConnect(args = {}, deps = defaultDeps()) {
10054
10392
  await writeDeviceState({
10055
10393
  baseUrl: getBaseUrl(),
10056
10394
  deviceId: registered.device.id,
10057
- address: registered.device.address,
10395
+ address: registered.device.address ?? "",
10058
10396
  gatewayProvider: registered.provider
10059
10397
  }, deps.dir);
10060
10398
  const wasUp = deps.up();
@@ -10065,6 +10403,7 @@ async function runNetworkConnect(args = {}, deps = defaultDeps()) {
10065
10403
  return 1;
10066
10404
  }
10067
10405
  }
10406
+ const ipv4Clashes = ipv4Clashes4(registered, deps);
10068
10407
  const code = await wgQuick(deps, "up");
10069
10408
  if (code !== 0) {
10070
10409
  deps.error(`wg-quick exited ${code}. The config is in ${configPath(deps.dir)}.`);
@@ -10086,6 +10425,12 @@ async function runNetworkConnect(args = {}, deps = defaultDeps()) {
10086
10425
  ` Another organization: ${getCliName()} network connect --org <org>.`
10087
10426
  ] : [],
10088
10427
  ...network.netZone && pinned && shortNameOrganizationChanged ? clearingNotice(network.netCookieSite) : [],
10428
+ ...network.ingress4 && !device.address4 ? [
10429
+ " No IPv4 address was free on this provider's host, so browser names work over IPv6 only:",
10430
+ " Chrome on a network without IPv6 cannot open them on this machine. Firefox and Safari",
10431
+ " can. Run connect again later: an address frees up when another device goes."
10432
+ ] : [],
10433
+ ...ipv4Clashes.map(({ address, owner }) => ` ${address} is already routed through ${owner} on this machine: browsers may not reach the private network's names over IPv4 while it is. Ask your Ghostdock admin about the IPv4 block.`),
10089
10434
  ...dns === "none" ? [
10090
10435
  " No split DNS on this machine (no systemd-resolved): use the addresses from",
10091
10436
  ` \`${getCliName()} network ls\`.`
@@ -10094,6 +10439,17 @@ async function runNetworkConnect(args = {}, deps = defaultDeps()) {
10094
10439
  ], deps.log);
10095
10440
  return 0;
10096
10441
  }
10442
+ function ipv4Clashes4(registered, deps) {
10443
+ const addresses = [registered.network.ingress4, registered.device.address4].filter((a) => typeof a === "string" && isIPv4Address(a));
10444
+ const clashes = [];
10445
+ for (const address of addresses) {
10446
+ const [command, args] = ipv4RouteCommand(deps.platform, address);
10447
+ const owner = ipv4RouteOwner(deps.platform, deps.output(command, args));
10448
+ if (owner)
10449
+ clashes.push({ address, owner });
10450
+ }
10451
+ return clashes;
10452
+ }
10097
10453
  function clearingNotice(cookieSite) {
10098
10454
  const site = cookieSite?.site ?? "the net domain";
10099
10455
  return [
@@ -10210,7 +10566,7 @@ async function runNetworkLs(args, deps = defaultDeps()) {
10210
10566
  const note = unreachableNote(s, connected);
10211
10567
  lines.push([
10212
10568
  ` ${s.name.padEnd(width)}`,
10213
- s.address.padEnd(28),
10569
+ (s.address ?? "-").padEnd(28),
10214
10570
  s.canonicalName.padEnd(canonicalWidth),
10215
10571
  ...s.httpsName ? [`https://${s.httpsName}`.padEnd(httpsWidth)] : [],
10216
10572
  ...s.shortName ? [`https://${s.shortName}`.padEnd(shortWidth)] : [],
@@ -11961,6 +12317,7 @@ var GROUP_DESCRIPTIONS = {
11961
12317
  providers: "Sandbox providers of an organization.",
11962
12318
  sandboxes: "Create, run and manage sandboxes.",
11963
12319
  "sandboxes commands": "Run commands in a sandbox and read their output.",
12320
+ "sandboxes containers": "Containers of a sandbox’s Docker engine.",
11964
12321
  "sandboxes env": "Environment of one sandbox.",
11965
12322
  "sandboxes files": "Files inside a sandbox.",
11966
12323
  "sandboxes mcps": "MCP endpoints a sandbox exposes.",
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.13",
4
+ "version": "0.0.14",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "ghostdock": "dist/index.js",