@miosa/cli 1.0.95 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.md +83 -17
  2. package/dist/bin/miosa.js +5 -1
  3. package/dist/bin/miosa.js.map +1 -1
  4. package/dist/client.d.ts +1 -1
  5. package/dist/client.d.ts.map +1 -1
  6. package/dist/client.js +6 -5
  7. package/dist/client.js.map +1 -1
  8. package/dist/commands/cloud.d.ts +3 -0
  9. package/dist/commands/cloud.d.ts.map +1 -0
  10. package/dist/commands/cloud.js +192 -0
  11. package/dist/commands/cloud.js.map +1 -0
  12. package/dist/commands/connect.d.ts +4 -1
  13. package/dist/commands/connect.d.ts.map +1 -1
  14. package/dist/commands/connect.js +46 -15
  15. package/dist/commands/connect.js.map +1 -1
  16. package/dist/commands/databases.d.ts.map +1 -1
  17. package/dist/commands/databases.js +3 -2
  18. package/dist/commands/databases.js.map +1 -1
  19. package/dist/commands/db.js +1 -1
  20. package/dist/commands/desktop.d.ts.map +1 -1
  21. package/dist/commands/desktop.js +2 -1
  22. package/dist/commands/desktop.js.map +1 -1
  23. package/dist/commands/hosts.d.ts +5 -1
  24. package/dist/commands/hosts.d.ts.map +1 -1
  25. package/dist/commands/hosts.js +5 -4
  26. package/dist/commands/hosts.js.map +1 -1
  27. package/dist/commands/mcp.d.ts +25 -0
  28. package/dist/commands/mcp.d.ts.map +1 -1
  29. package/dist/commands/mcp.js +26 -11
  30. package/dist/commands/mcp.js.map +1 -1
  31. package/dist/commands/opencomputers.d.ts +10 -0
  32. package/dist/commands/opencomputers.d.ts.map +1 -0
  33. package/dist/commands/opencomputers.js +25 -0
  34. package/dist/commands/opencomputers.js.map +1 -0
  35. package/dist/commands/sandbox.d.ts.map +1 -1
  36. package/dist/commands/sandbox.js +84 -22
  37. package/dist/commands/sandbox.js.map +1 -1
  38. package/dist/commands/shell.d.ts.map +1 -1
  39. package/dist/commands/shell.js +3 -2
  40. package/dist/commands/shell.js.map +1 -1
  41. package/dist/commands/storage.d.ts.map +1 -1
  42. package/dist/commands/storage.js +3 -2
  43. package/dist/commands/storage.js.map +1 -1
  44. package/dist/commands/templates.d.ts.map +1 -1
  45. package/dist/commands/templates.js +94 -0
  46. package/dist/commands/templates.js.map +1 -1
  47. package/dist/version.d.ts +3 -0
  48. package/dist/version.d.ts.map +1 -0
  49. package/dist/version.js +5 -0
  50. package/dist/version.js.map +1 -0
  51. package/package.json +4 -3
@@ -23,6 +23,13 @@ import { registerSandboxDevCommands, runFullSandboxDoctor, } from "./sandbox-dev
23
23
  const DEFAULT_INTERACTIVE_SANDBOX_TIMEOUT_SEC = 3_600;
24
24
  const DEFAULT_CREATE_WAIT_TIMEOUT_SEC = 120;
25
25
  const EXPIRING_SANDBOX_THRESHOLD_SEC = 5 * 60;
