ghostdock 0.0.13 → 0.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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",
@@ -4625,6 +4679,13 @@ var GENERATED_TOOL_CATALOG = [
4625
4679
  minLength: 1,
4626
4680
  description: "Sandbox name (e.g. quiet-harbor) or id, from sandboxes_list or sandboxes_create. Names are unique within the organization."
4627
4681
  },
4682
+ container: {
4683
+ 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.",
4684
+ type: "string",
4685
+ minLength: 1,
4686
+ maxLength: 255,
4687
+ pattern: "^[a-zA-Z0-9][a-zA-Z0-9_.-]*$"
4688
+ },
4628
4689
  files: {
4629
4690
  description: "Small inline files: { path, content } for utf8 text, or { path, contentBase64 } for binary.",
4630
4691
  type: "array",
@@ -4701,6 +4762,13 @@ var GENERATED_TOOL_CATALOG = [
4701
4762
  minLength: 1,
4702
4763
  description: "Sandbox name (e.g. quiet-harbor) or id, from sandboxes_list or sandboxes_create. Names are unique within the organization."
4703
4764
  },
4765
+ container: {
4766
+ 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.",
4767
+ type: "string",
4768
+ minLength: 1,
4769
+ maxLength: 255,
4770
+ pattern: "^[a-zA-Z0-9][a-zA-Z0-9_.-]*$"
4771
+ },
4704
4772
  path: {
4705
4773
  description: "Directory to list. Absolute or relative to /. Defaults to /workspace.",
4706
4774
  type: "string",
@@ -4780,6 +4848,13 @@ var GENERATED_TOOL_CATALOG = [
4780
4848
  minLength: 1,
4781
4849
  description: "Sandbox name (e.g. quiet-harbor) or id, from sandboxes_list or sandboxes_create. Names are unique within the organization."
4782
4850
  },
4851
+ container: {
4852
+ 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.",
4853
+ type: "string",
4854
+ minLength: 1,
4855
+ maxLength: 255,
4856
+ pattern: "^[a-zA-Z0-9][a-zA-Z0-9_.-]*$"
4857
+ },
4783
4858
  path: {
4784
4859
  type: "string",
4785
4860
  minLength: 1,
@@ -5922,6 +5997,13 @@ var GENERATED_TOOL_CATALOG = [
5922
5997
  type: "string",
5923
5998
  minLength: 1,
5924
5999
  description: "Sandbox name (e.g. quiet-harbor) or id, from sandboxes_list or sandboxes_create. Names are unique within the organization."
6000
+ },
6001
+ container: {
6002
+ 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.",
6003
+ type: "string",
6004
+ minLength: 1,
6005
+ maxLength: 255,
6006
+ pattern: "^[a-zA-Z0-9][a-zA-Z0-9_.-]*$"
5925
6007
  }
5926
6008
  },
5927
6009
  required: [
@@ -5982,17 +6064,17 @@ var GENERATED_TOOL_CATALOG = [
5982
6064
  }
5983
6065
  },
5984
6066
  {
5985
- id: "sandboxes_commands_run",
5986
- name: "sandboxes_commands_run",
6067
+ id: "sandboxes_containers_list",
6068
+ name: "sandboxes_containers_list",
5987
6069
  cli: [
5988
6070
  "sandboxes",
5989
- "commands",
5990
- "run"
6071
+ "containers",
6072
+ "list"
5991
6073
  ],
5992
6074
  cliArgs: [
5993
6075
  "sandboxId"
5994
6076
  ],
5995
- description: "Run a shell command in a sandbox. Waits up to 120 seconds, then returns sessionId and commandId to poll. Starts the sandbox when allowed. Sessions do not survive idle-stop: for a process that must keep running, read the sandbox-boot skill. Never put secret values in the command; it is logged.",
6077
+ 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).",
5996
6078
  inputSchema: {
5997
6079
  $schema: "https://json-schema.org/draft/2020-12/schema",
5998
6080
  type: "object",
@@ -6006,31 +6088,11 @@ var GENERATED_TOOL_CATALOG = [
6006
6088
  type: "string",
6007
6089
  minLength: 1,
6008
6090
  description: "Sandbox name (e.g. quiet-harbor) or id, from sandboxes_list or sandboxes_create. Names are unique within the organization."
6009
- },
6010
- command: {
6011
- type: "string",
6012
- minLength: 1,
6013
- description: "Shell command to run. Never include secret values: commands are logged."
6014
- },
6015
- sessionId: {
6016
- description: "Session from sandboxes_shells_create, to keep cwd and environment from earlier commands. Omit for a one-off session.",
6017
- type: "string",
6018
- minLength: 1
6019
- },
6020
- cwd: {
6021
- description: "Directory to run the command in.",
6022
- type: "string",
6023
- minLength: 1
6024
- },
6025
- waitSeconds: {
6026
- description: "Seconds to wait before returning handles to poll: 1 to 120, default 120.",
6027
- type: "number"
6028
6091
  }
6029
6092
  },
6030
6093
  required: [
6031
6094
  "organization",
6032
- "sandboxId",
6033
- "command"
6095
+ "sandboxId"
6034
6096
  ],
6035
6097
  additionalProperties: false
6036
6098
  },
@@ -6044,17 +6106,16 @@ var GENERATED_TOOL_CATALOG = [
6044
6106
  }
6045
6107
  },
