@kvbox/cli 0.5.9 → 0.5.10

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/kvbox.js CHANGED
@@ -147,7 +147,7 @@ async function spin(message, work, opts = {}) {
147
147
  }
148
148
 
149
149
  // src/program.ts
150
- import { Command as Command39 } from "commander";
150
+ import { Command as Command40 } from "commander";
151
151
 
152
152
  // ../shared/src/version.ts
153
153
  import { createRequire } from "module";
@@ -162,10 +162,10 @@ function versionOf(srcFileUrl, injected) {
162
162
  }
163
163
 
164
164
  // src/version.ts
165
- var VERSION = versionOf(import.meta.url, true ? "0.5.9" : void 0);
165
+ var VERSION = versionOf(import.meta.url, true ? "0.5.10" : void 0);
166
166
 
167
167
  // src/commands/vm/index.ts
168
- import { Command as Command15 } from "commander";
168
+ import { Command as Command16 } from "commander";
169
169
 
170
170
  // src/commands/vm/connect.ts
171
171
  import { spawnSync as spawnSync2 } from "child_process";
@@ -417,7 +417,9 @@ var MachineSchema = z2.object({
417
417
  */
418
418
  ssh: z2.object({ host: z2.string(), port: z2.number().int(), user: z2.string() }).optional(),
419
419
  /** Whether the owner slug is a user or an org. Only the control plane knows; it fills it in on `GET /machines`. */
420
- ownerKind: z2.enum(["user", "org"]).optional()
420
+ ownerKind: z2.enum(["user", "org"]).optional(),
421
+ /** Seconds without network traffic before the host hibernates the box (#47). Absent: never. */
422
+ autoHibernateSec: z2.number().int().positive().optional()
421
423
  });