26
+ const SANDBOX_SIZE_CONTRACTS = {
27
+ xs: { cpu: 1, memory: 2_048, disk: 10_240 },
28
+ small: { cpu: 2, memory: 4_096, disk: 10_240 },
29
+ medium: { cpu: 4, memory: 8_192, disk: 20_480 },
30
+ large: { cpu: 8, memory: 16_384, disk: 40_960 },
31
+ xl: { cpu: 16, memory: 32_768, disk: 81_920 },
32
+ };
26
33
  export function register(program) {
27
34
  // -------------------------------------------------------------------------
28
35
  // sandbox / sandboxes command group — built manually to avoid subcommand
@@ -67,7 +74,7 @@ export function register(program) {
67
74
  { header: "NAME", key: "name" },
68
75
  {
69
76
  header: "STATUS",
70
- key: "status",
77
+ key: "state",
71
78
  color: (val) => statusColor(val.trim()),
72
79
  },
73
80
  {
@@ -116,7 +123,7 @@ export function register(program) {
116
123
  { label: "Name", value: str(sb["name"]) },
117
124
  {
118
125
  label: "Status",
119
- value: statusColor(str(sb["status"])),
126
+ value: statusColor(str(sb["state"])),
120
127
  },
121
128
  ];
122
129
  if (sb["template_id"]) {
@@ -174,10 +181,13 @@ export function register(program) {
174
181
  .description("Create a new Sandbox")
175
182
  .option("--template <template>", "Template / image ID (default: miosa-sandbox)")
176
183
  .option("--name <name>", "Human-readable name for the Sandbox")
184
+ .option("--size <size>", "Named size: xs, small, medium, large, or xl (default: small)", parseSandboxSize)
177
185
  .option("--cpu <n>", "vCPU count", parseIntegerOption)
178
186
  .option("--memory <size>", "Memory size, e.g. 4096mb or 4gb", parseSizeMb)
179
187
  .option("--disk <size>", "Disk size, e.g. 10240mb or 10gb", parseSizeMb)
180
188
  .option("--timeout <duration>", "Wall-clock timeout, e.g. 300s, 1h", parseDurationSec)
189
+ .option("--idle-timeout <duration>", "Idle timeout before pause; 0 disables it (default: 0)", parseDurationSec)
190
+ .option("--idempotency-key <key>", "Retry-safe create key retained by the service for 24 hours")
181
191
  .option("--publish-port <port>", "Expose this port after create", parseIntegerOption)
182
192
  .option("--wait", "Wait for sandbox running and published port readiness")
183
193
  .option("--probe-path <path>", "HTTP path to probe when --publish-port is set", "/")
@@ -217,19 +227,21 @@ Note:
217
227
  renderCreateSuccess(raw, Date.now() - t0);
218
228
  return;
219
229
  }
220
- const body = {};
221
- if (opts.template)
222
- body["template_id"] = opts.template;
230
+ const body = {
231
+ template_id: opts.template ?? "miosa-sandbox",
232
+ };
223
233
  if (opts.name)
224
234
  body["name"] = opts.name;
225
- if (opts.cpu != null)
226
- body["cpu_count"] = opts.cpu;
227
- if (opts.memory != null)
228
- body["memory_mb"] = opts.memory;
229
- if (opts.disk != null)
230
- body["disk_size_mb"] = opts.disk;
235
+ const resources = resolveSandboxResources(opts);
236
+ body["size"] = resources.size;
237
+ if (resources.legacy) {
238
+ body["cpu_count"] = resources.legacy.cpu;
239
+ body["memory_mb"] = resources.legacy.memory;
240
+ body["disk_size_mb"] = resources.legacy.disk;
241
+ }
231
242
  const timeoutSec = opts.timeout ?? DEFAULT_INTERACTIVE_SANDBOX_TIMEOUT_SEC;
232
243
  body["timeout_sec"] = timeoutSec;
244
+ body["idle_timeout_sec"] = opts.idleTimeout ?? 0;
233
245
  if (opts.source)
234
246
  body["source"] = opts.source;
235
247
  if (opts.revision)
@@ -271,7 +283,9 @@ Note:
271
283
  body["persistent"] = opts.nonPersistent ? false : true;
272
284
  if (opts.autoStart)
273
285
  body["auto_start"] = true;
274
- const raw = unwrap(await client().apiPost(apiPath("/sandboxes"), body));
286
+ const raw = unwrap(await client().apiPost(apiPath("/sandboxes"), body, opts.idempotencyKey
287
+ ? { "Idempotency-Key": opts.idempotencyKey }
288
+ : undefined));
275
289
  const sb = (raw ?? {});
276
290
  const id = String(sb["id"] ?? "");
277
291
  if (opts.publishPort != null && id) {
@@ -307,14 +321,16 @@ Note:
307
321
  // delete
308
322
  sandbox
309
323
  .command("delete <sandbox-id>")
310
- .description("Delete a Sandbox")
324
+ .description("Permanently delete a Sandbox (legacy API extension)")
325
+ .requiredOption("--force", "Confirm permanent filesystem deletion")
311
326
  .option("--json", "Output as JSON")
312
327
  .action((id, opts) => runAction(() => deleteAndPrint(`/sandboxes/${enc(id)}`, opts)));
313
328
  // destroy / rm — aliases for delete
314
329
  sandbox
315
330
  .command("destroy <sandbox-id>")
316
331
  .alias("rm")
317
- .description("Destroy a Sandbox (alias for delete)")
332
+ .description("Permanently destroy a Sandbox (legacy API extension)")
333
+ .requiredOption("--force", "Confirm permanent filesystem deletion")
318
334
  .option("--json", "Output as JSON")
319
335
  .action((id, opts) => runAction(() => deleteAndPrint(`/sandboxes/${enc(id)}`, opts)));
320
336
  // action — lifecycle operations: pause, resume
@@ -329,6 +345,11 @@ Note:
329
345
  }
330
346
  await postAndPrint(`/sandboxes/${enc(id)}/${enc(action)}`, opts, {});
331
347
  }));
348
+ sandbox
349
+ .command("pause <sandbox-id>")
350
+ .description("Pause a running persistent Sandbox and preserve its workspace")
351
+ .option("--json", "Output as JSON")
352
+ .action((id, opts) => runAction(() => postAndPrint(`/sandboxes/${enc(id)}/pause`, opts, {})));
332
353
  // stop — snapshot + pause (mirrors `box stop`)
333
354
  sandbox
334
355
  .command("stop <sandbox-id>")
@@ -336,7 +357,7 @@ Note:
336
357
  .option("--no-snapshot", "Deprecated compatibility flag; stop still preserves state server-side")
337
358
  .option("--json", "Output as JSON")
338
359
  .action((id, opts) => runAction(async () => {
339
- const stopped = unwrap(await client().apiPost(apiPath(`/sandboxes/${enc(id)}/stop`), {}));
360
+ const stopped = unwrap(await client().apiPost(apiPath(`/sandboxes/${enc(id)}/pause`), {}));
340
361
  if (isJsonMode(opts)) {
341
362
  console.log(JSON.stringify(stopped, null, 2));
342
363
  return;
@@ -357,19 +378,27 @@ Note:
357
378
  sandbox
358
379
  .command("resume <sandbox-id>")
359
380
  .description("Resume a paused Sandbox")
381
+ .option("--idempotency-key <key>", "Retry-safe lifecycle key sent as Idempotency-Key")
360
382
  .option("--json", "Output as JSON")
361
383
  .action((id, opts) => runAction(() => resumeSandboxAndPrint(id, opts)));
362
384
  // fork — clone from snapshot in one call (mirrors `box fork`)
363
385
  sandbox
364
386
  .command("fork <sandbox-id>")
365
- .description("Clone (fork) a Sandbox from its current state")
366
- .option("--name <name>", "Optional name for the forked Sandbox")
387
+ .description("Snapshot and fork a running Sandbox into a new Sandbox")
388
+ .option("--template <template>", "Template or immutable image override")
389
+ .option("--timeout <duration>", "Fork timeout", parseDurationSec)
390
+ .option("--idempotency-key <key>", "Retry-safe fork key sent as Idempotency-Key")
367
391
  .option("--json", "Output as JSON")
368
392
  .action((id, opts) => runAction(async () => {
369
393
  const body = {};
370
- if (opts.name)
371
- body["name"] = opts.name;
372
- await postAndPrint(`/sandboxes/${enc(id)}/fork`, opts, body);
394
+ if (opts.template)
395
+ body["template_id"] = opts.template;
396
+ if (opts.timeout != null)
397
+ body["timeout_sec"] = opts.timeout;
398
+ const result = unwrap(await client().apiPost(apiPath(`/sandboxes/${enc(id)}/fork`), body, opts.idempotencyKey
399
+ ? { "Idempotency-Key": opts.idempotencyKey }
400
+ : undefined));
401
+ printValue(result, opts);
373
402
  }));
374
403
  // desktop — open the Sandbox's web desktop URL (mirrors `box desktop`).
375
404
  // Sandboxes are headless by default; only templates that expose a desktop
@@ -2293,7 +2322,7 @@ function parsePublishDatabase(value) {
2293
2322
  normalized === "postgresql" ||
2294
2323
  normalized === "create:postgres" ||
2295
2324
  normalized === "create:postgresql") {
2296
- return { engine: "postgresql", engine_version: "15" };
2325
+ return { engine: "postgresql", engine_version: "16" };
2297
2326
  }
2298
2327
  if (normalized.startsWith("existing:")) {
2299
2328
  return { existing_database_id: value.slice("existing:".length) };
@@ -2603,7 +2632,10 @@ async function waitForSandboxRunning(c, sandboxId, timeoutSec) {
2603
2632
  }
2604
2633
  async function resumeSandboxAndPrint(sandboxId, opts) {
2605
2634
  try {
2606
- await postAndPrint(`/sandboxes/${enc(sandboxId)}/resume`, opts, {});
2635
+ const result = unwrap(await client().apiPost(apiPath(`/sandboxes/${enc(sandboxId)}/resume`), {}, opts.idempotencyKey
2636
+ ? { "Idempotency-Key": opts.idempotencyKey }
2637
+ : undefined));
2638
+ printValue(result, opts);
2607
2639
  }
2608
2640
  catch (err) {
2609
2641
  if (err instanceof ApiResponseError && err.code === "SANDBOX_NOT_PAUSED") {
@@ -3387,6 +3419,36 @@ function parseIntegerOption(value) {
3387
3419
  throw new UserError(`Invalid integer: ${value}`);
3388
3420
  return n;
3389
3421
  }
3422
+ function parseSandboxSize(value) {
3423
+ const size = String(value).trim().toLowerCase();
3424
+ if (size in SANDBOX_SIZE_CONTRACTS)
3425
+ return size;
3426
+ throw new UserError(`Invalid sandbox size: ${value}. Expected xs, small, medium, large, or xl.`);
3427
+ }
3428
+ function resolveSandboxResources(opts) {
3429
+ const legacyValues = [opts.cpu, opts.memory, opts.disk];
3430
+ const legacyCount = legacyValues.filter((value) => value != null).length;
3431
+ if (legacyCount === 0)
3432
+ return { size: opts.size ?? "small" };
3433
+ if (legacyCount !== legacyValues.length) {
3434
+ throw new UserError("Legacy resource overrides require --cpu, --memory, and --disk together. Prefer --size.");
3435
+ }
3436
+ const legacy = {
3437
+ cpu: opts.cpu,
3438
+ memory: opts.memory,
3439
+ disk: opts.disk,
3440
+ };
3441
+ const matchingSize = Object.entries(SANDBOX_SIZE_CONTRACTS).find(([, contract]) => contract.cpu === legacy.cpu &&
3442
+ contract.memory === legacy.memory &&
3443
+ contract.disk === legacy.disk)?.[0];
3444
+ if (!matchingSize) {
3445
+ throw new UserError("Legacy --cpu/--memory/--disk values must exactly match a named sandbox size. Prefer --size.");
3446
+ }
3447
+ if (opts.size && opts.size !== matchingSize) {
3448
+ throw new UserError(`Legacy resource overrides match ${matchingSize}, not requested size ${opts.size}.`);
3449
+ }
3450
+ return { size: matchingSize, legacy };
3451
+ }
3390
3452
  function parseSizeMb(value) {
3391
3453
  const match = String(value)
3392
3454
  .trim()