6046
6108
  {
6047
- id: "sandboxes_commands_get",
6048
- name: "sandboxes_commands_get",
6109
+ id: "sandboxes_default_container",
6110
+ name: "sandboxes_default_container",
6049
6111
  cli: [
6050
6112
  "sandboxes",
6051
- "commands",
6052
- "get"
6113
+ "default-container"
6053
6114
  ],
6054
6115
  cliArgs: [
6055
- "commandId"
6116
+ "sandboxId"
6056
6117
  ],
6057
- description: "Get a shell command’s status and exit code.",
6118
+ 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.",
6058
6119
  inputSchema: {
6059
6120
  $schema: "https://json-schema.org/draft/2020-12/schema",
6060
6121
  type: "object",
@@ -6069,22 +6130,21 @@ var GENERATED_TOOL_CATALOG = [
6069
6130
  minLength: 1,
6070
6131
  description: "Sandbox name (e.g. quiet-harbor) or id, from sandboxes_list or sandboxes_create. Names are unique within the organization."
6071
6132
  },
6072
- sessionId: {
6133
+ container: {
6134
+ description: "Container to make the default, by name. The running sandbox must have it.",
6073
6135
  type: "string",
6074
6136
  minLength: 1,
6075
- description: "Session id returned by sandboxes_commands_run together with the commandId."
6137
+ maxLength: 255,
6138
+ pattern: "^[a-zA-Z0-9][a-zA-Z0-9_.-]*$"
6076
6139
  },
6077
- commandId: {
6078
- type: "string",
6079
- minLength: 1,
6080
- description: "Command id returned by sandboxes_commands_run."
6140
+ clear: {
6141
+ description: "Clear the default.",
6142
+ type: "boolean"
6081
6143
  }
6082
6144
  },
6083
6145
  required: [
6084
6146
  "organization",
6085
- "sandboxId",
6086
- "sessionId",
6087
- "commandId"
6147
+ "sandboxId"
6088
6148
  ],
6089
6149
  additionalProperties: false
6090
6150
  },
@@ -6098,17 +6158,18 @@ var GENERATED_TOOL_CATALOG = [
6098
6158
  }
6099
6159
  },
6100
6160
  {
6101
- id: "sandboxes_commands_logs",
6102
- name: "sandboxes_commands_logs",
6161
+ id: "sandboxes_containers_protection",
6162
+ name: "sandboxes_containers_protection",
6103
6163
  cli: [
6104
6164
  "sandboxes",
6105
- "commands",
6106
- "logs"
6165
+ "containers",
6166
+ "protection"
6107
6167
  ],
6108
6168
  cliArgs: [
6109
- "commandId"
6169
+ "sandboxId",
6170
+ "container"
6110
6171
  ],
6111
- description: "Get a page of a shell command’s stdout and stderr.",
6172
+ 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.",
6112
6173
  inputSchema: {
6113
6174
  $schema: "https://json-schema.org/draft/2020-12/schema",
6114
6175
  type: "object",
@@ -6123,28 +6184,23 @@ var GENERATED_TOOL_CATALOG = [
6123
6184
  minLength: 1,
6124
6185
  description: "Sandbox name (e.g. quiet-harbor) or id, from sandboxes_list or sandboxes_create. Names are unique within the organization."
6125
6186
  },
6126
- sessionId: {
6127
- type: "string",
6128
- minLength: 1,
6129
- description: "Session id returned by sandboxes_commands_run together with the commandId."
6130
- },
6131
- commandId: {
6187
+ container: {
6132
6188
  type: "string",
6133
6189
  minLength: 1,
6134
- description: "Command id returned by sandboxes_commands_run."
6190
+ maxLength: 255,
6191
+ pattern: "^[a-zA-Z0-9][a-zA-Z0-9_.-]*$",
6192
+ description: "Container, by name."
6135
6193
  },
6136
- offset: {
6137
- description: "Character offset to continue reading from. Default 0.",
6138
- type: "integer",
6139
- minimum: 0,
6140
- maximum: 9007199254740991
6194
+ protected: {
6195
+ type: "boolean",
6196
+ description: "true: protect it; false: allow removing it."
6141
6197
  }
6142
6198
  },
6143
6199
  required: [
6144
6200
  "organization",
6145
6201
  "sandboxId",
6146
- "sessionId",
6147
- "commandId"
6202
+ "container",
6203
+ "protected"
6148
6204
  ],
6149
6205
  additionalProperties: false
6150
6206
  },
@@ -6158,17 +6214,18 @@ var GENERATED_TOOL_CATALOG = [
6158
6214
  }
6159
6215
  },
6160
6216
  {
6161
- id: "sandboxes_commands_kill",
6162
- name: "sandboxes_commands_kill",
6217
+ id: "sandboxes_containers_start",
6218
+ name: "sandboxes_containers_start",
6163
6219
  cli: [
6164
6220
  "sandboxes",
6165
- "commands",
6166
- "kill"
6221
+ "containers",
6222
+ "start"
6167
6223
  ],
6168
6224
  cliArgs: [
6169
- "commandId"
6225
+ "sandboxId",
6226
+ "container"
6170
6227
  ],
6171
- description: "Interrupt a running shell command.",
6228
+ description: "Start a stopped container, as docker start does. A running container stays as it is. Starts the sandbox when needed.",
6172
6229
  inputSchema: {
6173
6230
  $schema: "https://json-schema.org/draft/2020-12/schema",
6174
6231
  type: "object",
@@ -6183,22 +6240,18 @@ var GENERATED_TOOL_CATALOG = [
6183
6240
  minLength: 1,
6184
6241
  description: "Sandbox name (e.g. quiet-harbor) or id, from sandboxes_list or sandboxes_create. Names are unique within the organization."
6185
6242
  },
6186
- sessionId: {
6187
- type: "string",
6188
- minLength: 1,
6189
- description: "Session id returned by sandboxes_commands_run together with the commandId."
6190
- },
6191
- commandId: {
6243
+ container: {
6192
6244
  type: "string",
6193
6245
  minLength: 1,
6194
- description: "Command id returned by sandboxes_commands_run."
6246
+ maxLength: 255,
6247
+ pattern: "^[a-zA-Z0-9][a-zA-Z0-9_.-]*$",
6248
+ description: "Container, by name."
6195
6249
  }
6196
6250
  },
6197
6251
  required: [
6198
6252
  "organization",
6199
6253
  "sandboxId",
6200
- "sessionId",
6201
- "commandId"
6254
+ "container"
6202
6255
  ],
6203
6256
  additionalProperties: false
6204
6257
  },
@@ -6212,17 +6265,18 @@ var GENERATED_TOOL_CATALOG = [
6212
6265
  }
6213
6266
  },
6214
6267
  {
6215
- id: "sandboxes_shells_create",
6216
- name: "sandboxes_shells_create",
6268
+ id: "sandboxes_containers_stop",
6269
+ name: "sandboxes_containers_stop",
6217
6270
  cli: [
6218
6271
  "sandboxes",
6219
- "shells",
6220
- "create"
6272
+ "containers",
6273
+ "stop"
6221
6274
  ],
6222
6275
  cliArgs: [
6223
- "sandboxId"
6276
+ "sandboxId",
6277
+ "container"
6224
6278
  ],
6225
- description: "Open a shell session that keeps cwd and environment between commands. Starts the sandbox when allowed; the session ends at idle-stop.",
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.",
6226
6280
  inputSchema: {
6227
6281
  $schema: "https://json-schema.org/draft/2020-12/schema",
6228
6282
  type: "object",
@@ -6237,15 +6291,18 @@ var GENERATED_TOOL_CATALOG = [
6237
6291
  minLength: 1,
6238
6292
  description: "Sandbox name (e.g. quiet-harbor) or id, from sandboxes_list or sandboxes_create. Names are unique within the organization."
6239
6293
  },
6240
- sessionId: {
6241
- description: "Id for the new session: a slug; user-cmd, image-cmd and compose are reserved. Omit to generate one.",
6294
+ container: {
6242
6295
  type: "string",
6243
- minLength: 1
6296
+ minLength: 1,
6297
+ maxLength: 255,
6298
+ pattern: "^[a-zA-Z0-9][a-zA-Z0-9_.-]*$",
6299
+ description: "Container, by name."
6244
6300
  }
6245
6301
  },
6246
6302
  required: [
6247
6303
  "organization",
6248
- "sandboxId"
6304
+ "sandboxId",
6305
+ "container"
6249
6306
  ],
6250
6307
  additionalProperties: false
6251
6308
  },
@@ -6259,17 +6316,18 @@ var GENERATED_TOOL_CATALOG = [
6259
6316
  }
6260
6317
  },
6261
6318
  {
6262
- id: "sandboxes_shells_close",
6263
- name: "sandboxes_shells_close",
6319
+ id: "sandboxes_containers_restart",
6320
+ name: "sandboxes_containers_restart",
6264
6321
  cli: [
6265
6322
  "sandboxes",
6266
- "shells",
6267
- "close"
6323
+ "containers",
6324
+ "restart"
6268
6325
  ],
6269
6326
  cliArgs: [
6270
- "sessionId"
6327
+ "sandboxId",
6328
+ "container"
6271
6329
  ],
6272
- description: "Close a shell session.",
6330
+ description: "Restart a container, as docker restart does (stopping as docker stop does). Starts the sandbox when needed.",
6273
6331
  inputSchema: {
6274
6332
  $schema: "https://json-schema.org/draft/2020-12/schema",
6275
6333
  type: "object",
@@ -6284,16 +6342,18 @@ var GENERATED_TOOL_CATALOG = [
6284
6342
  minLength: 1,
6285
6343
  description: "Sandbox name (e.g. quiet-harbor) or id, from sandboxes_list or sandboxes_create. Names are unique within the organization."
6286
6344
  },
6287
- sessionId: {
6345
+ container: {
6288
6346
  type: "string",
6289
6347
  minLength: 1,
6290
- description: "Session id from sandboxes_shells_create."
6348
+ maxLength: 255,
6349
+ pattern: "^[a-zA-Z0-9][a-zA-Z0-9_.-]*$",
6350
+ description: "Container, by name."
6291
6351
  }
6292
6352
  },
6293
6353
  required: [
6294
6354
  "organization",
6295
6355
  "sandboxId",
6296
- "sessionId"
6356
+ "container"
6297
6357
  ],
6298
6358
  additionalProperties: false
6299
6359
  },
@@ -6307,16 +6367,18 @@ var GENERATED_TOOL_CATALOG = [
6307
6367
  }
6308
6368
  },
6309
6369
  {
6310
- id: "sandboxes_set_boot",
6311
- name: "sandboxes_set_boot",
6370
+ id: "sandboxes_containers_logs",
6371
+ name: "sandboxes_containers_logs",
6312
6372
  cli: [
6313
6373
  "sandboxes",
6314
- "set-boot"
6374
+ "containers",
6375
+ "logs"
6315
6376
  ],
6316
6377
  cliArgs: [
6317
- "sandboxId"
6378
+ "sandboxId",
6379
+ "container"
6318
6380
  ],
6319
- description: "Register a command that starts now and again after every restart. For example bun dev. On Compose it runs after the stack; on a Dockerfile it replaces the image CMD. Snapshots cannot register one. Read the sandbox-boot skill.",
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).",
6320
6382
  inputSchema: {
6321
6383
  $schema: "https://json-schema.org/draft/2020-12/schema",
6322
6384
  type: "object",
@@ -6331,25 +6393,29 @@ var GENERATED_TOOL_CATALOG = [
6331
6393
  minLength: 1,
6332
6394
  description: "Sandbox name (e.g. quiet-harbor) or id, from sandboxes_list or sandboxes_create. Names are unique within the organization."
6333
6395
  },
6334
- command: {
6396
+ container: {
6335
6397
  type: "string",
6336
6398
  minLength: 1,
6337
- description: "Command to start, e.g. bun dev."
6399
+ maxLength: 255,
6400
+ pattern: "^[a-zA-Z0-9][a-zA-Z0-9_.-]*$",
6401
+ description: "Container, by name."
6338
6402
  },
6339
- cwd: {
6340
- description: "Directory to run it in. Default /.",
6341
- type: "string",
6342
- minLength: 1
6403
+ tail: {
6404
+ description: "The last N lines (at most 5000).",
6405
+ type: "integer",
6406
+ minimum: 1,
6407
+ maximum: 5000
6343
6408
  },
6344
- start: {
6345
- description: "Also start it now, not only after restarts. Default true.",
6346
- type: "boolean"
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})$"
6347
6413
  }
6348
6414
  },
6349
6415
  required: [
6350
6416
  "organization",
6351
6417
  "sandboxId",
6352
- "command"
6418
+ "container"
6353
6419
  ],
6354
6420
  additionalProperties: false
6355
6421
  },
@@ -6363,16 +6429,17 @@ var GENERATED_TOOL_CATALOG = [
6363
6429
  }
6364
6430
  },