422
424
  var CreateMachineRequest = z2.object({
423
425
  name: z2.string().optional(),
@@ -431,7 +433,9 @@ var CreateMachineRequest = z2.object({
431
433
  * its SSH CA as one `cert-authority …` line (#51). Written into the box's authorized_keys at every
432
434
  * boot (#49); a host older than that ignores them.
433
435
  */
434
- sshKeys: z2.array(z2.string()).optional()
436
+ sshKeys: z2.array(z2.string()).optional(),
437
+ /** Seconds without network traffic before the box is hibernated (#47); 0: never. Absent: the host's default. */
438
+ autoHibernateSec: z2.number().int().min(0).optional()
435
439
  });
436
440
  var SetSshKeysRequest = z2.object({
437
441
  sshKeys: z2.array(z2.string())
@@ -442,6 +446,10 @@ var RescaleMachineRequest = z2.object({
442
446
  memMib: z2.number().int().min(256).optional(),
443
447
  diskGib: z2.number().int().min(1).optional()
444
448
  });
449
+ var SetMachineRequest = z2.object({
450
+ /** Seconds without network traffic before the box is hibernated (#47); 0: never. */
451
+ autoHibernateSec: z2.number().int().min(0).optional()
452
+ });
445
453
  var SnapshotSchema = z2.object({
446
454
  name: z2.string(),
447
455
  owner: z2.string(),
@@ -494,7 +502,10 @@ var HealthSchema = z2.object({
494
502
  /** The public IP the relay owns for this host. */
495
503
  publicIp: z2.string(),
496
504
  /** The relay's WireGuard endpoint the tunnel dials. */
497
- endpoint: z2.string()
505
+ endpoint: z2.string(),
506
+ /** Times the server redialed the tunnel since it started, because the relay stopped answering. Older servers omit them. */
507
+ restarts: z2.number().int().optional(),
508
+ lastRestartAt: z2.string().nullable().optional()
498
509
  }).nullable().optional(),
499
510
  /** Boxes brought back at the server's last start; null when there were none or it is still at it. Older servers omit it. */
500
511
  recovery: RecoveryReportSchema.nullable().optional()
@@ -889,6 +900,9 @@ var ServerClient = class {
889
900
  rescaleMachine(name, req) {
890
901
  return this.call("POST", `/machines/${encodeURIComponent(name)}/rescale`, MachineSchema, req);
891
902
  }
903
+ setMachine(name, req) {
904
+ return this.call("POST", `/machines/${encodeURIComponent(name)}/set`, MachineSchema, req);
905
+ }
892
906
  /** `since`: an ISO date, already validated; the server counts from the box's creation without it. */
893
907
  machineUsage(name, since) {
894
908
  const query = since === void 0 ? "" : `?since=${encodeURIComponent(since)}`;
@@ -1015,6 +1029,9 @@ function sshArgs(ep, p = paths()) {
1015
1029
  ...hostKey,
1016
1030
  "-o",
1017
1031
  "LogLevel=ERROR",
1032
+ // A quiet open session still moves a few bytes a minute, so the host's auto-hibernate (#47) sees it.
1033
+ "-o",
1034
+ "ServerAliveInterval=60",
1018
1035
  "-o",
1019
1036
  "SetEnv=LC_ALL=C.UTF-8",
1020
1037
  `${ep.user}@${ep.host}`
@@ -1106,7 +1123,8 @@ async function serving(p, timeoutMs, check) {
1106
1123
 
1107
1124
  // src/commands/vm/new.ts
1108
1125
  import { Command, Option } from "commander";
1109
- var newCommand = new Command("new").description("boot a new box from the base image or a snapshot").argument("[name]", "box name (box1, box2, \u2026 when omitted)").addOption(new Option("-s, --size <size>", "box size (default: server config, s)").choices(SIZE_NAMES)).option("-f, --from <snapshot>", "start from a snapshot instead of the base image").option("--force", "skip the capacity guard").option("--json", "print the box as JSON").action(async (name, opts, cmd) => {
1126
+ var newCommand = new Command("new").description("boot a new box from the base image or a snapshot").argument("[name]", "box name (box1, box2, \u2026 when omitted)").addOption(new Option("-s, --size <size>", "box size (default: server config, s)").choices(SIZE_NAMES)).option("-f, --from <snapshot>", "start from a snapshot instead of the base image").option("--auto-hibernate <duration>", "hibernate the box after this long without network traffic (30m, 4h, 1d; off: never; default: server config, off)").option("--force", "skip the capacity guard").option("--json", "print the box as JSON").action(async (name, opts, cmd) => {
1127
+ const autoHibernateSec = opts.autoHibernate === void 0 ? void 0 : parseIdleTimeout(opts.autoHibernate);
1110
1128
  const client = await clientFor(cmd.optsWithGlobals());
1111
1129
  try {
1112
1130
  const what = opts.from !== void 0 ? `restoring ${name ?? "a new box"} from snapshot ${opts.from}\u2026` : `booting ${name ?? "a new box"}\u2026`;
@@ -1114,8 +1132,10 @@ var newCommand = new Command("new").description("boot a new box from the base im
1114
1132
  ...name !== void 0 ? { name } : {},
1115
1133
  ...opts.size !== void 0 ? { size: opts.size } : {},
1116
1134
  ...opts.from !== void 0 ? { from: opts.from } : {},
1135
+ ...autoHibernateSec !== void 0 ? { autoHibernateSec } : {},
1117
1136
  ...opts.force ? { force: true } : {}
1118
1137
  }));
1138
+ if (autoHibernateSec && !box.autoHibernateSec) warn(`box ${name ?? box.name}: its host runs a kvbox-server too old for auto-hibernate; it will not hibernate on its own`);
1119
1139
  if (opts.json) {
1120
1140
  json(box);
1121
1141
  return;
@@ -1126,6 +1146,7 @@ var newCommand = new Command("new").description("boot a new box from the base im
1126
1146
  info(`${color.green("\u2713")} ${describe(box, user)}`);
1127
1147
  if (opts.from !== void 0) info(` connect with: kvbox connect ${displayName(box, user)} (already up: everything that ran in the snapshot is running)`);
1128
1148
  else info(` connect with: kvbox connect ${displayName(box, user)} (SSH is up a couple of seconds after boot)`);
1149
+ if (box.autoHibernateSec) info(` it ${autoHibernateText(box.autoHibernateSec)}`);
1129
1150
  } finally {
1130
1151
  await client.close();
1131
1152
  }
@@ -1137,6 +1158,18 @@ function describe(box, user) {
1137
1158
  const from = box.source.startsWith("snapshot:") ? ` from snapshot ${shortRef(box.source.slice("snapshot:".length), box.owner)}` : "";
1138
1159
  return `box ${color.bold(displayName(box, user))} is ${box.status} at ${box.ip}${from}: ${box.vcpu} vCPU, ${gb(box.memMib * 1024 ** 2)} RAM, ${gb(box.diskBytes)} disk`;
1139
1160
  }
1161
+ function autoHibernateText(sec) {
1162
+ return sec ? `hibernates after ${duration(sec)} without network traffic (kvbox connect wakes it)` : "never hibernates on its own";
1163
+ }
1164
+ var UNIT_SEC = { m: 60, h: 3600, d: 86400 };
1165
+ function parseIdleTimeout(text) {
1166
+ const t = text.trim().toLowerCase();
1167
+ if (t === "off" || t === "never" || t === "0") return 0;
1168
+ if (!/^(\d+[mhd])+$/.test(t)) throw new KvboxError(`--auto-hibernate ${text}: give a duration like 30m, 4h or 1d, or off`);
1169
+ let sec = 0;
1170
+ for (const [, n, unit] of t.matchAll(/(\d+)([mhd])/g)) sec += Number(n) * UNIT_SEC[unit];
1171
+ return sec;
1172
+ }
1140
1173
 
1141
1174
  // src/commands/vm/connect.ts
1142
1175
  var SSH_FAILED = 255;
@@ -1412,9 +1445,29 @@ var rmCommand = new Command9("rm").description("destroy a box (its disk is delet
1412
1445
  }
1413
1446
  });
1414
1447
 
1415
- // src/commands/vm/start.ts
1448
+ // src/commands/vm/set.ts
1416
1449
  import { Command as Command10 } from "commander";
1417
- var startCommand = new Command10("start").description("boot a stopped box again (same name, IP and disk)").argument("<name>", "box name").option("--json", "print the box as JSON").action(async (name, opts, cmd) => {
1450
+ var setCommand = new Command10("set").description("change a box's settings, e.g. --auto-hibernate 30m (off: never)").argument("<name>", "box name").option("--auto-hibernate <duration>", "hibernate the box after this long without network traffic (30m, 4h, 1d; off: never)").option("--json", "print the box as JSON").action(async (name, opts, cmd) => {
1451
+ if (opts.autoHibernate === void 0) throw new KvboxError("nothing to change: give --auto-hibernate");
1452
+ const autoHibernateSec = parseIdleTimeout(opts.autoHibernate);
1453
+ const client = await clientFor(cmd.optsWithGlobals());
1454
+ try {
1455
+ const found = pickRef(await client.listMachines(), name, me(client));
1456
+ if (!found) throw new KvboxError(`box ${name}: not found (kvbox ls)`);
1457
+ const box = await spin(`updating ${name}\u2026`, client.setMachine(displayName(found, me(client)), { autoHibernateSec }));
1458
+ if (opts.json) {
1459
+ json(box);
1460
+ return;
1461
+ }
1462
+ info(`${color.green("\u2713")} box ${color.bold(name)}: ${autoHibernateText(box.autoHibernateSec)}`);
1463
+ } finally {
1464
+ await client.close();
1465
+ }
1466
+ });
1467
+
1468
+ // src/commands/vm/start.ts
1469
+ import { Command as Command11 } from "commander";
1470
+ var startCommand = new Command11("start").description("boot a stopped box again (same name, IP and disk)").argument("<name>", "box name").option("--json", "print the box as JSON").action(async (name, opts, cmd) => {
1418
1471
  const client = await clientFor(cmd.optsWithGlobals());
1419
1472
  try {
1420
1473
  const box = await spin(`booting ${name}\u2026`, client.startMachine(name));
@@ -1426,8 +1479,8 @@ var startCommand = new Command10("start").description("boot a stopped box again
1426
1479
  });
1427
1480
 
1428
1481
  // src/commands/vm/stop.ts
1429
- import { Command as Command11 } from "commander";
1430
- var stopCommand = new Command11("stop").description("shut a box down (its disk is kept; start it again with kvbox start)").argument("<name>", "box name").option("--json", "print the box as JSON").action(async (name, opts, cmd) => {
1482
+ import { Command as Command12 } from "commander";
1483
+ var stopCommand = new Command12("stop").description("shut a box down (its disk is kept; start it again with kvbox start)").argument("<name>", "box name").option("--json", "print the box as JSON").action(async (name, opts, cmd) => {
1431
1484
  const client = await clientFor(cmd.optsWithGlobals());
1432
1485
  try {
1433
1486
  const box = await spin(`shutting ${name} down\u2026`, client.stopMachine(name));
@@ -1439,8 +1492,8 @@ var stopCommand = new Command11("stop").description("shut a box down (its disk i
1439
1492
  });
1440
1493
 
1441
1494
  // src/commands/vm/update.ts
1442
- import { Command as Command12 } from "commander";
1443
- var updateCommand = new Command12("update").description("move a box onto the current base image (its files stay; a running box restarts)").argument("<name>", "box name").option("--json", "print the box as JSON").action(async (name, opts, cmd) => {
1495
+ import { Command as Command13 } from "commander";
1496
+ var updateCommand = new Command13("update").description("move a box onto the current base image (its files stay; a running box restarts)").argument("<name>", "box name").option("--json", "print the box as JSON").action(async (name, opts, cmd) => {
1444
1497
  const client = await clientFor(cmd.optsWithGlobals());
1445
1498
  try {
1446
1499
  const before = pickRef(await client.listMachines(), name, me(client));
@@ -1462,8 +1515,8 @@ var updateCommand = new Command12("update").description("move a box onto the cur
1462
1515
  });
1463
1516
 
1464
1517
  // src/commands/vm/usage.ts
1465
- import { Command as Command13 } from "commander";
1466
- var usageCommand = new Command13("usage").description("how long a box ran, slept and sat stopped, as vCPU-hours, RAM and disk GiB-hours (and cost, when the host prices them)").argument("<name>", "box name").option("--since <date>", "count from this date instead of the box's creation, e.g. 2026-10-01").option("--json", "print as JSON").action(async (name, opts, cmd) => {
1518
+ import { Command as Command14 } from "commander";
1519
+ var usageCommand = new Command14("usage").description("how long a box ran, slept and sat stopped, as vCPU-hours, RAM and disk GiB-hours (and cost, when the host prices them)").argument("<name>", "box name").option("--since <date>", "count from this date instead of the box's creation, e.g. 2026-10-01").option("--json", "print as JSON").action(async (name, opts, cmd) => {
1467
1520
  let since;
1468
1521
  if (opts.since !== void 0) {
1469
1522
  const t = Date.parse(opts.since);
@@ -1510,8 +1563,8 @@ function block(u) {
1510
1563
  }
1511
1564
 
1512
1565
  // src/commands/vm/wake.ts
1513
- import { Command as Command14 } from "commander";
1514
- var wakeCommand = new Command14("wake").description("continue a hibernated box where it left off (same name, IP and disk)").argument("<name>", "box name").option("--json", "print the box as JSON").action(async (name, opts, cmd) => {
1566
+ import { Command as Command15 } from "commander";
1567
+ var wakeCommand = new Command15("wake").description("continue a hibernated box where it left off (same name, IP and disk)").argument("<name>", "box name").option("--json", "print the box as JSON").action(async (name, opts, cmd) => {
1515
1568
  const client = await clientFor(cmd.optsWithGlobals());
1516
1569
  try {
1517
1570
  const box = await spin(`waking ${name} (loading its memory from disk)\u2026`, client.wakeMachine(name));
@@ -1523,15 +1576,15 @@ var wakeCommand = new Command14("wake").description("continue a hibernated box w
1523
1576
  });
1524
1577
 
1525
1578
  // src/commands/vm/index.ts
1526
- var vmCommand = new Command15("vm").enablePositionalOptions().description("manage boxes (each one is a Firecracker microVM)").addCommand(newCommand).addCommand(lsCommand).addCommand(connectCommand).addCommand(execCommand).addCommand(stopCommand).addCommand(startCommand).addCommand(pauseCommand).addCommand(resumeCommand).addCommand(hibernateCommand).addCommand(wakeCommand).addCommand(updateCommand).addCommand(rescaleCommand).addCommand(usageCommand).addCommand(rmCommand);
1579
+ var vmCommand = new Command16("vm").enablePositionalOptions().description("manage boxes (each one is a Firecracker microVM)").addCommand(newCommand).addCommand(lsCommand).addCommand(connectCommand).addCommand(execCommand).addCommand(stopCommand).addCommand(startCommand).addCommand(pauseCommand).addCommand(resumeCommand).addCommand(hibernateCommand).addCommand(wakeCommand).addCommand(updateCommand).addCommand(rescaleCommand).addCommand(setCommand).addCommand(usageCommand).addCommand(rmCommand);
1527
1580
  for (const cmd of vmCommand.commands) cmd.option("--org <slug>", "act in this org (- for your personal boxes)");
1528
1581
  var vmAliases = [newCommand, lsCommand, connectCommand, execCommand, stopCommand, startCommand, pauseCommand, resumeCommand, hibernateCommand, wakeCommand, rmCommand];
1529
1582
 
1530
1583
  // src/commands/server/index.ts
1531
- import { Command as Command23 } from "commander";
1584
+ import { Command as Command24 } from "commander";
1532
1585
 
1533
1586
  // src/commands/server/join.ts
1534
- import { Command as Command16 } from "commander";
1587
+ import { Command as Command17 } from "commander";
1535
1588
 
1536
1589
  // src/commands/server/daemon.ts
1537
1590
  import { spawn as spawn3 } from "child_process";
@@ -1585,7 +1638,7 @@ function runKvboxServer(args) {
1585
1638
  }
1586
1639
 
1587
1640
  // src/commands/server/join.ts
1588
- var joinCommand = new Command16("join").description("put this machine behind a control plane (needs a one-time token minted there) and restart its server").argument("<control-plane-url>", "the control plane, e.g. https://api.kvbox.sh").requiredOption("--token <token>", "one-time join token from the control plane").option("--advertise <host-or-ip>", "address the control plane reaches this machine at (default: its default-route address)").option("--name <name>", "how the control plane lists this machine (default: hostname)").action(async (url, opts) => {
1641
+ var joinCommand = new Command17("join").description("put this machine behind a control plane (needs a one-time token minted there) and restart its server").argument("<control-plane-url>", "the control plane, e.g. https://api.kvbox.sh").requiredOption("--token <token>", "one-time join token from the control plane").option("--advertise <host-or-ip>", "address the control plane reaches this machine at (default: its default-route address)").option("--name <name>", "how the control plane lists this machine (default: hostname)").action(async (url, opts) => {
1589
1642
  const args = ["join", url, "--token", opts.token];
1590
1643
  if (opts.advertise !== void 0) args.push("--advertise", opts.advertise);
1591
1644
  if (opts.name !== void 0) args.push("--name", opts.name);
@@ -1595,8 +1648,8 @@ var joinCommand = new Command16("join").description("put this machine behind a c
1595
1648
  });
1596
1649
 
1597
1650
  // src/commands/server/leave.ts
1598
- import { Command as Command17 } from "commander";
1599
- var leaveCommand = new Command17("leave").description("take this machine out from behind its control plane and restart its server").action(async () => {
1651
+ import { Command as Command18 } from "commander";
1652
+ var leaveCommand = new Command18("leave").description("take this machine out from behind its control plane and restart its server").action(async () => {
1600
1653
  const p = paths();
1601
1654
  for (const line of (await runKvboxServer(["leave"])).split("\n")) info(line);
1602
1655
  if (await runningPid(p) === null) return;
@@ -1605,14 +1658,14 @@ var leaveCommand = new Command17("leave").description("take this machine out fro
1605
1658
  });
1606
1659
 
1607
1660
  // src/commands/server/relay.ts
1608
- import { Command as Command18 } from "commander";
1609
- var relayCommand = new Command18("relay").description("reach the internet through a relay (paste what `kvbox-relay add` printed on the relay VM); `off` takes the tunnel down").argument("<join-string>", "the line `kvbox-relay add` printed, or `off`").action(async (joinString) => {
1661
+ import { Command as Command19 } from "commander";
1662
+ var relayCommand = new Command19("relay").description("reach the internet through a relay (paste what `kvbox-relay add` printed on the relay VM); `off` takes the tunnel down").argument("<join-string>", "the line `kvbox-relay add` printed, or `off`").action(async (joinString) => {
1610
1663
  for (const line of (await runKvboxServer(["relay", joinString])).split("\n")) info(line);
1611
1664
  });
1612
1665
 
1613
1666
  // src/commands/server/start.ts
1614
- import { Command as Command19 } from "commander";
1615
- var startCommand2 = new Command19("start").description("start the local kvbox-server in the background").action(async (_opts, cmd) => {
1667
+ import { Command as Command20 } from "commander";
1668
+ var startCommand2 = new Command20("start").description("start the local kvbox-server in the background").action(async (_opts, cmd) => {
1616
1669
  const client = await clientFor({ ...cmd.optsWithGlobals(), local: true }, { autoStart: false });
1617
1670
  try {
1618
1671
  const health = await client.health();
@@ -1628,8 +1681,8 @@ var startCommand2 = new Command19("start").description("start the local kvbox-se
1628
1681
  });
1629
1682
 
1630
1683
  // src/commands/server/status.ts
1631
- import { Command as Command20 } from "commander";
1632
- var statusCommand = new Command20("status").description("show whether this machine's server is running, its control plane and which base image new boxes get").option("--json", "print as JSON").action(async (opts, cmd) => {
1684
+ import { Command as Command21 } from "commander";
1685
+ var statusCommand = new Command21("status").description("show whether this machine's server is running, its control plane and which base image new boxes get").option("--json", "print as JSON").action(async (opts, cmd) => {
1633
1686
  const client = await clientFor({ ...cmd.optsWithGlobals(), local: true });
1634
1687
  try {
1635
1688
  const health = await client.health();
@@ -1646,7 +1699,12 @@ var statusCommand = new Command20("status").description("show whether this machi
1646
1699
  }
1647
1700
  if (health.controlPlane) info(`control plane: ${health.controlPlane.name} at ${health.controlPlane.url}, this host answers it on ${health.controlPlane.hostUrl}`);
1648
1701
  else info("control plane: none (kvbox server join <url> --token <token> puts this machine behind one)");
1649
- if (health.relay) info(`relay: ${health.relay.name} lends this machine ${health.relay.publicIp} (tunnel to ${health.relay.endpoint})`);
1702
+ if (health.relay) {
1703
+ info(`relay: ${health.relay.name} lends this machine ${health.relay.publicIp} (tunnel to ${health.relay.endpoint})`);
1704
+ if (health.relay.restarts && health.relay.lastRestartAt) {
1705
+ info(`the tunnel stopped carrying traffic and was redialed ${health.relay.restarts} time(s) since the server started, last at ${health.relay.lastRestartAt}`);
1706
+ }
1707
+ }
1650
1708
  if (health.base === null) info("base image: none usable yet (run install-server.sh)");
1651
1709
  else info(`base image: ${health.base} (what kvbox new uses; kvbox vm update <box> moves an older box onto it)`);
1652
1710
  }
@@ -1656,8 +1714,8 @@ var statusCommand = new Command20("status").description("show whether this machi
1656
1714
  });
1657
1715
 
1658
1716
  // src/commands/server/stop.ts
1659
- import { Command as Command21 } from "commander";
1660
- var stopCommand2 = new Command21("stop").description("stop the local server (running boxes are not affected)").action(async () => {
1717
+ import { Command as Command22 } from "commander";
1718
+ var stopCommand2 = new Command22("stop").description("stop the local server (running boxes are not affected)").action(async () => {
1661
1719
  const pid = await runningPid(paths());
1662
1720
  if (pid === null) throw new KvboxError("kvbox-server is not running");
1663
1721
  await stopLocalServer(pid);
@@ -1665,8 +1723,8 @@ var stopCommand2 = new Command21("stop").description("stop the local server (run
1665
1723
  });
1666
1724
 
1667
1725
  // src/commands/server/trust-ca.ts
1668
- import { Command as Command22 } from "commander";
1669
- var trustCaCommand = new Command22("trust-ca").description("make every cloud box on this machine trust a new SSH CA (after `ca rotate` on the control plane)").argument("[public-key]", "the CA public key (or its cert-authority line); or --from-cloud").option("--from-cloud", "ask the control plane this host joined for its current CA, show the fingerprint, then ask").option("-y, --yes", "do not ask for confirmation").option("--json", "print the report as JSON").action(async (publicKey, opts, cmd) => {
1726
+ import { Command as Command23 } from "commander";
1727
+ var trustCaCommand = new Command23("trust-ca").description("make every cloud box on this machine trust a new SSH CA (after `ca rotate` on the control plane)").argument("[public-key]", "the CA public key (or its cert-authority line); or --from-cloud").option("--from-cloud", "ask the control plane this host joined for its current CA, show the fingerprint, then ask").option("-y, --yes", "do not ask for confirmation").option("--json", "print the report as JSON").action(async (publicKey, opts, cmd) => {
1670
1728
  if (publicKey === void 0 && !opts.fromCloud) throw new KvboxError("give the CA public key, or --from-cloud to fetch it from the control plane this host joined");
1671
1729
  if (publicKey !== void 0 && opts.fromCloud) throw new KvboxError("either a public key or --from-cloud, not both");
1672
1730
  const client = await clientFor({ ...cmd.optsWithGlobals(), local: true });
@@ -1704,7 +1762,7 @@ function describeReport(report) {
1704
1762
  }
1705
1763
 
1706
1764
  // src/commands/server/index.ts
1707
- var serverCommand = new Command23("server").description("run or manage the kvbox server").addCommand(startCommand2).addCommand(statusCommand).addCommand(stopCommand2).addCommand(joinCommand).addCommand(leaveCommand).addCommand(relayCommand).addCommand(trustCaCommand);
1765
+ var serverCommand = new Command24("server").description("run or manage the kvbox server").addCommand(startCommand2).addCommand(statusCommand).addCommand(stopCommand2).addCommand(joinCommand).addCommand(leaveCommand).addCommand(relayCommand).addCommand(trustCaCommand);
1708
1766
 
1709
1767
  // src/commands/login.ts
1710
1768
  import { spawnSync as spawnSync3 } from "child_process";
@@ -1712,8 +1770,8 @@ import { mkdir as mkdir3, readFile as readFile5, writeFile as writeFile3 } from
1712
1770
  import { hostname as osHostname } from "os";
1713
1771
  import { createInterface } from "readline/promises";
1714
1772
  import { Writable } from "stream";
1715
- import { Command as Command24 } from "commander";
1716
- var loginCommand = new Command24("login").description("log in to the kvbox cloud (or your own control plane) and target it from now on").argument("[url]", `control plane URL (default: ${DEFAULT_CLOUD_URL})`).option("-e, --email <email>", "account email (prompted otherwise)").option("--password-stdin", "read the password from the first line of stdin (for scripts)").action(async (urlArg, opts) => {
1773
+ import { Command as Command25 } from "commander";
1774
+ var loginCommand = new Command25("login").description("log in to the kvbox cloud (or your own control plane) and target it from now on").argument("[url]", `control plane URL (default: ${DEFAULT_CLOUD_URL})`).option("-e, --email <email>", "account email (prompted otherwise)").option("--password-stdin", "read the password from the first line of stdin (for scripts)").action(async (urlArg, opts) => {
1717
1775
  const url = normalizeUrl(urlArg ?? DEFAULT_CLOUD_URL);
1718
1776
  const creds = await readCredentials(opts);
1719
1777
  const session = await runLogin(url, creds, paths());
@@ -1811,8 +1869,8 @@ async function firstLine(stream) {
1811
1869
  }
1812
1870
 
1813
1871
  // src/commands/logout.ts
1814
- import { Command as Command25 } from "commander";
1815
- var logoutCommand = new Command25("logout").description("log out of the control plane; commands target this machine's kvbox-server again").action(async () => {
1872
+ import { Command as Command26 } from "commander";
1873
+ var logoutCommand = new Command26("logout").description("log out of the control plane; commands target this machine's kvbox-server again").action(async () => {
1816
1874
  const p = paths();
1817
1875
  const session = await readSession(p).catch(() => null);
1818
1876
  switch (await runLogout(p)) {
@@ -1849,8 +1907,8 @@ async function runLogout(p = paths()) {
1849
1907
  }
1850
1908
 
1851
1909
  // src/commands/whoami.ts
1852
- import { Command as Command26 } from "commander";
1853
- var whoamiCommand = new Command26("whoami").description("show which account and control plane commands target (or local mode)").option("--json", "print as JSON").action(async (opts) => {
1910
+ import { Command as Command27 } from "commander";
1911
+ var whoamiCommand = new Command27("whoami").description("show which account and control plane commands target (or local mode)").option("--json", "print as JSON").action(async (opts) => {
1854
1912
  const session = await readSession();
1855
1913
  if (opts.json) {
1856
1914
  json(session ? { user: session.user, url: session.url, sshCertExpires: session.sshCertExpires ?? null } : null);
@@ -1866,11 +1924,11 @@ function describeSession(session) {
1866
1924
  }
1867
1925
 
1868
1926
  // src/commands/snapshots/index.ts
1869
- import { Command as Command30 } from "commander";
1927
+ import { Command as Command31 } from "commander";
1870
1928
 
1871
1929
  // src/commands/snapshots/ls.ts
1872
- import { Command as Command27, Option as Option4 } from "commander";
1873
- var lsCommand2 = new Command27("ls").description("list snapshots (yours and your orgs'; --org <slug> for one org)").addOption(new Option4("-a, --all", "kept for older scripts: the listing covers everything you can see anyway").hideHelp()).option("--json", "print as JSON").action(async (opts, cmd) => {
1930
+ import { Command as Command28, Option as Option4 } from "commander";
1931
+ var lsCommand2 = new Command28("ls").description("list snapshots (yours and your orgs'; --org <slug> for one org)").addOption(new Option4("-a, --all", "kept for older scripts: the listing covers everything you can see anyway").hideHelp()).option("--json", "print as JSON").action(async (opts, cmd) => {
1874
1932
  const client = await clientFor(cmd.optsWithGlobals());
1875
1933
  try {
1876
1934
  const cloud = client.target.kind === "cloud";
@@ -1901,8 +1959,8 @@ var lsCommand2 = new Command27("ls").description("list snapshots (yours and your
1901
1959
  });
1902
1960
 
1903
1961
  // src/commands/snapshots/new.ts
1904
- import { Command as Command28 } from "commander";
1905
- var newCommand2 = new Command28("new").description("freeze a running box (memory + disk) into a snapshot; the box keeps running").argument("<box>", "box name").argument("<name>", "snapshot name").option("--json", "print the snapshot as JSON").action(async (box, name, opts, cmd) => {
1962
+ import { Command as Command29 } from "commander";
1963
+ var newCommand2 = new Command29("new").description("freeze a running box (memory + disk) into a snapshot; the box keeps running").argument("<box>", "box name").argument("<name>", "snapshot name").option("--json", "print the snapshot as JSON").action(async (box, name, opts, cmd) => {
1906
1964
  const client = await clientFor(cmd.optsWithGlobals());
1907
1965
  try {
1908
1966
  const snap = await spin(`saving snapshot ${name} from box ${box} (memory + disk)\u2026`, client.createSnapshot({ box, name }));
@@ -1914,8 +1972,8 @@ var newCommand2 = new Command28("new").description("freeze a running box (memory
1914
1972
  });
1915
1973
 
1916
1974
  // src/commands/snapshots/rm.ts
1917
- import { Command as Command29 } from "commander";
1918
- var rmCommand2 = new Command29("rm").description("delete a snapshot (boxes made from it are not affected)").argument("<name>", "snapshot name").option("-y, --yes", "do not ask for confirmation").action(async (name, opts, cmd) => {
1975
+ import { Command as Command30 } from "commander";
1976
+ var rmCommand2 = new Command30("rm").description("delete a snapshot (boxes made from it are not affected)").argument("<name>", "snapshot name").option("-y, --yes", "do not ask for confirmation").action(async (name, opts, cmd) => {
1919
1977
  if (!await confirm(`Delete snapshot ${name}?`, opts.yes ?? false)) {
1920
1978
  info("cancelled");
1921
1979
  process.exitCode = 1;
@@ -1931,14 +1989,14 @@ var rmCommand2 = new Command29("rm").description("delete a snapshot (boxes made
1931
1989
  });
1932
1990
 
1933
1991
  // src/commands/snapshots/index.ts
1934
- var snapshotsCommand = new Command30("snapshots").aliases(["snapshot", "snap"]).description("freeze running boxes into snapshots and manage them").addCommand(newCommand2).addCommand(lsCommand2).addCommand(rmCommand2);
1992
+ var snapshotsCommand = new Command31("snapshots").aliases(["snapshot", "snap"]).description("freeze running boxes into snapshots and manage them").addCommand(newCommand2).addCommand(lsCommand2).addCommand(rmCommand2);
1935
1993
  for (const cmd of snapshotsCommand.commands) cmd.option("--org <slug>", "act in this org (- for your personal boxes)");
1936
1994
 
1937
1995
  // src/commands/org/index.ts
1938
- import { Command as Command36 } from "commander";
1996
+ import { Command as Command37 } from "commander";
1939
1997
 
1940
1998
  // src/commands/org/ls.ts
1941
- import { Command as Command31 } from "commander";
1999
+ import { Command as Command32 } from "commander";
1942
2000
 
1943
2001
  // src/commands/org/context.ts
1944
2002
  var NOT_LOGGED_IN = "orgs live on the control plane: kvbox login first";
@@ -1958,7 +2016,7 @@ function orgFor(opts, target) {
1958
2016
  }
1959
2017
 
1960
2018
  // src/commands/org/ls.ts
1961
- var lsCommand3 = new Command31("ls").description("list the orgs you belong to and your role in each").option("--json", "print as JSON").action(async (opts, cmd) => {
2019
+ var lsCommand3 = new Command32("ls").description("list the orgs you belong to and your role in each").option("--json", "print as JSON").action(async (opts, cmd) => {
1962
2020
  const client = await cloudClientFor(cmd.optsWithGlobals());
1963
2021
  try {
1964
2022
  const orgs = await client.listOrgs();
@@ -1980,8 +2038,8 @@ function orgRows(orgs) {
1980
2038
  }
1981
2039
 
1982
2040
  // src/commands/org/members.ts
1983
- import { Command as Command32, Option as Option5 } from "commander";
1984
- var lsCommand4 = new Command32("ls").description("list an org's members and their roles").option("-o, --org <slug>", "the org (or --org before the command)").option("--json", "print as JSON").action(async (opts, cmd) => {
2041
+ import { Command as Command33, Option as Option5 } from "commander";
2042
+ var lsCommand4 = new Command33("ls").description("list an org's members and their roles").option("-o, --org <slug>", "the org (or --org before the command)").option("--json", "print as JSON").action(async (opts, cmd) => {
1985
2043
  const client = await cloudClientFor(cmd.optsWithGlobals());
1986
2044
  try {
1987
2045
  const org = await client.getOrg(orgFor(opts, client.target));
@@ -1995,7 +2053,7 @@ var lsCommand4 = new Command32("ls").description("list an org's members and thei
1995
2053
  await client.close();
1996
2054
  }
1997
2055
  });
1998
- var addCommand = new Command32("add").description("add a user to an org (admins and owners; only an owner can add an owner)").argument("<user>", "their username").addOption(new Option5("-r, --role <role>", "their role").choices(ORG_ROLES).default("member")).option("-o, --org <slug>", "the org (or --org before the command)").action(async (user, opts, cmd) => {
2056
+ var addCommand = new Command33("add").description("add a user to an org (admins and owners; only an owner can add an owner)").argument("<user>", "their username").addOption(new Option5("-r, --role <role>", "their role").choices(ORG_ROLES).default("member")).option("-o, --org <slug>", "the org (or --org before the command)").action(async (user, opts, cmd) => {
1999
2057
  const client = await cloudClientFor(cmd.optsWithGlobals());
2000
2058
  try {
2001
2059
  const slug = orgFor(opts, client.target);
@@ -2005,7 +2063,7 @@ var addCommand = new Command32("add").description("add a user to an org (admins
2005
2063
  await client.close();
2006
2064
  }
2007
2065
  });
2008
- var rmCommand3 = new Command32("rm").description("remove a member (or leave: remove yourself)").argument("<user>", "their username").option("-o, --org <slug>", "the org (or --org before the command)").option("-y, --yes", "do not ask for confirmation").action(async (user, opts, cmd) => {
2066
+ var rmCommand3 = new Command33("rm").description("remove a member (or leave: remove yourself)").argument("<user>", "their username").option("-o, --org <slug>", "the org (or --org before the command)").option("-y, --yes", "do not ask for confirmation").action(async (user, opts, cmd) => {
2009
2067
  const client = await cloudClientFor(cmd.optsWithGlobals());
2010
2068
  try {
2011
2069
  const slug = orgFor(opts, client.target);
@@ -2021,11 +2079,11 @@ var rmCommand3 = new Command32("rm").description("remove a member (or leave: rem
2021
2079
  await client.close();
2022
2080
  }
2023
2081
  });
2024
- var membersCommand = new Command32("members").description("who is in an org and what they may do").addCommand(lsCommand4).addCommand(addCommand).addCommand(rmCommand3);
2082
+ var membersCommand = new Command33("members").description("who is in an org and what they may do").addCommand(lsCommand4).addCommand(addCommand).addCommand(rmCommand3);
2025
2083
 
2026
2084
  // src/commands/org/new.ts
2027
- import { Command as Command33 } from "commander";
2028
- var newCommand3 = new Command33("new").description("create an org; you become its owner (the slug is permanent, the name is not)").argument("<slug>", "the org's slug: lowercase, like a username (acme)").option("-n, --name <name>", "display name (default: the slug)").option("--json", "print the org as JSON").action(async (slug, opts, cmd) => {
2085
+ import { Command as Command34 } from "commander";
2086
+ var newCommand3 = new Command34("new").description("create an org; you become its owner (the slug is permanent, the name is not)").argument("<slug>", "the org's slug: lowercase, like a username (acme)").option("-n, --name <name>", "display name (default: the slug)").option("--json", "print the org as JSON").action(async (slug, opts, cmd) => {
2029
2087
  const client = await cloudClientFor(cmd.optsWithGlobals());
2030
2088
  try {
2031
2089
  const org = await client.createOrg({ slug, name: opts.name ?? slug });
@@ -2041,8 +2099,8 @@ var newCommand3 = new Command33("new").description("create an org; you become it
2041
2099
  });
2042
2100
 
2043
2101
  // src/commands/org/rm.ts
2044
- import { Command as Command34 } from "commander";
2045
- var rmCommand4 = new Command34("rm").description("delete an org (owners only; remove its boxes and snapshots first)").argument("<slug>", "the org").option("-y, --yes", "do not ask for confirmation").action(async (slug, opts, cmd) => {
2102
+ import { Command as Command35 } from "commander";
2103
+ var rmCommand4 = new Command35("rm").description("delete an org (owners only; remove its boxes and snapshots first)").argument("<slug>", "the org").option("-y, --yes", "do not ask for confirmation").action(async (slug, opts, cmd) => {
2046
2104
  if (!await confirm(`Delete org ${slug}? Its members lose access; its slug stays taken.`, opts.yes ?? false)) {
2047
2105
  info("cancelled");
2048
2106
  process.exitCode = 1;
@@ -2058,8 +2116,8 @@ var rmCommand4 = new Command34("rm").description("delete an org (owners only; re
2058
2116
  });
2059
2117
 
2060
2118
  // src/commands/org/role.ts
2061
- import { Argument, Command as Command35 } from "commander";
2062
- var roleCommand = new Command35("role").description("change a member's role (owners only)").argument("<user>", "their username").addArgument(new Argument("<role>", "owner, admin or member").choices(ORG_ROLES)).option("-o, --org <slug>", "the org (default: the current context)").action(async (user, role, opts, cmd) => {
2119
+ import { Argument, Command as Command36 } from "commander";
2120
+ var roleCommand = new Command36("role").description("change a member's role (owners only)").argument("<user>", "their username").addArgument(new Argument("<role>", "owner, admin or member").choices(ORG_ROLES)).option("-o, --org <slug>", "the org (default: the current context)").action(async (user, role, opts, cmd) => {
2063
2121
  const client = await cloudClientFor(cmd.optsWithGlobals());
2064
2122
  try {
2065
2123
  const slug = orgFor(opts, client.target);
@@ -2071,11 +2129,11 @@ var roleCommand = new Command35("role").description("change a member's role (own
2071
2129
  });
2072
2130
 
2073
2131
  // src/commands/org/index.ts
2074
- var orgCommand = new Command36("org").description("orgs: shared slugs that own boxes, with owners, admins and members").addCommand(lsCommand3).addCommand(newCommand3).addCommand(membersCommand).addCommand(roleCommand).addCommand(rmCommand4);
2132
+ var orgCommand = new Command37("org").description("orgs: shared slugs that own boxes, with owners, admins and members").addCommand(lsCommand3).addCommand(newCommand3).addCommand(membersCommand).addCommand(roleCommand).addCommand(rmCommand4);
2075
2133
 
2076
2134
  // src/commands/usage.ts
2077
- import { Command as Command37 } from "commander";
2078
- var usageCommand2 = new Command37("usage").description("what your boxes used this month (or --since/--until): running time, vCPU-h, RAM and disk GiB-h, cost").option("--since <date>", "count from this date, e.g. 2026-10-01 (default: the 1st of this month)").option("--until <date>", "count up to this date (default: now)").option("--org <slug>", "one org's boxes only").option("--json", "print as JSON").action(async (opts, cmd) => {
2135
+ import { Command as Command38 } from "commander";
2136
+ var usageCommand2 = new Command38("usage").description("what your boxes used this month (or --since/--until): running time, vCPU-h, RAM and disk GiB-h, cost").option("--since <date>", "count from this date, e.g. 2026-10-01 (default: the 1st of this month)").option("--until <date>", "count up to this date (default: now)").option("--org <slug>", "one org's boxes only").option("--json", "print as JSON").action(async (opts, cmd) => {
2079
2137
  for (const [name, value] of [
2080
2138
  ["since", opts.since],
2081
2139
  ["until", opts.until]
@@ -2148,8 +2206,8 @@ function usageRows(report, cloud) {
2148
2206
  }
2149
2207
 
2150
2208
  // src/commands/credit.ts
2151
- import { Command as Command38 } from "commander";
2152
- var creditCommand = new Command38("credit").description("the prepaid credit left on your account and on each of your orgs").option("--org <slug>", "one org only").option("--json", "print as JSON").action(async (opts, cmd) => {
2209
+ import { Command as Command39 } from "commander";
2210
+ var creditCommand = new Command39("credit").description("the prepaid credit left on your account and on each of your orgs").option("--org <slug>", "one org only").option("--json", "print as JSON").action(async (opts, cmd) => {
2153
2211
  const client = await clientFor(cmd.optsWithGlobals(), { autoStart: false });
2154
2212
  try {
2155
2213
  if (client.target.kind === "local") throw new KvboxError("credit lives on the control plane: kvbox login first");
@@ -2178,7 +2236,7 @@ function creditRow(b, me2) {
2178
2236
 
2179
2237
  // src/program.ts
2180
2238
  function buildProgram() {
2181
- const program = new Command39("kvbox").description("self-hosted Firecracker microVMs with Docker-like ergonomics").version(VERSION, "-v, --version").option("--local", "use this machine's kvbox-server over its socket, ignoring kvbox login").option("--org <slug>", "the org: where kvbox new creates, and which box a name means when it is not one of yours; before the command here, or after it").showHelpAfterError().enablePositionalOptions();
2239
+ const program = new Command40("kvbox").description("self-hosted Firecracker microVMs with Docker-like ergonomics").version(VERSION, "-v, --version").option("--local", "use this machine's kvbox-server over its socket, ignoring kvbox login").option("--org <slug>", "the org: where kvbox new creates, and which box a name means when it is not one of yours; before the command here, or after it").showHelpAfterError().enablePositionalOptions();
2182
2240
  program.addCommand(vmCommand);
2183
2241
  program.addCommand(snapshotsCommand);
2184
2242
  program.addCommand(serverCommand);