6365
6431
  {
6366
- id: "sandboxes_update",
6367
- name: "sandboxes_update",
6432
+ id: "sandboxes_storage_list",
6433
+ name: "sandboxes_storage_list",
6368
6434
  cli: [
6369
6435
  "sandboxes",
6370
- "update"
6436
+ "storage",
6437
+ "list"
6371
6438
  ],
6372
6439
  cliArgs: [
6373
6440
  "sandboxId"
6374
6441
  ],
6375
- description: "Change a sandbox’s display name, idle-stop or auto-delete interval. Omit a field to keep it; null resets it to the platform default.",
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).",
6376
6443
  inputSchema: {
6377
6444
  $schema: "https://json-schema.org/draft/2020-12/schema",
6378
6445
  type: "object",
@@ -6386,39 +6453,6 @@ var GENERATED_TOOL_CATALOG = [
6386
6453
  type: "string",
6387
6454
  minLength: 1,
6388
6455
  description: "Sandbox name (e.g. quiet-harbor) or id, from sandboxes_list or sandboxes_create. Names are unique within the organization."
6389
- },
6390
- displayName: {
6391
- description: "New display name. Null clears it; omit to keep it.",
6392
- anyOf: [
6393
- {
6394
- type: "string"
6395
- },
6396
- {
6397
- type: "null"
6398
- }
6399
- ]
6400
- },
6401
- autoStopIntervalMinutes: {
6402
- description: "Minutes of inactivity before the platform worker idle-stops the sandbox. -1 always on. 0 is rejected. Null resets to the platform default. Provider-native timers are optional backup.",
6403
- anyOf: [
6404
- {
6405
- type: "number"
6406
- },
6407
- {
6408
- type: "null"
6409
- }
6410
- ]
6411
- },
6412
- autoDeleteIntervalMinutes: {
6413
- description: "Minutes the sandbox stays stopped before the record is deleted. 0 deletes it when it stops. -1 keeps it. Null resets to the platform default. Provider-native timers are optional backup.",
6414
- anyOf: [
6415
- {
6416
- type: "number"
6417
- },
6418
- {
6419
- type: "null"
6420
- }
6421
- ]
6422
6456
  }
6423
6457
  },
6424
6458
  required: [
@@ -6437,16 +6471,17 @@ var GENERATED_TOOL_CATALOG = [
6437
6471
  }
6438
6472
  },
6439
6473
  {
6440
- id: "sandboxes_resize",
6441
- name: "sandboxes_resize",
6474
+ id: "sandboxes_storage_delete",
6475
+ name: "sandboxes_storage_delete",
6442
6476
  cli: [
6443
6477
  "sandboxes",
6444
- "resize"
6478
+ "storage",
6479
+ "delete"
6445
6480
  ],
6446
6481
  cliArgs: [
6447
6482
  "sandboxId"
6448
6483
  ],
6449
- description: "Change a sandbox’s CPU and/or memory. While running only increases are allowed; stop it first to decrease (this tool does not stop it). Disk: sandboxes_resize_disk.",
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.",
6450
6485
  inputSchema: {
6451
6486
  $schema: "https://json-schema.org/draft/2020-12/schema",
6452
6487
  type: "object",
@@ -6461,20 +6496,25 @@ var GENERATED_TOOL_CATALOG = [
6461
6496
  minLength: 1,
6462
6497
  description: "Sandbox name (e.g. quiet-harbor) or id, from sandboxes_list or sandboxes_create. Names are unique within the organization."
6463
6498
  },
6464
- cpu: {
6465
- description: "Number of CPUs.",
6466
- type: "number",
6467
- exclusiveMinimum: 0
6499
+ kind: {
6500
+ type: "string",
6501
+ enum: [
6502
+ "volume",
6503
+ "folder"
6504
+ ],
6505
+ description: "volume or folder."
6468
6506
  },
6469
- memoryGiB: {
6470
- description: "Memory in GiB.",
6471
- type: "number",
6472
- exclusiveMinimum: 0
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)."
6473
6511
  }
6474
6512
  },
6475
6513
  required: [
6476
6514
  "organization",
6477
- "sandboxId"
6515
+ "sandboxId",
6516
+ "kind",
6517
+ "name"
6478
6518
  ],
6479
6519
  additionalProperties: false
6480
6520
  },
@@ -6488,16 +6528,17 @@ var GENERATED_TOOL_CATALOG = [
6488
6528
  }
6489
6529
  },
6490
6530
  {
6491
- id: "sandboxes_resize_disk",
6492
- name: "sandboxes_resize_disk",
6531
+ id: "sandboxes_commands_run",
6532
+ name: "sandboxes_commands_run",
6493
6533
  cli: [
6494
6534
  "sandboxes",
6495
- "resize-disk"
6535
+ "commands",
6536
+ "run"
6496
6537
  ],
6497
6538
  cliArgs: [
6498
6539
  "sandboxId"
6499
6540
  ],
6500
- description: "Grow a sandbox’s disk. The sandbox must be stopped first (this tool does not stop it). Disk only grows, never shrinks.",
6541
+ description: "Run a shell command in a sandbox. Waits up to 120 seconds, then returns sessionId and commandId to poll. Starts the sandbox when allowed. Sessions do not survive idle-stop: for a process that must keep running, read the sandbox-boot skill. Never put secret values in the command; it is logged.",
6501
6542
  inputSchema: {
6502
6543
  $schema: "https://json-schema.org/draft/2020-12/schema",
6503
6544
  type: "object",
@@ -6512,10 +6553,557 @@ var GENERATED_TOOL_CATALOG = [
6512
6553
  minLength: 1,
6513
6554
  description: "Sandbox name (e.g. quiet-harbor) or id, from sandboxes_list or sandboxes_create. Names are unique within the organization."
6514
6555
  },
6515
- diskGiB: {
6516
- type: "number",
6517
- exclusiveMinimum: 0,
6518
- description: "New total disk size in GiB; must be larger than the current size."
6556
+ container: {
6557
+ 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.",
6558
+ type: "string",
6559
+ minLength: 1,
6560
+ maxLength: 255,
6561
+ pattern: "^[a-zA-Z0-9][a-zA-Z0-9_.-]*$"
6562
+ },
6563
+ command: {
6564
+ type: "string",
6565
+ minLength: 1,
6566
+ description: "Shell command to run. Never include secret values: commands are logged."
6567
+ },
6568
+ sessionId: {
6569
+ description: "Session from sandboxes_shells_create, to keep cwd and environment from earlier commands. Omit for a one-off session.",
6570
+ type: "string",
6571
+ minLength: 1
6572
+ },
6573
+ cwd: {
6574
+ description: "Directory to run the command in.",
6575
+ type: "string",
6576
+ minLength: 1
6577
+ },
6578
+ waitSeconds: {
6579
+ description: "Seconds to wait before returning handles to poll: 1 to 120, default 120.",
6580
+ type: "number"
6581
+ }
6582
+ },
6583
+ required: [
6584
+ "organization",
6585
+ "sandboxId",
6586
+ "command"
6587
+ ],
6588
+ additionalProperties: false
6589
+ },
6590
+ outputSchema: {
6591
+ $schema: "https://json-schema.org/draft/2020-12/schema",
6592
+ type: "object",
6593
+ propertyNames: {
6594
+ type: "string"
6595
+ },
6596
+ additionalProperties: {}
6597
+ }
6598
+ },
6599
+ {
6600
+ id: "sandboxes_commands_get",
6601
+ name: "sandboxes_commands_get",
6602
+ cli: [
6603
+ "sandboxes",
6604
+ "commands",
6605
+ "get"
6606
+ ],
6607
+ cliArgs: [
6608
+ "commandId"
6609
+ ],
6610
+ description: "Get a shell command’s status and exit code.",
6611
+ inputSchema: {
6612
+ $schema: "https://json-schema.org/draft/2020-12/schema",
6613
+ type: "object",
6614
+ properties: {
6615
+ organization: {
6616
+ type: "string",
6617
+ minLength: 1,
6618
+ description: "Organization id or slug, from organizations_list. Ids survive renames; slugs change when the organization is renamed."
6619
+ },
6620
+ sandboxId: {
6621
+ type: "string",
6622
+ minLength: 1,
6623
+ description: "Sandbox name (e.g. quiet-harbor) or id, from sandboxes_list or sandboxes_create. Names are unique within the organization."
6624
+ },
6625
+ container: {
6626
+ 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.",
6627
+ type: "string",
6628
+ minLength: 1,
6629
+ maxLength: 255,
6630
+ pattern: "^[a-zA-Z0-9][a-zA-Z0-9_.-]*$"
6631
+ },
6632
+ sessionId: {
6633
+ type: "string",
6634
+ minLength: 1,
6635
+ description: "Session id returned by sandboxes_commands_run together with the commandId."
6636
+ },
6637
+ commandId: {
6638
+ type: "string",
6639
+ minLength: 1,
6640
+ description: "Command id returned by sandboxes_commands_run."
6641
+ }
6642
+ },
6643
+ required: [
6644
+ "organization",
6645
+ "sandboxId",
6646
+ "sessionId",
6647
+ "commandId"
6648
+ ],
6649
+ additionalProperties: false
6650
+ },
6651
+ outputSchema: {
6652
+ $schema: "https://json-schema.org/draft/2020-12/schema",
6653
+ type: "object",
6654
+ propertyNames: {
6655
+ type: "string"
6656
+ },
6657
+ additionalProperties: {}
6658
+ }
6659
+ },
6660
+ {
6661
+ id: "sandboxes_commands_logs",
6662
+ name: "sandboxes_commands_logs",
6663
+ cli: [
6664
+ "sandboxes",
6665
+ "commands",
6666
+ "logs"
6667
+ ],
6668
+ cliArgs: [
6669
+ "commandId"
6670
+ ],
6671
+ description: "Get a page of a shell command’s stdout and stderr.",
6672
+ inputSchema: {
6673
+ $schema: "https://json-schema.org/draft/2020-12/schema",
6674
+ type: "object",
6675
+ properties: {
6676
+ organization: {
6677
+ type: "string",
6678
+ minLength: 1,
6679
+ description: "Organization id or slug, from organizations_list. Ids survive renames; slugs change when the organization is renamed."
6680
+ },
6681
+ sandboxId: {
6682
+ type: "string",
6683
+ minLength: 1,
6684
+ description: "Sandbox name (e.g. quiet-harbor) or id, from sandboxes_list or sandboxes_create. Names are unique within the organization."
6685
+ },
6686
+ container: {
6687
+ 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.",
6688
+ type: "string",
6689
+ minLength: 1,
6690
+ maxLength: 255,
6691
+ pattern: "^[a-zA-Z0-9][a-zA-Z0-9_.-]*$"
6692
+ },
6693
+ sessionId: {
6694
+ type: "string",
6695
+ minLength: 1,
6696
+ description: "Session id returned by sandboxes_commands_run together with the commandId."
6697
+ },
6698
+ commandId: {
6699
+ type: "string",
6700
+ minLength: 1,
6701
+ description: "Command id returned by sandboxes_commands_run."
6702
+ },
6703
+ offset: {
6704
+ description: "Character offset to continue reading from. Default 0.",
6705
+ type: "integer",
6706
+ minimum: 0,
6707
+ maximum: 9007199254740991
6708
+ }
6709
+ },
6710
+ required: [
6711
+ "organization",
6712
+ "sandboxId",
6713
+ "sessionId",
6714
+ "commandId"
6715
+ ],
6716
+ additionalProperties: false
6717
+ },
6718
+ outputSchema: {
6719
+ $schema: "https://json-schema.org/draft/2020-12/schema",
6720
+ type: "object",
6721
+ propertyNames: {
6722
+ type: "string"
6723
+ },
6724
+ additionalProperties: {}
6725
+ }
6726
+ },
6727
+ {
6728
+ id: "sandboxes_commands_kill",
6729
+ name: "sandboxes_commands_kill",
6730
+ cli: [
6731
+ "sandboxes",
6732
+ "commands",
6733
+ "kill"
6734
+ ],
6735
+ cliArgs: [
6736
+ "commandId"
6737
+ ],
6738
+ description: "Interrupt a running shell command.",
6739
+ inputSchema: {
6740
+ $schema: "https://json-schema.org/draft/2020-12/schema",
6741
+ type: "object",
6742
+ properties: {
6743
+ organization: {
6744
+ type: "string",
6745
+ minLength: 1,
6746
+ description: "Organization id or slug, from organizations_list. Ids survive renames; slugs change when the organization is renamed."
6747
+ },
6748
+ sandboxId: {
6749
+ type: "string",
6750
+ minLength: 1,
6751
+ description: "Sandbox name (e.g. quiet-harbor) or id, from sandboxes_list or sandboxes_create. Names are unique within the organization."
6752
+ },
6753
+ container: {
6754
+ 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.",
6755
+ type: "string",
6756
+ minLength: 1,
6757
+ maxLength: 255,
6758
+ pattern: "^[a-zA-Z0-9][a-zA-Z0-9_.-]*$"
6759
+ },
6760
+ sessionId: {
6761
+ type: "string",
6762
+ minLength: 1,
6763
+ description: "Session id returned by sandboxes_commands_run together with the commandId."
6764
+ },
6765
+ commandId: {
6766
+ type: "string",
6767
+ minLength: 1,
6768
+ description: "Command id returned by sandboxes_commands_run."
6769
+ }
6770
+ },
6771
+ required: [
6772
+ "organization",
6773
+ "sandboxId",
6774
+ "sessionId",
6775
+ "commandId"
6776
+ ],
6777
+ additionalProperties: false
6778
+ },
6779
+ outputSchema: {
6780
+ $schema: "https://json-schema.org/draft/2020-12/schema",
6781
+ type: "object",
6782
+ propertyNames: {
6783
+ type: "string"
6784
+ },
6785
+ additionalProperties: {}
6786
+ }
6787
+ },
6788
+ {
6789
+ id: "sandboxes_shells_create",
6790
+ name: "sandboxes_shells_create",
6791
+ cli: [
6792
+ "sandboxes",
6793
+ "shells",
6794
+ "create"
6795
+ ],
6796
+ cliArgs: [
6797
+ "sandboxId"
6798
+ ],
6799
+ description: "Open a shell session that keeps cwd and environment between commands. Starts the sandbox when allowed; the session ends at idle-stop.",
6800
+ inputSchema: {
6801
+ $schema: "https://json-schema.org/draft/2020-12/schema",
6802
+ type: "object",
6803
+ properties: {
6804
+ organization: {
6805
+ type: "string",
6806
+ minLength: 1,
6807
+ description: "Organization id or slug, from organizations_list. Ids survive renames; slugs change when the organization is renamed."
6808
+ },
6809
+ sandboxId: {
6810
+ type: "string",
6811
+ minLength: 1,
6812
+ description: "Sandbox name (e.g. quiet-harbor) or id, from sandboxes_list or sandboxes_create. Names are unique within the organization."
6813
+ },
6814
+ container: {
6815
+ 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.",
6816
+ type: "string",
6817
+ minLength: 1,
6818
+ maxLength: 255,
6819
+ pattern: "^[a-zA-Z0-9][a-zA-Z0-9_.-]*$"
6820
+ },
6821
+ sessionId: {
6822
+ description: "Id for the new session: a slug; user-cmd, image-cmd and compose are reserved. Omit to generate one.",
6823
+ type: "string",
6824
+ minLength: 1
6825
+ }
6826
+ },
6827
+ required: [
6828
+ "organization",
6829
+ "sandboxId"
6830
+ ],
6831
+ additionalProperties: false
6832
+ },
6833
+ outputSchema: {
6834
+ $schema: "https://json-schema.org/draft/2020-12/schema",
6835
+ type: "object",
6836
+ propertyNames: {
6837
+ type: "string"
6838
+ },
6839
+ additionalProperties: {}
6840
+ }
6841
+ },
6842
+ {
6843
+ id: "sandboxes_shells_close",
6844
+ name: "sandboxes_shells_close",
6845
+ cli: [
6846
+ "sandboxes",
6847
+ "shells",
6848
+ "close"
6849
+ ],
6850
+ cliArgs: [
6851
+ "sessionId"
6852
+ ],
6853
+ description: "Close a shell session.",
6854
+ inputSchema: {
6855
+ $schema: "https://json-schema.org/draft/2020-12/schema",
6856
+ type: "object",
6857
+ properties: {
6858
+ organization: {
6859
+ type: "string",
6860
+ minLength: 1,
6861
+ description: "Organization id or slug, from organizations_list. Ids survive renames; slugs change when the organization is renamed."
6862
+ },
6863
+ sandboxId: {
6864
+ type: "string",
6865
+ minLength: 1,
6866
+ description: "Sandbox name (e.g. quiet-harbor) or id, from sandboxes_list or sandboxes_create. Names are unique within the organization."
6867
+ },
6868
+ container: {
6869
+ 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.",
6870
+ type: "string",
6871
+ minLength: 1,
6872
+ maxLength: 255,
6873
+ pattern: "^[a-zA-Z0-9][a-zA-Z0-9_.-]*$"
6874
+ },
6875
+ sessionId: {
6876
+ type: "string",
6877
+ minLength: 1,
6878
+ description: "Session id from sandboxes_shells_create."
6879
+ }
6880
+ },
6881
+ required: [
6882
+ "organization",
6883
+ "sandboxId",
6884
+ "sessionId"
6885
+ ],
6886
+ additionalProperties: false
6887
+ },
6888
+ outputSchema: {
6889
+ $schema: "https://json-schema.org/draft/2020-12/schema",
6890
+ type: "object",
6891
+ propertyNames: {
6892
+ type: "string"
6893
+ },
6894
+ additionalProperties: {}
6895
+ }
6896
+ },
6897
+ {
6898
+ id: "sandboxes_set_boot",
6899
+ name: "sandboxes_set_boot",
6900
+ cli: [
6901
+ "sandboxes",
6902
+ "set-boot"
6903
+ ],
6904
+ cliArgs: [
6905
+ "sandboxId"
6906
+ ],
6907
+ description: "Register a command that starts now and again after every restart. For example bun dev. On Compose it runs after the stack; on a Dockerfile it replaces the image CMD. Snapshots cannot register one. Read the sandbox-boot skill.",
6908
+ inputSchema: {
6909
+ $schema: "https://json-schema.org/draft/2020-12/schema",
6910
+ type: "object",
6911
+ properties: {
6912
+ organization: {
6913
+ type: "string",
6914
+ minLength: 1,
6915
+ description: "Organization id or slug, from organizations_list. Ids survive renames; slugs change when the organization is renamed."
6916
+ },
6917
+ sandboxId: {
6918
+ type: "string",
6919
+ minLength: 1,
6920
+ description: "Sandbox name (e.g. quiet-harbor) or id, from sandboxes_list or sandboxes_create. Names are unique within the organization."
6921
+ },
6922
+ command: {
6923
+ type: "string",
6924
+ minLength: 1,
6925
+ description: "Command to start, e.g. bun dev."
6926
+ },
6927
+ cwd: {
6928
+ description: "Directory to run it in. Default /.",
6929
+ type: "string",
6930
+ minLength: 1
6931
+ },
6932
+ start: {
6933
+ description: "Also start it now, not only after restarts. Default true.",
6934
+ type: "boolean"
6935
+ }
6936
+ },
6937
+ required: [
6938
+ "organization",
6939
+ "sandboxId",
6940
+ "command"
6941
+ ],
6942
+ additionalProperties: false
6943
+ },
6944
+ outputSchema: {
6945
+ $schema: "https://json-schema.org/draft/2020-12/schema",
6946
+ type: "object",
6947
+ propertyNames: {
6948
+ type: "string"
6949
+ },
6950
+ additionalProperties: {}
6951
+ }
6952
+ },
6953
+ {
6954
+ id: "sandboxes_update",
6955
+ name: "sandboxes_update",
6956
+ cli: [
6957
+ "sandboxes",
6958
+ "update"
6959
+ ],
6960
+ cliArgs: [
6961
+ "sandboxId"
6962
+ ],
6963
+ description: "Change a sandbox’s display name, idle-stop or auto-delete interval. Omit a field to keep it; null resets it to the platform default.",
6964
+ inputSchema: {
6965
+ $schema: "https://json-schema.org/draft/2020-12/schema",
6966
+ type: "object",
6967
+ properties: {
6968
+ organization: {
6969
+ type: "string",
6970
+ minLength: 1,
6971
+ description: "Organization id or slug, from organizations_list. Ids survive renames; slugs change when the organization is renamed."
6972
+ },
6973
+ sandboxId: {
6974
+ type: "string",
6975
+ minLength: 1,
6976
+ description: "Sandbox name (e.g. quiet-harbor) or id, from sandboxes_list or sandboxes_create. Names are unique within the organization."
6977
+ },
6978
+ displayName: {
6979
+ description: "New display name. Null clears it; omit to keep it.",
6980
+ anyOf: [
6981
+ {
6982
+ type: "string"
6983
+ },
6984
+ {
6985
+ type: "null"
6986
+ }
6987
+ ]
6988
+ },
6989
+ autoStopIntervalMinutes: {
6990
+ description: "Minutes of inactivity before the platform worker idle-stops the sandbox. -1 always on. 0 is rejected. Null resets to the platform default. Provider-native timers are optional backup.",
6991
+ anyOf: [
6992
+ {
6993
+ type: "number"
6994
+ },
6995
+ {
6996
+ type: "null"
6997
+ }
6998
+ ]
6999
+ },
7000
+ autoDeleteIntervalMinutes: {
7001
+ description: "Minutes the sandbox stays stopped before the record is deleted. 0 deletes it when it stops. -1 keeps it. Null resets to the platform default. Provider-native timers are optional backup.",
7002
+ anyOf: [
7003
+ {
7004
+ type: "number"
7005
+ },
7006
+ {
7007
+ type: "null"
7008
+ }
7009
+ ]
7010
+ }
7011
+ },
7012
+ required: [
7013
+ "organization",
7014
+ "sandboxId"
7015
+ ],
7016
+ additionalProperties: false
7017
+ },
7018
+ outputSchema: {
7019
+ $schema: "https://json-schema.org/draft/2020-12/schema",
7020
+ type: "object",
7021
+ propertyNames: {
7022
+ type: "string"
7023
+ },
7024
+ additionalProperties: {}
7025
+ }
7026
+ },
7027
+ {
7028
+ id: "sandboxes_resize",
7029
+ name: "sandboxes_resize",
7030
+ cli: [
7031
+ "sandboxes",
7032
+ "resize"
7033
+ ],
7034
+ cliArgs: [
7035
+ "sandboxId"
7036
+ ],
7037
+ description: "Change a sandbox’s CPU and/or memory. While running only increases are allowed; stop it first to decrease (this tool does not stop it). Disk: sandboxes_resize_disk.",
7038
+ inputSchema: {
7039
+ $schema: "https://json-schema.org/draft/2020-12/schema",
7040
+ type: "object",
7041
+ properties: {
7042
+ organization: {
7043
+ type: "string",
7044
+ minLength: 1,
7045
+ description: "Organization id or slug, from organizations_list. Ids survive renames; slugs change when the organization is renamed."
7046
+ },
7047
+ sandboxId: {
7048
+ type: "string",
7049
+ minLength: 1,
7050
+ description: "Sandbox name (e.g. quiet-harbor) or id, from sandboxes_list or sandboxes_create. Names are unique within the organization."
7051
+ },
7052
+ cpu: {
7053
+ description: "Number of CPUs.",
7054
+ type: "number",
7055
+ exclusiveMinimum: 0
7056
+ },
7057
+ memoryGiB: {
7058
+ description: "Memory in GiB.",
7059
+ type: "number",
7060
+ exclusiveMinimum: 0
7061
+ }
7062
+ },
7063
+ required: [
7064
+ "organization",
7065
+ "sandboxId"
7066
+ ],
7067
+ additionalProperties: false
7068
+ },
7069
+ outputSchema: {
7070
+ $schema: "https://json-schema.org/draft/2020-12/schema",
7071
+ type: "object",
7072
+ propertyNames: {
7073
+ type: "string"
7074
+ },
7075
+ additionalProperties: {}
7076
+ }
7077
+ },
7078
+ {
7079
+ id: "sandboxes_resize_disk",
7080
+ name: "sandboxes_resize_disk",
7081
+ cli: [
7082
+ "sandboxes",
7083
+ "resize-disk"
7084
+ ],
7085
+ cliArgs: [
7086
+ "sandboxId"
7087
+ ],
7088
+ description: "Grow a sandbox’s disk. The sandbox must be stopped first (this tool does not stop it). Disk only grows, never shrinks.",
7089
+ inputSchema: {
7090
+ $schema: "https://json-schema.org/draft/2020-12/schema",
7091
+ type: "object",
7092
+ properties: {
7093
+ organization: {
7094
+ type: "string",
7095
+ minLength: 1,
7096
+ description: "Organization id or slug, from organizations_list. Ids survive renames; slugs change when the organization is renamed."
7097
+ },
7098
+ sandboxId: {
7099
+ type: "string",
7100
+ minLength: 1,
7101
+ description: "Sandbox name (e.g. quiet-harbor) or id, from sandboxes_list or sandboxes_create. Names are unique within the organization."
7102
+ },
7103
+ diskGiB: {
7104
+ type: "number",
7105
+ exclusiveMinimum: 0,
7106
+ description: "New total disk size in GiB; must be larger than the current size."
6519
7107
  }
6520
7108
  },
6521
7109
  required: [
@@ -8588,7 +9176,7 @@ function getCliName(argv = process.argv) {
8588
9176
  return CLI_NAMES.find((name) => name === invoked) ?? "ghostdock";
8589
9177
  }
8590
9178
  // package.json
8591
- var version = "0.0.13";
9179
+ var version = "0.0.15";
8592
9180
  var package_default = {
8593
9181
  name: "ghostdock",
8594
9182
  description: "Ghostdock CLI — device login and agent tool invocation from the command line",
@@ -9193,6 +9781,150 @@ var envCommand = defineCommand({
9193
9781
  }
9194
9782
  });
9195
9783
 
9784
+ // src/container-logs.ts
9785
+ var FOLLOW_INTERVAL_MS = 2000;
9786
+ function defaultLogsDeps(follow) {
9787
+ let stop = false;
9788
+ if (follow) {
9789
+ process.once("SIGINT", () => {
9790
+ stop = true;
9791
+ });
9792
+ }
9793
+ return {
9794
+ request: (input) => authorizedRequest("/api/v1/tools/sandboxes_containers_logs", {
9795
+ method: "POST",
9796
+ body: JSON.stringify(input)
9797
+ }),
9798
+ out: (line) => process.stdout.write(`${line}
9799
+ `),
9800
+ err: (line) => process.stderr.write(`${line}
9801
+ `),
9802
+ sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
9803
+ stopped: () => stop
9804
+ };
9805
+ }
9806
+ async function runContainerLogs(input, options, deps = defaultLogsDeps(options.follow)) {
9807
+ const json = isJsonOutput();
9808
+ const color = colorEnabled(process.stdout);
9809
+ const print = (line) => {
9810
+ if (json) {
9811
+ deps.out(JSON.stringify(line));
9812
+ return;
9813
+ }
9814
+ const text = options.timestamps && line.time ? `${dim(line.time, color)} ${line.text}` : line.text;
9815
+ (line.stream === "stderr" ? deps.err : deps.out)(text);
9816
+ };
9817
+ let result = await deps.request(input);
9818
+ if (!result.running) {
9819
+ deps.err("The sandbox is stopped: it has no logs to show.");
9820
+ return 1;
9821
+ }
9822
+ let last = null;
9823
+ for (;; ) {
9824
+ for (const line of result.lines) {
9825
+ print(line);
9826
+ if (line.time)
9827
+ last = line.time;
9828
+ }
9829
+ if (!options.follow) {
9830
+ if (result.truncated && json) {
9831
+ deps.out(JSON.stringify({ truncated: true }));
9832
+ } else if (result.truncated) {
9833
+ deps.err(dim("(older lines left out: use --tail for more)", colorEnabled(process.stderr)));
9834
+ }
9835
+ return 0;
9836
+ }
9837
+ if (!result.truncated)
9838
+ await deps.sleep(FOLLOW_INTERVAL_MS);
9839
+ if (deps.stopped())
9840
+ return 0;
9841
+ const { tail: _tail, ...rest } = input;
9842
+ try {
9843
+ result = await deps.request(last ? { ...rest, after: last } : input);
9844
+ } catch (error) {
9845
+ deps.err(red(error instanceof Error ? error.message : String(error), colorEnabled(process.stderr)));
9846
+ return 1;
9847
+ }
9848
+ if (!result.running) {
9849
+ deps.err("The sandbox stopped.");
9850
+ return 0;
9851
+ }
9852
+ }
9853
+ }
9854
+
9855
+ // src/container-picker.ts
9856
+ import { createInterface } from "node:readline/promises";
9857
+ function notRunningContainerOf(error) {
9858
+ if (!(error instanceof ApiError) || !error.data || typeof error.data !== "object")
9859
+ return null;
9860
+ const { code, params } = error.data;
9861
+ if (code !== "container_not_running")
9862
+ return null;
9863
+ const container = params && typeof params === "object" ? params : {};
9864
+ return typeof container.container === "string" && container.container ? container.container : null;
9865
+ }
9866
+ function containerQuestionOf(error) {
9867
+ if (!(error instanceof ApiError) || !error.data || typeof error.data !== "object")
9868
+ return null;
9869
+ const { code, params } = error.data;
9870
+ if (code !== "container_choice_required" && code !== "container_default_missing")
9871
+ return null;
9872
+ const record = params && typeof params === "object" ? params : {};
9873
+ const running = typeof record.containers === "string" ? record.containers.split(",").map((name) => name.trim()).filter(Boolean) : [];
9874
+ if (running.length === 0)
9875
+ return null;
9876
+ return {
9877
+ running,
9878
+ ...code === "container_default_missing" && typeof record.container === "string" ? { missingDefault: record.container } : {}
9879
+ };
9880
+ }
9881
+ var say = (line) => process.stderr.write(`${line}
9882
+ `);
9883
+ var chooseContainerOnTerminal = async ({ running, missingDefault }) => {
9884
+ if (missingDefault) {
9885
+ say(`
9886
+ The default container ${missingDefault} is gone.`);
9887
+ }
9888
+ say(missingDefault ? "Which container?" : `
9889
+ Several containers are running. Which one?`);
9890
+ running.forEach((name, index) => {
9891
+ say(` ${index + 1}. ${name}`);
9892
+ });
9893
+ const prompt = createInterface({ input: process.stdin, output: process.stderr });
9894
+ try {
9895
+ const answer = (await prompt.question("Number (Enter to cancel): ")).trim();
9896
+ const index = Number.parseInt(answer, 10) - 1;
9897
+ return Number.isInteger(index) && index >= 0 && index < running.length ? running[index] ?? null : null;
9898
+ } finally {
9899
+ prompt.close();
9900
+ }
9901
+ };
9902
+ var confirmDefaultOnTerminal = async (container) => {
9903
+ const prompt = createInterface({ input: process.stdin, output: process.stderr });
9904
+ try {
9905
+ const answer = (await prompt.question(`Make ${container} the default? [y/N] `)).trim();
9906
+ return /^y(es)?$/i.test(answer);
9907
+ } finally {
9908
+ prompt.close();
9909
+ }
9910
+ };
9911
+ var confirmStartOnTerminal = async (container) => {
9912
+ const prompt = createInterface({ input: process.stdin, output: process.stderr });
9913
+ try {
9914
+ const answer = (await prompt.question(`Container ${container} is not running. Start it? [Y/n] `)).trim();
9915
+ return answer === "" || /^y(es)?$/i.test(answer);
9916
+ } finally {
9917
+ prompt.close();
9918
+ }
9919
+ };
9920
+ function defaultContainerPickers() {
9921
+ return process.stdin.isTTY && process.stderr.isTTY ? {
9922
+ choose: chooseContainerOnTerminal,
9923
+ confirmDefault: confirmDefaultOnTerminal,
9924
+ confirmStart: confirmStartOnTerminal
9925
+ } : undefined;
9926
+ }
9927
+
9196
9928
  // src/progress.ts
9197
9929
  var PROGRESS_FRAMES = ["▂▄▆", "▄▆█", "▆█▆", "█▆▄", "▆▄▂", "▄▂▄"];
9198
9930
  var FRAME_MS = 120;
@@ -9293,10 +10025,10 @@ function symbolForStatus(status) {
9293
10025
  return "failed";
9294
10026
  if (/^(running|active|ready|deployed|published|online|verified|ok)$/.test(s))
9295
10027
  return "ready";
9296
- if (/^(stopped|deleted|unpublished|vanished|archived|revoked|closed|killed)$/.test(s)) {
10028
+ if (/^(stopped|deleted|unpublished|vanished|archived|revoked|closed|killed|exited|dead|paused)$/.test(s)) {
9297
10029
  return "stopped";
9298
10030
  }
9299
- if (/(pending|starting|spawning|stopping|building|progress|awaiting|queued|creating)/.test(s)) {
10031
+ if (/(pending|starting|spawning|stopping|building|progress|awaiting|queued|creating|restarting|removing)/.test(s)) {
9300
10032
  return "progress";
9301
10033
  }
9302
10034
  return "item";
@@ -9311,6 +10043,9 @@ function lineKeys(item) {
9311
10043
  const idKeys = "id" in item ? ID_KEYS.filter((key) => key !== "sandboxId") : ID_KEYS;
9312
10044
  return { id: firstKey(item, idKeys), label: firstKey(item, LABEL_KEYS) };
9313
10045
  }
10046
+ function shortId(id) {
10047
+ return /^[0-9a-f]{64}$/.test(id) ? id.slice(0, 12) : id;
10048
+ }
9314
10049
  function itemLine(item, color) {
9315
10050
  const keys = lineKeys(item);
9316
10051
  const id = keys.id ? item[keys.id] : undefined;
@@ -9319,7 +10054,7 @@ function itemLine(item, color) {
9319
10054
  const url = firstString(item, URL_KEYS);
9320
10055
  const parts = [label ?? id ?? "(unnamed)"];
9321
10056
  if (label && id && id !== label)
9322
- parts.push(dim(id, color));
10057
+ parts.push(dim(shortId(id), color));
9323
10058
  if (status)
9324
10059
  parts.push(status);
9325
10060
  if (url)
@@ -9552,7 +10287,7 @@ function buildToolInput(schema, rawArgs) {
9552
10287
  }
9553
10288
 
9554
10289
  // src/commands/invoke.ts
9555
- async function runToolInvoke(tool, rawArgs, log = console.log, error = printError, notice = printNotice) {
10290
+ async function runToolInvoke(tool, rawArgs, log = console.log, error = printError, notice = printNotice, pickers = defaultContainerPickers()) {
9556
10291
  let input;
9557
10292
  try {
9558
10293
  input = buildToolInput(tool.inputSchema, rawArgs);
@@ -9564,29 +10299,110 @@ async function runToolInvoke(tool, rawArgs, log = console.log, error = printErro
9564
10299
  error(err instanceof Error ? err.message : String(err));
9565
10300
  return 1;
9566
10301
  }
10302
+ if (tool.name === "sandboxes_containers_logs") {
10303
+ try {
10304
+ return await runContainerLogs(input, {
10305
+ follow: rawArgs.follow === true,
10306
+ timestamps: rawArgs.timestamps === true
10307
+ });
10308
+ } catch (err) {
10309
+ error(err instanceof Error ? err.message : `${tool.name} failed.`);
10310
+ return 1;
10311
+ }
10312
+ }
9567
10313
  try {
9568
- const request = () => authorizedRequest(`/api/v1/tools/${tool.name}`, {
10314
+ const send = () => authorizedRequest(`/api/v1/tools/${tool.name}`, {
9569
10315
  method: "POST",
9570
10316
  body: JSON.stringify(input)
9571
10317
  });
10318
+ const request = () => withContainerStart(input, send, rawArgs.start === true, pickers, notice);
9572
10319
  const waitMessage = PROGRESS_MESSAGES[tool.name];
9573
10320
  if (!waitMessage) {
9574
- const result = await request();
10321
+ const result = await withContainerPick(tool, input, request, pickers, notice);
9575
10322
  printResult(result, () => renderResult(result, colorEnabled(process.stdout)), log);
10323
+ stopNote(result, notice);
9576
10324
  return 0;
9577
10325
  }
9578
- const { value: result, elapsed } = await withProgress(waitMessage, request);
10326
+ const { value: result, elapsed } = await withContainerPick(tool, input, () => withProgress(waitMessage, request), pickers, notice);
9579
10327
  printResult(result, () => {
9580
10328
  const color = colorEnabled(process.stdout);
9581
10329
  const lines = renderResult(result, color);
9582
10330
  return bootLines(result, color).length > 0 ? lines : withElapsed(lines, elapsed);
9583
10331
  }, log);
10332
+ stopNote(result, notice);
9584
10333
  return 0;
9585
10334
  } catch (err) {
9586
10335
  error(err instanceof Error ? err.message : `${tool.name} failed.`);
9587
10336
  return 1;
9588
10337
  }
9589
10338
  }
10339
+ async function withContainerPick(tool, input, send, pickers, notice) {
10340
+ try {
10341
+ return await send();
10342
+ } catch (err) {
10343
+ const properties = tool.inputSchema.properties;
10344
+ const question = containerQuestionOf(err);
10345
+ if (!pickers || !question || !properties || !("container" in properties) || input.container) {
10346
+ throw err;
10347
+ }
10348
+ const container = await pickers.choose(question);
10349
+ if (!container)
10350
+ throw err;
10351
+ input.container = container;
10352
+ const result = await send();
10353
+ if (typeof input.sandboxId === "string" && await pickers.confirmDefault(container)) {
10354
+ try {
10355
+ await authorizedRequest("/api/v1/tools/sandboxes_default_container", {
10356
+ method: "POST",
10357
+ body: JSON.stringify({
10358
+ organization: input.organization,
10359
+ sandboxId: input.sandboxId,
10360
+ container
10361
+ })
10362
+ });
10363
+ notice(eventLine("done", `Default container: ${container}.`));
10364
+ } catch (err) {
10365
+ notice(eventLine("failed", `The default container was not set: ${err instanceof Error ? err.message : String(err)}`));
10366
+ }
10367
+ }
10368
+ return result;
10369
+ }
10370
+ }
10371
+ async function withContainerStart(input, send, start, pickers, notice) {
10372
+ try {
10373
+ return await send();
10374
+ } catch (err) {
10375
+ const container = notRunningContainerOf(err);
10376
+ if (!container || typeof input.sandboxId !== "string")
10377
+ throw err;
10378
+ if (!start && !(pickers && await pickers.confirmStart(container)))
10379
+ throw err;
10380
+ await authorizedRequest("/api/v1/tools/sandboxes_containers_start", {
10381
+ method: "POST",
10382
+ body: JSON.stringify({
10383
+ organization: input.organization,
10384
+ sandboxId: input.sandboxId,
10385
+ container
10386
+ })
10387
+ });
10388
+ notice(eventLine("done", `Started ${container}.`));
10389
+ return await send();
10390
+ }
10391
+ }
10392
+ function stopNote(result, notice) {
10393
+ if (!result || typeof result !== "object")
10394
+ return;
10395
+ const { action, note, container } = result;
10396
+ if (action !== "stop" || typeof container !== "string")
10397
+ return;
10398
+ if (note === "default") {
10399
+ notice(`${container} is the sandbox's default container: SSH, commands and files ask which container until it runs again.`);
10400
+ } else if (note === "last_running") {
10401
+ notice("No container is running now: SSH, commands and files have none to land in until one runs.");
10402
+ } else if (note === "sandbox_stopped") {
10403
+ notice(`The sandbox is stopped, so ${container} is too: nothing was done.`);
10404
+ }
10405
+ }
9590
10406
  var PROGRESS_MESSAGES = {
9591
10407
  sandboxes_create: "Creating sandbox…",
9592
10408
  sandboxes_wake: "Waking sandbox…",
@@ -9595,6 +10411,9 @@ var PROGRESS_MESSAGES = {
9595
10411
  sandboxes_resize: "Resizing sandbox…",
9596
10412
  sandboxes_resize_disk: "Resizing disk…",
9597
10413
  sandboxes_commands_run: "Running command…",
10414
+ sandboxes_containers_start: "Starting container…",
10415
+ sandboxes_containers_stop: "Stopping container…",
10416
+ sandboxes_containers_restart: "Restarting container…",
9598
10417
  snapshots_create: "Saving snapshot…",
9599
10418
  projects_redeploy: "Redeploying…",
9600
10419
  providers_verify_incus: "Checking the Incus host…"
@@ -9617,6 +10436,25 @@ function cliText(catalog, skills = BUILTIN_SKILL_NAMES) {
9617
10436
  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);
9618
10437
  };
9619
10438
  }
10439
+ function cliOnlyArgs(tool) {
10440
+ const schema = tool.inputSchema;
10441
+ const args = {};
10442
+ if (schema.properties && "container" in schema.properties && !schema.required?.includes("container")) {
10443
+ args.start = {
10444
+ type: "boolean",
10445
+ description: "Start the container first when it is not running, without asking."
10446
+ };
10447
+ }
10448
+ if (tool.name === "sandboxes_containers_logs") {
10449
+ args.follow = {
10450
+ type: "boolean",
10451
+ alias: "f",
10452
+ description: "Keep printing new lines until Ctrl-C."
10453
+ };
10454
+ args.timestamps = { type: "boolean", description: "Put each line's time in front of it." };
10455
+ }
10456
+ return args;
10457
+ }
9620
10458
  var generatedToolCommands = new WeakSet;
9621
10459
  function createToolCommands(catalog) {
9622
10460
  const commands = {};
@@ -9627,7 +10465,7 @@ function createToolCommands(catalog) {
9627
10465
  name: tool.cli?.at(-1) ?? tool.name,
9628
10466
  description: toCliText(tool.description)
9629
10467
  },
9630
- args: buildToolArgs(tool.inputSchema, toCliText, tool.cliArgs),
10468
+ args: { ...buildToolArgs(tool.inputSchema, toCliText, tool.cliArgs), ...cliOnlyArgs(tool) },
9631
10469
  async run({ args }) {
9632
10470
  process.exitCode = await runToolInvoke(tool, args);
9633
10471
  }
@@ -9642,7 +10480,7 @@ function createToolCommands(catalog) {
9642
10480
  import { hostname } from "node:os";
9643
10481
 
9644
10482
  // src/commands/login-organization.ts
9645
- import { createInterface } from "node:readline/promises";
10483
+ import { createInterface as createInterface2 } from "node:readline/promises";
9646
10484
  function label(organization) {
9647
10485
  return `${organization.name} (${organization.slug ?? organization.id})`;
9648
10486
  }
@@ -9652,7 +10490,7 @@ Which organization should commands use by default?`);
9652
10490
  organizations.forEach((organization, index) => {
9653
10491
  console.log(` ${index + 1}. ${label(organization)}`);
9654
10492
  });
9655
- const prompt = createInterface({ input: process.stdin, output: process.stdout });
10493
+ const prompt = createInterface2({ input: process.stdin, output: process.stdout });
9656
10494
  try {
9657
10495
  const answer = (await prompt.question(`Number (Enter to skip): `)).trim();
9658
10496
  const index = Number.parseInt(answer, 10) - 1;
@@ -9927,14 +10765,21 @@ function renderConfig(input) {
9927
10765
  if (!isDnsZone(zone))
9928
10766
  throw new Error(`The server sent a zone that is not a DNS name: ${zone}`);
9929
10767
  }
10768
+ if (!device.address || !isIPv6Address(device.address)) {
10769
+ throw new Error(`The server sent no IPv6 address for this device: ${device.address}`);
10770
+ }
9930
10771
  if (!isIPv6Address(network.gatewayAddress)) {
9931
10772
  throw new Error(`The server sent a gateway that is not an IPv6 address: ${network.gatewayAddress}`);
9932
10773
  }
10774
+ const ipv4 = device.address4 && network.ingress4 ? { address: device.address4, ingress: network.ingress4 } : null;
10775
+ if (ipv4 && (!isIPv4Address(ipv4.address) || !isIPv4Address(ipv4.ingress))) {
10776
+ throw new Error(`The server sent an IPv4 address that is not one: ${ipv4.address}, ${ipv4.ingress}`);
10777
+ }
9933
10778
  const lines = [
9934
10779
  "# Written by `ghostdock network connect`. Run it again rather than editing this file.",
9935
10780
  "[Interface]",
9936
10781
  `PrivateKey = ${input.privateKey}`,
9937
- `Address = ${device.address}/128`,
10782
+ `Address = ${device.address}/128${ipv4 ? `, ${ipv4.address}/32` : ""}`,
9938
10783
  `MTU = ${network.mtu}`
9939
10784
  ];
9940
10785
  if (input.dns === "macos-resolver") {
@@ -9946,7 +10791,7 @@ function renderConfig(input) {
9946
10791
  const domains = zones.map((zone) => `'~${zone}'`).join(" ");
9947
10792
  lines.push(`PostUp = resolvectl dns %i ${network.gatewayAddress} && resolvectl domain %i ${domains}`);
9948
10793
  }
9949
- lines.push("", "[Peer]", `PublicKey = ${gateway.publicKey}`, `Endpoint = ${gateway.endpoint}`, `AllowedIPs = ${network.platformPrefix}`, "PersistentKeepalive = 25", "");
10794
+ lines.push("", "[Peer]", `PublicKey = ${gateway.publicKey}`, `Endpoint = ${gateway.endpoint}`, `AllowedIPs = ${network.prefix}${ipv4 ? `, ${ipv4.ingress}/32` : ""}`, "PersistentKeepalive = 25", "");
9950
10795
  return lines.join(`
9951
10796
  `);
9952
10797
  }
@@ -9955,6 +10800,26 @@ function isDnsZone(zone) {
9955
10800
  const labels = zone.split(".");
9956
10801
  return zone.length <= 253 && labels.length >= 2 && labels.every((label) => DNS_LABEL.test(label));
9957
10802
  }
10803
+ function isIPv4Address(text) {
10804
+ 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);
10805
+ }
10806
+ function ipv4RouteOwner(platform, output) {
10807
+ if (platform === "darwin") {
10808
+ if (/^\s*destination: default$/m.test(output))
10809
+ return null;
10810
+ return /^\s*interface: (\S+)$/m.exec(output)?.[1] ?? null;
10811
+ }
10812
+ for (const line of output.split(`
10813
+ `).map((l) => l.trim())) {
10814
+ if (!line || /^(default|broadcast)\b/.test(line))
10815
+ continue;
10816
+ return /\bdev (\S+)/.exec(line)?.[1] ?? "another route";
10817
+ }
10818
+ return null;
10819
+ }
10820
+ function ipv4RouteCommand(platform, address) {
10821
+ return platform === "darwin" ? ["route", ["-n", "get", address]] : ["ip", ["-4", "route", "show", "table", "all", "match", address]];
10822
+ }
9958
10823
  function isIPv6Address(text) {
9959
10824
  return /^[0-9a-f:]{2,39}$/.test(text) && text.includes(":");
9960
10825
  }
@@ -9995,6 +10860,11 @@ function defaultDeps() {
9995
10860
  child.on("error", reject);
9996
10861
  child.on("close", (code) => resolve(code ?? 1));
9997
10862
  }),
10863
+ output: (command, args) => {
10864
+ const result = spawnSync(command, args, { encoding: "utf8" });
10865
+ return result.status === 0 ? result.stdout : "";
10866
+ },
10867
+ platform: process.platform,
9998
10868
  has: (command) => spawnSync("sh", ["-c", `command -v ${command}`]).status === 0,
9999
10869
  dir: networkDir(),
10000
10870
  up: () => tunnelUp(),
@@ -10054,7 +10924,7 @@ async function runNetworkConnect(args = {}, deps = defaultDeps()) {
10054
10924
  await writeDeviceState({
10055
10925
  baseUrl: getBaseUrl(),
10056
10926
  deviceId: registered.device.id,
10057
- address: registered.device.address,
10927
+ address: registered.device.address ?? "",
10058
10928
  gatewayProvider: registered.provider
10059
10929
  }, deps.dir);
10060
10930
  const wasUp = deps.up();
@@ -10065,6 +10935,7 @@ async function runNetworkConnect(args = {}, deps = defaultDeps()) {
10065
10935
  return 1;
10066
10936
  }
10067
10937
  }
10938
+ const ipv4Clashes = ipv4Clashes4(registered, deps);
10068
10939
  const code = await wgQuick(deps, "up");
10069
10940
  if (code !== 0) {
10070
10941
  deps.error(`wg-quick exited ${code}. The config is in ${configPath(deps.dir)}.`);
@@ -10086,6 +10957,12 @@ async function runNetworkConnect(args = {}, deps = defaultDeps()) {
10086
10957
  ` Another organization: ${getCliName()} network connect --org <org>.`
10087
10958
  ] : [],
10088
10959
  ...network.netZone && pinned && shortNameOrganizationChanged ? clearingNotice(network.netCookieSite) : [],
10960
+ ...network.ingress4 && !device.address4 ? [
10961
+ " No IPv4 address was free on this provider's host, so browser names work over IPv6 only:",
10962
+ " Chrome on a network without IPv6 cannot open them on this machine. Firefox and Safari",
10963
+ " can. Run connect again later: an address frees up when another device goes."
10964
+ ] : [],
10965
+ ...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
10966
  ...dns === "none" ? [
10090
10967
  " No split DNS on this machine (no systemd-resolved): use the addresses from",
10091
10968
  ` \`${getCliName()} network ls\`.`
@@ -10094,6 +10971,17 @@ async function runNetworkConnect(args = {}, deps = defaultDeps()) {
10094
10971
  ], deps.log);
10095
10972
  return 0;
10096
10973
  }
10974
+ function ipv4Clashes4(registered, deps) {
10975
+ const addresses = [registered.network.ingress4, registered.device.address4].filter((a) => typeof a === "string" && isIPv4Address(a));
10976
+ const clashes = [];
10977
+ for (const address of addresses) {
10978
+ const [command, args] = ipv4RouteCommand(deps.platform, address);
10979
+ const owner = ipv4RouteOwner(deps.platform, deps.output(command, args));
10980
+ if (owner)
10981
+ clashes.push({ address, owner });
10982
+ }
10983
+ return clashes;
10984
+ }
10097
10985
  function clearingNotice(cookieSite) {
10098
10986
  const site = cookieSite?.site ?? "the net domain";
10099
10987
  return [
@@ -10210,7 +11098,7 @@ async function runNetworkLs(args, deps = defaultDeps()) {
10210
11098
  const note = unreachableNote(s, connected);
10211
11099
  lines.push([
10212
11100
  ` ${s.name.padEnd(width)}`,
10213
- s.address.padEnd(28),
11101
+ (s.address ?? "-").padEnd(28),
10214
11102
  s.canonicalName.padEnd(canonicalWidth),
10215
11103
  ...s.httpsName ? [`https://${s.httpsName}`.padEnd(httpsWidth)] : [],
10216
11104
  ...s.shortName ? [`https://${s.shortName}`.padEnd(shortWidth)] : [],
@@ -11961,6 +12849,7 @@ var GROUP_DESCRIPTIONS = {
11961
12849
  providers: "Sandbox providers of an organization.",
11962
12850
  sandboxes: "Create, run and manage sandboxes.",
11963
12851
  "sandboxes commands": "Run commands in a sandbox and read their output.",
12852
+ "sandboxes containers": "Containers of a sandbox’s Docker engine.",
11964
12853
  "sandboxes env": "Environment of one sandbox.",
11965
12854
  "sandboxes files": "Files inside a sandbox.",
11966
12855
  "sandboxes mcps": "MCP endpoints a sandbox exposes.",