@kvbox/cli 0.5.17 → 0.5.19

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
@@ -10,8 +10,10 @@ var KvboxError = class extends Error {
10
10
  exitCode;
11
11
  };
12
12
  var AmbiguousRefError = class extends KvboxError {
13
- constructor(kind, name, slugs) {
14
- super(`${kind} ${name} exists in ${slugs.slice(0, -1).join(", ")} and ${slugs.at(-1)}; say which with --org ${slugs[0]} or ${slugs[0]}/${name}`, 1);
13
+ /** `orgFlag` false: `--org` names something else on this command (a fork's target), so only `slug/name` is offered. */
14
+ constructor(kind, name, slugs, orgFlag = true) {
15
+ const way = orgFlag ? `--org ${slugs[0]} or ${slugs[0]}/${name}` : `${slugs[0]}/${name}`;
16
+ super(`${kind} ${name} exists in ${slugs.slice(0, -1).join(", ")} and ${slugs.at(-1)}; say which with ${way}`, 1);
15
17
  this.name = "AmbiguousRefError";
16
18
  }
17
19
  };
@@ -147,7 +149,7 @@ async function spin(message, work, opts = {}) {
147
149
  }
148
150
 
149
151
  // src/program.ts
150
- import { Command as Command48 } from "commander";
152
+ import { Command as Command49 } from "commander";
151
153
 
152
154
  // ../shared/src/version.ts
153
155
  import { createRequire } from "module";
@@ -162,10 +164,10 @@ function versionOf(srcFileUrl, injected) {
162
164
  }
163
165
 
164
166
  // src/version.ts
165
- var VERSION = versionOf(import.meta.url, true ? "0.5.17" : void 0);
167
+ var VERSION = versionOf(import.meta.url, true ? "0.5.19" : void 0);
166
168
 
167
169
  // src/commands/vm/index.ts
168
- import { Command as Command24 } from "commander";
170
+ import { Command as Command25 } from "commander";
169
171
 
170
172
  // src/commands/vm/connect.ts
171
173
  import { spawnSync as spawnSync2 } from "child_process";
@@ -230,6 +232,8 @@ function paths(home = kvboxHome()) {
230
232
  * The next snapshot is a diff of the pages dirtied since, merged onto this. Absent after a boot.
231
233
  */
232
234
  memBase: join(dir, "mem.base"),
235
+ /** `kvbox vm fork` (#79): the source's VM state, captured here and gone once the box runs. Its memory is `memBase`. */
236
+ forkVmstate: join(dir, "fork.vmstate"),
233
237
  /** `kvbox hibernate`: the box's memory image and VM state while it is hibernated; gone again after `wake` or `stop`. */
234
238
  hibernateDir: join(dir, "hibernate"),
235
239
  hibernateVmstate: join(dir, "hibernate", "vmstate"),
@@ -427,7 +431,7 @@ var MachineSchema = z2.object({
427
431
  // base image version the box's overlay stacks on
428
432
  createdAt: z2.string(),
429
433
  source: z2.string(),
430
- // "base" or "snapshot:<name>"
434
+ // "base", "snapshot:<owner/name>" or "fork:<owner/name>" (#79)
431
435
  status: BoxStatusSchema,
432
436
  /**
433
437
  * Public TCP port on the host that reaches the box's SSH while it runs (#49). Only boxes made
@@ -463,6 +467,20 @@ var CreateMachineRequest = z2.object({
463
467
  /** Seconds without network traffic before the box is hibernated (#47); 0: never. Absent: the host's default. */
464
468
  autoHibernateSec: z2.number().int().min(0).optional()
465
469
  });
470
+ var ForkMachineRequest = z2.object({
471
+ name: z2.string().optional(),
472
+ /**
473
+ * The new box's slug, bare (`acme`): the control plane sends the target of `--org` here; the host
474
+ * qualifies it with the caller's realm. Absent: the source's owner. A CLI leaves it out.
475
+ */
476
+ owner: z2.string().optional(),
477
+ /** A cold fork only: a warm one keeps the source's vCPU and memory. */
478
+ size: SizeSchema.optional(),
479
+ force: z2.boolean().optional(),
480
+ /** As in CreateMachineRequest: the control plane's CA line for the new box's owner. */
481
+ sshKeys: z2.array(z2.string()).optional(),
482
+ autoHibernateSec: z2.number().int().min(0).optional()
483
+ });
466
484
  var SetSshKeysRequest = z2.object({
467
485
  sshKeys: z2.array(z2.string())
468
486
  });
@@ -578,7 +596,12 @@ var BoxUsageSchema = MachineUsageSchema.omit({ window: true, status: true }).ext
578
596
  id: z2.string(),
579
597
  status: UsageStatusSchema,
580
598
  /** Through a control plane: whether the owner slug is a user's or an org's (what `kvbox ls` shows). */
581
- ownerKind: z2.enum(["user", "org"]).optional()
599
+ ownerKind: z2.enum(["user", "org"]).optional(),
600
+ /**
601
+ * Through a control plane: the username that made the box (#60), absent when the box is gone from
602
+ * the index (removed), was found by reconcile, or its maker was deleted. The ledger does not record it.
603
+ */
604
+ createdBy: z2.string().optional()
582
605
  });
583
606
  var UsageReportSchema = z2.object({
584
607
  window: z2.object({ from: z2.string(), to: z2.string() }),
@@ -923,6 +946,10 @@ var ServerClient = class {
923
946
  createMachine(req) {
924
947
  return this.call("POST", "/machines", MachineSchema, req);
925
948
  }
949
+ /** `kvbox vm fork` (#79): `name` is the source; the copy goes to the `--org` slug, else the caller's own. */
950
+ forkMachine(name, req) {
951
+ return this.call("POST", `/machines/${encodeURIComponent(name)}/fork`, MachineSchema, req);
952
+ }
926
953
  stopMachine(name) {
927
954
  return this.call("POST", `/machines/${encodeURIComponent(name)}/stop`, MachineSchema);
928
955
  }
@@ -1242,7 +1269,7 @@ function displayName(box, user) {
1242
1269
  return user !== void 0 && slugOf(box.owner) !== user ? `${slugOf(box.owner)}/${box.name}` : box.name;
1243
1270
  }
1244
1271
  function describe(box, user) {
1245
- const from = box.source.startsWith("snapshot:") ? ` from snapshot ${shortRef(box.source.slice("snapshot:".length), box.owner)}` : "";
1272
+ const from = box.source.startsWith("snapshot:") ? ` from snapshot ${shortRef(box.source.slice("snapshot:".length), box.owner)}` : box.source.startsWith("fork:") ? `, a fork of ${shortRef(box.source.slice("fork:".length), box.owner)}` : "";
1246
1273
  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`;
1247
1274
  }
1248
1275
  function autoHibernateText(sec) {
@@ -1386,9 +1413,36 @@ var execCommand = new Command3("exec").description("run a command in a box and e
1386
1413
  process.exit(status2);
1387
1414
  });
1388
1415
 
1416
+ // src/commands/vm/fork.ts
1417
+ import { Command as Command4, Option as Option2 } from "commander";
1418
+ var forkCommand = new Command4("fork").description("copy a box, running or stopped, into a new one on the same host (--org: into an org)").argument("<box>", "the box to copy").argument("[name]", "the copy's name (box1, box2, \u2026 when omitted)").addOption(new Option2("-s, --size <size>", "the copy's size, for a stopped box only (default: the box's)").choices(SIZE_NAMES)).option("--auto-hibernate <duration>", "hibernate the copy after this long without network traffic (30m, 4h, 1d; off: never; default: the box's)").option("--force", "skip the capacity guard").option("--json", "print the new box as JSON").action(async (source, name, opts, cmd) => {
1419
+ const autoHibernateSec = opts.autoHibernate === void 0 ? void 0 : parseIdleTimeout(opts.autoHibernate);
1420
+ const client = await clientFor(cmd.optsWithGlobals());
1421
+ try {
1422
+ const box = await spin(`forking ${source}\u2026`, client.forkMachine(source, {
1423
+ ...name !== void 0 ? { name } : {},
1424
+ ...opts.size !== void 0 ? { size: opts.size } : {},
1425
+ ...autoHibernateSec !== void 0 ? { autoHibernateSec } : {},
1426
+ ...opts.force ? { force: true } : {}
1427
+ }));
1428
+ if (opts.json) {
1429
+ json(box);
1430
+ return;
1431
+ }
1432
+ const user = me(client);
1433
+ process.stdout.write(`${box.name}
1434
+ `);
1435
+ info(`${color.green("\u2713")} ${describe(box, user)}`);
1436
+ info(` connect with: kvbox connect ${displayName(box, user)}`);
1437
+ info(` it ${autoHibernateText(box.autoHibernateSec)}; published ports and HTTPS names stay with ${source}`);
1438
+ } finally {
1439
+ await client.close();
1440
+ }
1441
+ });
1442
+
1389
1443
  // src/commands/vm/hibernate.ts
1390
- import { Command as Command4 } from "commander";
1391
- var hibernateCommand = new Command4("hibernate").description("save a box's memory to disk and free its RAM (kvbox wake continues it where it left off)").argument("<name>", "box name").option("--json", "print the box as JSON").action(async (name, opts, cmd) => {
1444
+ import { Command as Command5 } from "commander";
1445
+ var hibernateCommand = new Command5("hibernate").description("save a box's memory to disk and free its RAM (kvbox wake continues it where it left off)").argument("<name>", "box name").option("--json", "print the box as JSON").action(async (name, opts, cmd) => {
1392
1446
  const client = await clientFor(cmd.optsWithGlobals());
1393
1447
  try {
1394
1448
  const box = await spin(`hibernating ${name} (saving its memory to disk)\u2026`, client.hibernateMachine(name));
@@ -1400,7 +1454,7 @@ var hibernateCommand = new Command4("hibernate").description("save a box's memor
1400
1454
  });
1401
1455
 
1402
1456
  // src/commands/vm/ls.ts
1403
- import { Command as Command5 } from "commander";
1457
+ import { Command as Command6 } from "commander";
1404
1458
  function ownerCell(b, me2) {
1405
1459
  const slug = slugOf(b.owner);
1406
1460
  if (slug === me2) return "private";
@@ -1410,7 +1464,7 @@ function addressOf(b) {
1410
1464
  if (b.ssh !== void 0) return b.ssh.host;
1411
1465
  return b.ip === "" ? "-" : b.ip;
1412
1466
  }
1413
- var lsCommand = new Command5("ls").description("list boxes with status and address (yours and your orgs'; --org <slug> for one org)").option("-a, --all", "every box on the platform (platform admins only)").option("--json", "print as JSON").action(async (opts, cmd) => {
1467
+ var lsCommand = new Command6("ls").description("list boxes with status and address (yours and your orgs'; --org <slug> for one org)").option("-a, --all", "every box on the platform (platform admins only)").option("--json", "print as JSON").action(async (opts, cmd) => {
1414
1468
  const client = await clientFor(cmd.optsWithGlobals());
1415
1469
  try {
1416
1470
  const boxes = await spin("listing boxes\u2026", client.listMachines({ all: opts.all }));
@@ -1441,8 +1495,8 @@ function lsRows(boxes, cloud) {
1441
1495
  }
1442
1496
 
1443
1497
  // src/commands/vm/pause.ts
1444
- import { Command as Command6 } from "commander";
1445
- var pauseCommand = new Command6("pause").description("freeze a box in place (memory and processes kept, no CPU used; kvbox resume continues it instantly)").argument("<name>", "box name").option("--json", "print the box as JSON").action(async (name, opts, cmd) => {
1498
+ import { Command as Command7 } from "commander";
1499
+ var pauseCommand = new Command7("pause").description("freeze a box in place (memory and processes kept, no CPU used; kvbox resume continues it instantly)").argument("<name>", "box name").option("--json", "print the box as JSON").action(async (name, opts, cmd) => {
1446
1500
  const client = await clientFor(cmd.optsWithGlobals());
1447
1501
  try {
1448
1502
  const box = await spin(`pausing ${name}\u2026`, client.pauseMachine(name));
@@ -1454,7 +1508,7 @@ var pauseCommand = new Command6("pause").description("freeze a box in place (mem
1454
1508
  });
1455
1509
 
1456
1510
  // src/commands/vm/proxy/claim.ts
1457
- import { Command as Command7 } from "commander";
1511
+ import { Command as Command8 } from "commander";
1458
1512
 
1459
1513
  // ../shared/src/names.ts
1460
1514
  import { createHmac } from "crypto";
@@ -1540,7 +1594,7 @@ async function findBox(client, text) {
1540
1594
  }
1541
1595
 
1542
1596
  // src/commands/vm/proxy/claim.ts
1543
- var claimCommand = new Command7("claim").description("give a box its HTTPS address, https://<subdomain>.kvbox.app, serving one of its ports").argument("<box>", "box name").argument("<subdomain>", "3-63 lowercase letters, digits and '-'; must be free").requiredOption("--port <port>", "the box port the address serves").option("--json", "print the subdomain as JSON").action(async (name, subdomain, opts, cmd) => {
1597
+ var claimCommand = new Command8("claim").description("give a box its HTTPS address, https://<subdomain>.kvbox.app, serving one of its ports").argument("<box>", "box name").argument("<subdomain>", "3-63 lowercase letters, digits and '-'; must be free").requiredOption("--port <port>", "the box port the address serves").option("--json", "print the subdomain as JSON").action(async (name, subdomain, opts, cmd) => {
1544
1598
  assertSubdomain(subdomain);
1545
1599
  const [port] = parsePorts([opts.port]);
1546
1600
  const client = await clientFor(cmd.optsWithGlobals());
@@ -1563,11 +1617,11 @@ var claimCommand = new Command7("claim").description("give a box its HTTPS addre
1563
1617
  });
1564
1618
 
1565
1619
  // src/commands/vm/proxy/index.ts
1566
- import { Command as Command13 } from "commander";
1620
+ import { Command as Command14 } from "commander";
1567
1621
 
1568
1622
  // src/commands/vm/proxy/add.ts
1569
- import { Command as Command8 } from "commander";
1570
- var addCommand = new Command8("add").description("publish box ports on the internet; each gets a random public port, or an HTTPS name with --name").argument("<box>", "box name").argument("<port...>", "ports inside the box, e.g. 3000 8080").option("--udp", "UDP instead of TCP").option("--name <name>", "serve the port at https://<name>.<subdomain>.kvbox.app (one port; needs kvbox vm proxy claim first)").option("--json", "print the new forwards as JSON").action(async (name, portTexts, opts, cmd) => {
1623
+ import { Command as Command9 } from "commander";
1624
+ var addCommand = new Command9("add").description("publish box ports on the internet; each gets a random public port, or an HTTPS name with --name").argument("<box>", "box name").argument("<port...>", "ports inside the box, e.g. 3000 8080").option("--udp", "UDP instead of TCP").option("--name <name>", "serve the port at https://<name>.<subdomain>.kvbox.app (one port; needs kvbox vm proxy claim first)").option("--json", "print the new forwards as JSON").action(async (name, portTexts, opts, cmd) => {
1571
1625
  const guestPorts = parsePorts(portTexts);
1572
1626
  if (opts.name !== void 0) {
1573
1627
  assertRouteName(opts.name);
@@ -1608,7 +1662,7 @@ var addCommand = new Command8("add").description("publish box ports on the inter
1608
1662
  });
1609
1663
 
1610
1664
  // src/commands/vm/proxy/ls.ts
1611
- import { Command as Command9 } from "commander";
1665
+ import { Command as Command10 } from "commander";
1612
1666
  async function subdomainsOf(client) {
1613
1667
  try {
1614
1668
  return await client.listSubdomains();
@@ -1617,7 +1671,7 @@ async function subdomainsOf(client) {
1617
1671
  return [];
1618
1672
  }
1619
1673
  }
1620
- var lsCommand2 = new Command9("ls").description("list the box ports published on the internet").argument("[box]", "only this box").option("--json", "print as JSON").action(async (name, opts, cmd) => {
1674
+ var lsCommand2 = new Command10("ls").description("list the box ports published on the internet").argument("[box]", "only this box").option("--json", "print as JSON").action(async (name, opts, cmd) => {
1621
1675
  const client = await clientFor(cmd.optsWithGlobals());
1622
1676
  try {
1623
1677
  const user = me(client);
@@ -1643,8 +1697,8 @@ var lsCommand2 = new Command9("ls").description("list the box ports published on
1643
1697
  });
1644
1698
 
1645
1699
  // src/commands/vm/proxy/release.ts
1646
- import { Command as Command10 } from "commander";
1647
- var releaseCommand = new Command10("release").description("give up a box's HTTPS address and every name under it").argument("<box>", "box name").option("-y, --yes", "do not ask for confirmation").action(async (name, opts, cmd) => {
1700
+ import { Command as Command11 } from "commander";
1701
+ var releaseCommand = new Command11("release").description("give up a box's HTTPS address and every name under it").argument("<box>", "box name").option("-y, --yes", "do not ask for confirmation").action(async (name, opts, cmd) => {
1648
1702
  const client = await clientFor(cmd.optsWithGlobals());
1649
1703
  try {
1650
1704
  requireCloud(client);
@@ -1663,8 +1717,8 @@ var releaseCommand = new Command10("release").description("give up a box's HTTPS
1663
1717
  });
1664
1718
 
1665
1719
  // src/commands/vm/proxy/rm.ts
1666
- import { Command as Command11 } from "commander";
1667
- var rmCommand = new Command11("rm").description("stop publishing box ports, or an HTTPS name with --name").argument("<box>", "box name").argument("[port...]", "ports inside the box, as given to add").option("--udp", "the UDP forwards of those ports").option("--name <name>", "the HTTPS name to free (kvbox vm proxy release frees the whole subdomain)").option("-y, --yes", "do not ask for confirmation").action(async (name, portTexts, opts, cmd) => {
1720
+ import { Command as Command12 } from "commander";
1721
+ var rmCommand = new Command12("rm").description("stop publishing box ports, or an HTTPS name with --name").argument("<box>", "box name").argument("[port...]", "ports inside the box, as given to add").option("--udp", "the UDP forwards of those ports").option("--name <name>", "the HTTPS name to free (kvbox vm proxy release frees the whole subdomain)").option("-y, --yes", "do not ask for confirmation").action(async (name, portTexts, opts, cmd) => {
1668
1722
  if (opts.name !== void 0) {
1669
1723
  assertRouteName(opts.name);
1670
1724
  if (portTexts.length > 0) throw new KvboxError("--name frees a name, not ports: kvbox vm proxy rm <box> --name <name>");
@@ -1696,8 +1750,8 @@ var rmCommand = new Command11("rm").description("stop publishing box ports, or a
1696
1750
  });
1697
1751
 
1698
1752
  // src/commands/vm/proxy/set-port.ts
1699
- import { Command as Command12 } from "commander";
1700
- var setPortCommand = new Command12("set-port").description("point a box's HTTPS address at another of its ports").argument("<box>", "box name").argument("<port>", "the box port https://<subdomain>.kvbox.app serves from now on").option("--json", "print the subdomain as JSON").action(async (name, portText, opts, cmd) => {
1753
+ import { Command as Command13 } from "commander";
1754
+ var setPortCommand = new Command13("set-port").description("point a box's HTTPS address at another of its ports").argument("<box>", "box name").argument("<port>", "the box port https://<subdomain>.kvbox.app serves from now on").option("--json", "print the subdomain as JSON").action(async (name, portText, opts, cmd) => {
1701
1755
  const [port] = parsePorts([portText]);
1702
1756
  const client = await clientFor(cmd.optsWithGlobals());
1703
1757
  try {
@@ -1715,12 +1769,12 @@ var setPortCommand = new Command12("set-port").description("point a box's HTTPS
1715
1769
  });
1716
1770
 
1717
1771
  // src/commands/vm/proxy/index.ts
1718
- var proxyCommand = new Command13("proxy").description("publish box ports on the internet").addCommand(addCommand).addCommand(lsCommand2).addCommand(rmCommand).addCommand(claimCommand).addCommand(setPortCommand).addCommand(releaseCommand);
1772
+ var proxyCommand = new Command14("proxy").description("publish box ports on the internet").addCommand(addCommand).addCommand(lsCommand2).addCommand(rmCommand).addCommand(claimCommand).addCommand(setPortCommand).addCommand(releaseCommand);
1719
1773
  for (const cmd of proxyCommand.commands) cmd.option("--org <slug>", "act in this org (- for your personal boxes)");
1720
1774
 
1721
1775
  // src/commands/vm/rescale.ts
1722
- import { Command as Command14, Option as Option2 } from "commander";
1723
- var rescaleCommand = new Command14("rescale").description("change a box's vCPUs, memory or disk (e.g. --vcpu 4 --mem 16GB); a running box restarts").argument("<name>", "box name").addOption(new Option2("-s, --size <size>", "a preset to start from").choices(SIZE_NAMES)).option("--vcpu <n>", "number of vCPUs").option("--mem <size>", "memory, e.g. 16GB or 4096MB").option("--disk <size>", "disk size, e.g. 32GB (grow only)").option("--json", "print the box as JSON").action(async (name, opts, cmd) => {
1776
+ import { Command as Command15, Option as Option3 } from "commander";
1777
+ var rescaleCommand = new Command15("rescale").description("change a box's vCPUs, memory or disk (e.g. --vcpu 4 --mem 16GB); a running box restarts").argument("<name>", "box name").addOption(new Option3("-s, --size <size>", "a preset to start from").choices(SIZE_NAMES)).option("--vcpu <n>", "number of vCPUs").option("--mem <size>", "memory, e.g. 16GB or 4096MB").option("--disk <size>", "disk size, e.g. 32GB (grow only)").option("--json", "print the box as JSON").action(async (name, opts, cmd) => {
1724
1778
  const req = {
1725
1779
  ...opts.size !== void 0 ? { size: opts.size } : {},
1726
1780
  ...opts.vcpu !== void 0 ? { vcpu: parseCount(opts.vcpu) } : {},
@@ -1765,8 +1819,8 @@ function parseGib(flag, text) {
1765
1819
  }
1766
1820
 
1767
1821
  // src/commands/vm/resume.ts
1768
- import { Command as Command15 } from "commander";
1769
- var resumeCommand = new Command15("resume").description("continue a box that is in standby (kvbox pause)").argument("<name>", "box name").option("--json", "print the box as JSON").action(async (name, opts, cmd) => {
1822
+ import { Command as Command16 } from "commander";
1823
+ var resumeCommand = new Command16("resume").description("continue a box that is in standby (kvbox pause)").argument("<name>", "box name").option("--json", "print the box as JSON").action(async (name, opts, cmd) => {
1770
1824
  const client = await clientFor(cmd.optsWithGlobals());
1771
1825
  try {
1772
1826
  const box = await spin(`resuming ${name}\u2026`, client.resumeMachine(name));
@@ -1778,8 +1832,8 @@ var resumeCommand = new Command15("resume").description("continue a box that is
1778
1832
  });
1779
1833
 
1780
1834
  // src/commands/vm/rm.ts
1781
- import { Command as Command16 } from "commander";
1782
- var rmCommand2 = new Command16("rm").description("destroy a box (its disk is deleted; snapshots are kept)").argument("<name>", "box name").option("-y, --yes", "do not ask for confirmation").action(async (name, opts, cmd) => {
1835
+ import { Command as Command17 } from "commander";
1836
+ var rmCommand2 = new Command17("rm").description("destroy a box (its disk is deleted; snapshots are kept)").argument("<name>", "box name").option("-y, --yes", "do not ask for confirmation").action(async (name, opts, cmd) => {
1783
1837
  const client = await clientFor(cmd.optsWithGlobals());
1784
1838
  try {
1785
1839
  const box = pickRef(await boxesFor(client, name), name, me(client));
@@ -1798,8 +1852,8 @@ var rmCommand2 = new Command16("rm").description("destroy a box (its disk is del
1798
1852
  });
1799
1853
 
1800
1854
  // src/commands/vm/set.ts
1801
- import { Command as Command17 } from "commander";
1802
- var setCommand = new Command17("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) => {
1855
+ import { Command as Command18 } from "commander";
1856
+ var setCommand = new Command18("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) => {
1803
1857
  if (opts.autoHibernate === void 0) throw new KvboxError("nothing to change: give --auto-hibernate");
1804
1858
  const autoHibernateSec = parseIdleTimeout(opts.autoHibernate);
1805
1859
  const client = await clientFor(cmd.optsWithGlobals());
@@ -1818,8 +1872,8 @@ var setCommand = new Command17("set").description("change a box's settings, e.g.
1818
1872
  });
1819
1873
 
1820
1874
  // src/commands/vm/start.ts
1821
- import { Command as Command18 } from "commander";
1822
- var startCommand = new Command18("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) => {
1875
+ import { Command as Command19 } from "commander";
1876
+ var startCommand = new Command19("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) => {
1823
1877
  const client = await clientFor(cmd.optsWithGlobals());
1824
1878
  try {
1825
1879
  const box = await spin(`booting ${name}\u2026`, client.startMachine(name));
@@ -1831,8 +1885,8 @@ var startCommand = new Command18("start").description("boot a stopped box again
1831
1885
  });
1832
1886
 
1833
1887
  // src/commands/vm/stop.ts
1834
- import { Command as Command19 } from "commander";
1835
- var stopCommand = new Command19("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) => {
1888
+ import { Command as Command20 } from "commander";
1889
+ var stopCommand = new Command20("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) => {
1836
1890
  const client = await clientFor(cmd.optsWithGlobals());
1837
1891
  try {
1838
1892
  const box = await spin(`shutting ${name} down\u2026`, client.stopMachine(name));
@@ -1844,8 +1898,8 @@ var stopCommand = new Command19("stop").description("shut a box down (its disk i
1844
1898
  });
1845
1899
 
1846
1900
  // src/commands/vm/top.ts
1847
- import { Command as Command20 } from "commander";
1848
- var topCommand = new Command20("top").description("load, memory and disk of each running box, as the box itself sees them").option("-a, --all", "every running box on the platform (platform admins only)").option("--json", "print as JSON").action(async (opts, cmd) => {
1901
+ import { Command as Command21 } from "commander";
1902
+ var topCommand = new Command21("top").description("load, memory and disk of each running box, as the box itself sees them").option("-a, --all", "every running box on the platform (platform admins only)").option("--json", "print as JSON").action(async (opts, cmd) => {
1849
1903
  const client = await clientFor(cmd.optsWithGlobals());
1850
1904
  try {
1851
1905
  const running = (await client.listMachines({ all: opts.all })).filter((b) => b.status === "running");
@@ -1905,8 +1959,8 @@ function topRows(entries, cloud) {
1905
1959
  }
1906
1960
 
1907
1961
  // src/commands/vm/update.ts
1908
- import { Command as Command21 } from "commander";
1909
- var updateCommand = new Command21("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) => {
1962
+ import { Command as Command22 } from "commander";
1963
+ var updateCommand = new Command22("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) => {
1910
1964
  const client = await clientFor(cmd.optsWithGlobals());
1911
1965
  try {
1912
1966
  const before = pickRef(await boxesFor(client, name), name, me(client));
@@ -1928,8 +1982,8 @@ var updateCommand = new Command21("update").description("move a box onto the cur
1928
1982
  });
1929
1983
 
1930
1984
  // src/commands/vm/usage.ts
1931
- import { Command as Command22 } from "commander";
1932
- var usageCommand = new Command22("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) => {
1985
+ import { Command as Command23 } from "commander";
1986
+ var usageCommand = new Command23("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) => {
1933
1987
  let since;
1934
1988
  if (opts.since !== void 0) {
1935
1989
  const t = Date.parse(opts.since);
@@ -1976,8 +2030,8 @@ function block(u) {
1976
2030
  }
1977
2031
 
1978
2032
  // src/commands/vm/wake.ts
1979
- import { Command as Command23 } from "commander";
1980
- var wakeCommand = new Command23("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) => {
2033
+ import { Command as Command24 } from "commander";
2034
+ var wakeCommand = new Command24("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) => {
1981
2035
  const client = await clientFor(cmd.optsWithGlobals());
1982
2036
  try {
1983
2037
  const box = await spin(`waking ${name} (loading its memory from disk)\u2026`, client.wakeMachine(name));
@@ -1989,15 +2043,15 @@ var wakeCommand = new Command23("wake").description("continue a hibernated box w
1989
2043
  });
1990
2044
 
1991
2045
  // src/commands/vm/index.ts
1992
- var vmCommand = new Command24("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(topCommand).addCommand(proxyCommand).addCommand(rmCommand2);
2046
+ var vmCommand = new Command25("vm").enablePositionalOptions().description("manage boxes (each one is a Firecracker microVM)").addCommand(newCommand).addCommand(forkCommand).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(topCommand).addCommand(proxyCommand).addCommand(rmCommand2);
1993
2047
  for (const cmd of vmCommand.commands) if (cmd !== proxyCommand) cmd.option("--org <slug>", "act in this org (- for your personal boxes)");
1994
2048
  var vmAliases = [newCommand, lsCommand, connectCommand, execCommand, stopCommand, startCommand, pauseCommand, resumeCommand, hibernateCommand, wakeCommand, rmCommand2, claimCommand];
1995
2049
 
1996
2050
  // src/commands/server/index.ts
1997
- import { Command as Command32 } from "commander";
2051
+ import { Command as Command33 } from "commander";
1998
2052
 
1999
2053
  // src/commands/server/join.ts
2000
- import { Command as Command25 } from "commander";
2054
+ import { Command as Command26 } from "commander";
2001
2055
 
2002
2056
  // src/commands/server/daemon.ts
2003
2057
  import { spawn as spawn3 } from "child_process";
@@ -2051,7 +2105,7 @@ function runKvboxServer(args) {
2051
2105
  }
2052
2106
 
2053
2107
  // src/commands/server/join.ts
2054
- var joinCommand = new Command25("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) => {
2108
+ var joinCommand = new Command26("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) => {
2055
2109
  const args = ["join", url, "--token", opts.token];
2056
2110
  if (opts.advertise !== void 0) args.push("--advertise", opts.advertise);
2057
2111
  if (opts.name !== void 0) args.push("--name", opts.name);
@@ -2061,8 +2115,8 @@ var joinCommand = new Command25("join").description("put this machine behind a c
2061
2115
  });
2062
2116
 
2063
2117
  // src/commands/server/leave.ts
2064
- import { Command as Command26 } from "commander";
2065
- var leaveCommand = new Command26("leave").description("take this machine out from behind its control plane and restart its server").action(async () => {
2118
+ import { Command as Command27 } from "commander";
2119
+ var leaveCommand = new Command27("leave").description("take this machine out from behind its control plane and restart its server").action(async () => {
2066
2120
  const p = paths();
2067
2121
  for (const line of (await runKvboxServer(["leave"])).split("\n")) info(line);
2068
2122
  if (await runningPid(p) === null) return;
@@ -2071,14 +2125,14 @@ var leaveCommand = new Command26("leave").description("take this machine out fro
2071
2125
  });
2072
2126
 
2073
2127
  // src/commands/server/relay.ts
2074
- import { Command as Command27 } from "commander";
2075
- var relayCommand = new Command27("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) => {
2128
+ import { Command as Command28 } from "commander";
2129
+ var relayCommand = new Command28("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) => {
2076
2130
  for (const line of (await runKvboxServer(["relay", joinString])).split("\n")) info(line);
2077
2131
  });
2078
2132
 
2079
2133
  // src/commands/server/start.ts
2080
- import { Command as Command28 } from "commander";
2081
- var startCommand2 = new Command28("start").description("start the local kvbox-server in the background").action(async (_opts, cmd) => {
2134
+ import { Command as Command29 } from "commander";
2135
+ var startCommand2 = new Command29("start").description("start the local kvbox-server in the background").action(async (_opts, cmd) => {
2082
2136
  const client = await clientFor({ ...cmd.optsWithGlobals(), local: true }, { autoStart: false });
2083
2137
  try {
2084
2138
  const health = await client.health();
@@ -2094,8 +2148,8 @@ var startCommand2 = new Command28("start").description("start the local kvbox-se
2094
2148
  });
2095
2149
 
2096
2150
  // src/commands/server/status.ts
2097
- import { Command as Command29 } from "commander";
2098
- var statusCommand = new Command29("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) => {
2151
+ import { Command as Command30 } from "commander";
2152
+ var statusCommand = new Command30("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) => {
2099
2153
  const client = await clientFor({ ...cmd.optsWithGlobals(), local: true });
2100
2154
  try {
2101
2155
  const health = await client.health();
@@ -2127,8 +2181,8 @@ var statusCommand = new Command29("status").description("show whether this machi
2127
2181
  });
2128
2182
 
2129
2183
  // src/commands/server/stop.ts
2130
- import { Command as Command30 } from "commander";
2131
- var stopCommand2 = new Command30("stop").description("stop the local server (running boxes are not affected)").action(async () => {
2184
+ import { Command as Command31 } from "commander";
2185
+ var stopCommand2 = new Command31("stop").description("stop the local server (running boxes are not affected)").action(async () => {
2132
2186
  const pid = await runningPid(paths());
2133
2187
  if (pid === null) throw new KvboxError("kvbox-server is not running");
2134
2188
  await stopLocalServer(pid);
@@ -2136,8 +2190,8 @@ var stopCommand2 = new Command30("stop").description("stop the local server (run
2136
2190
  });
2137
2191
 
2138
2192
  // src/commands/server/trust-ca.ts
2139
- import { Command as Command31 } from "commander";
2140
- var trustCaCommand = new Command31("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) => {
2193
+ import { Command as Command32 } from "commander";
2194
+ var trustCaCommand = new Command32("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) => {
2141
2195
  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");
2142
2196
  if (publicKey !== void 0 && opts.fromCloud) throw new KvboxError("either a public key or --from-cloud, not both");
2143
2197
  const client = await clientFor({ ...cmd.optsWithGlobals(), local: true });
@@ -2175,7 +2229,7 @@ function describeReport(report) {
2175
2229
  }
2176
2230
 
2177
2231
  // src/commands/server/index.ts
2178
- var serverCommand = new Command32("server").description("run or manage the kvbox server").addCommand(startCommand2).addCommand(statusCommand).addCommand(stopCommand2).addCommand(joinCommand).addCommand(leaveCommand).addCommand(relayCommand).addCommand(trustCaCommand);
2232
+ var serverCommand = new Command33("server").description("run or manage the kvbox server").addCommand(startCommand2).addCommand(statusCommand).addCommand(stopCommand2).addCommand(joinCommand).addCommand(leaveCommand).addCommand(relayCommand).addCommand(trustCaCommand);
2179
2233
 
2180
2234
  // src/commands/login.ts
2181
2235
  import { spawnSync as spawnSync3 } from "child_process";
@@ -2183,8 +2237,8 @@ import { mkdir as mkdir3, readFile as readFile5, writeFile as writeFile3 } from
2183
2237
  import { hostname as osHostname } from "os";
2184
2238
  import { createInterface } from "readline/promises";
2185
2239
  import { Writable } from "stream";
2186
- import { Command as Command33 } from "commander";
2187
- var loginCommand = new Command33("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) => {
2240
+ import { Command as Command34 } from "commander";
2241
+ var loginCommand = new Command34("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) => {
2188
2242
  const url = normalizeUrl(urlArg ?? DEFAULT_CLOUD_URL);
2189
2243
  const creds = await readCredentials(opts);
2190
2244
  const session = await runLogin(url, creds, paths());
@@ -2282,8 +2336,8 @@ async function firstLine(stream) {
2282
2336
  }
2283
2337
 
2284
2338
  // src/commands/logout.ts
2285
- import { Command as Command34 } from "commander";
2286
- var logoutCommand = new Command34("logout").description("log out of the control plane; commands target this machine's kvbox-server again").action(async () => {
2339
+ import { Command as Command35 } from "commander";
2340
+ var logoutCommand = new Command35("logout").description("log out of the control plane; commands target this machine's kvbox-server again").action(async () => {
2287
2341
  const p = paths();
2288
2342
  const session = await readSession(p).catch(() => null);
2289
2343
  switch (await runLogout(p)) {
@@ -2320,8 +2374,8 @@ async function runLogout(p = paths()) {
2320
2374
  }
2321
2375
 
2322
2376
  // src/commands/whoami.ts
2323
- import { Command as Command35 } from "commander";
2324
- var whoamiCommand = new Command35("whoami").description("show which account and control plane commands target (or local mode)").option("--json", "print as JSON").action(async (opts) => {
2377
+ import { Command as Command36 } from "commander";
2378
+ var whoamiCommand = new Command36("whoami").description("show which account and control plane commands target (or local mode)").option("--json", "print as JSON").action(async (opts) => {
2325
2379
  const session = await readSession();
2326
2380
  if (opts.json) {
2327
2381
  json(session ? { user: session.user, url: session.url, sshCertExpires: session.sshCertExpires ?? null } : null);
@@ -2337,11 +2391,11 @@ function describeSession(session) {
2337
2391
  }
2338
2392
 
2339
2393
  // src/commands/snapshots/index.ts
2340
- import { Command as Command39 } from "commander";
2394
+ import { Command as Command40 } from "commander";
2341
2395
 
2342
2396
  // src/commands/snapshots/ls.ts
2343
- import { Command as Command36 } from "commander";
2344
- var lsCommand3 = new Command36("ls").description("list snapshots (yours and your orgs'; --org <slug> for one org)").option("-a, --all", "every snapshot on the platform (platform admins only)").option("--json", "print as JSON").action(async (opts, cmd) => {
2397
+ import { Command as Command37 } from "commander";
2398
+ var lsCommand3 = new Command37("ls").description("list snapshots (yours and your orgs'; --org <slug> for one org)").option("-a, --all", "every snapshot on the platform (platform admins only)").option("--json", "print as JSON").action(async (opts, cmd) => {
2345
2399
  const client = await clientFor(cmd.optsWithGlobals());
2346
2400
  try {
2347
2401
  const cloud = client.target.kind === "cloud";
@@ -2372,8 +2426,8 @@ var lsCommand3 = new Command36("ls").description("list snapshots (yours and your
2372
2426
  });
2373
2427
 
2374
2428
  // src/commands/snapshots/new.ts
2375
- import { Command as Command37 } from "commander";
2376
- var newCommand2 = new Command37("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) => {
2429
+ import { Command as Command38 } from "commander";
2430
+ var newCommand2 = new Command38("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) => {
2377
2431
  const client = await clientFor(cmd.optsWithGlobals());
2378
2432
  try {
2379
2433
  const snap = await spin(`saving snapshot ${name} from box ${box} (memory + disk)\u2026`, client.createSnapshot({ box, name }));
@@ -2385,8 +2439,8 @@ var newCommand2 = new Command37("new").description("freeze a running box (memory
2385
2439
  });
2386
2440
 
2387
2441
  // src/commands/snapshots/rm.ts
2388
- import { Command as Command38 } from "commander";
2389
- var rmCommand3 = new Command38("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) => {
2442
+ import { Command as Command39 } from "commander";
2443
+ var rmCommand3 = new Command39("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) => {
2390
2444
  if (!await confirm(`Delete snapshot ${name}?`, opts.yes ?? false)) {
2391
2445
  info("cancelled");
2392
2446
  process.exitCode = 1;
@@ -2402,14 +2456,14 @@ var rmCommand3 = new Command38("rm").description("delete a snapshot (boxes made
2402
2456
  });
2403
2457
 
2404
2458
  // src/commands/snapshots/index.ts
2405
- var snapshotsCommand = new Command39("snapshots").aliases(["snapshot", "snap"]).description("freeze running boxes into snapshots and manage them").addCommand(newCommand2).addCommand(lsCommand3).addCommand(rmCommand3);
2459
+ var snapshotsCommand = new Command40("snapshots").aliases(["snapshot", "snap"]).description("freeze running boxes into snapshots and manage them").addCommand(newCommand2).addCommand(lsCommand3).addCommand(rmCommand3);
2406
2460
  for (const cmd of snapshotsCommand.commands) cmd.option("--org <slug>", "act in this org (- for your personal boxes)");
2407
2461
 
2408
2462
  // src/commands/org/index.ts
2409
- import { Command as Command45 } from "commander";
2463
+ import { Command as Command46 } from "commander";
2410
2464
 
2411
2465
  // src/commands/org/ls.ts
2412
- import { Command as Command40 } from "commander";
2466
+ import { Command as Command41 } from "commander";
2413
2467
 
2414
2468
  // src/commands/org/context.ts
2415
2469
  var NOT_LOGGED_IN = "orgs live on the control plane: kvbox login first";
@@ -2429,7 +2483,7 @@ function orgFor(opts, target) {
2429
2483
  }
2430
2484
 
2431
2485
  // src/commands/org/ls.ts
2432
- var lsCommand4 = new Command40("ls").description("list the orgs you belong to and your role in each").option("--json", "print as JSON").action(async (opts, cmd) => {
2486
+ var lsCommand4 = new Command41("ls").description("list the orgs you belong to and your role in each").option("--json", "print as JSON").action(async (opts, cmd) => {
2433
2487
  const client = await cloudClientFor(cmd.optsWithGlobals());
2434
2488
  try {
2435
2489
  const orgs = await client.listOrgs();
@@ -2451,8 +2505,8 @@ function orgRows(orgs) {
2451
2505
  }
2452
2506
 
2453
2507
  // src/commands/org/members.ts
2454
- import { Command as Command41, Option as Option3 } from "commander";
2455
- var lsCommand5 = new Command41("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) => {
2508
+ import { Command as Command42, Option as Option4 } from "commander";
2509
+ var lsCommand5 = new Command42("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) => {
2456
2510
  const client = await cloudClientFor(cmd.optsWithGlobals());
2457
2511
  try {
2458
2512
  const org = await client.getOrg(orgFor(opts, client.target));
@@ -2466,7 +2520,7 @@ var lsCommand5 = new Command41("ls").description("list an org's members and thei
2466
2520
  await client.close();
2467
2521
  }
2468
2522
  });
2469
- var addCommand2 = new Command41("add").description("add a user to an org (admins and owners; only an owner can add an owner)").argument("<user>", "their username").addOption(new Option3("-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) => {
2523
+ var addCommand2 = new Command42("add").description("add a user to an org (admins and owners; only an owner can add an owner)").argument("<user>", "their username").addOption(new Option4("-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) => {
2470
2524
  const client = await cloudClientFor(cmd.optsWithGlobals());
2471
2525
  try {
2472
2526
  const slug = orgFor(opts, client.target);
@@ -2476,7 +2530,7 @@ var addCommand2 = new Command41("add").description("add a user to an org (admins
2476
2530
  await client.close();
2477
2531
  }
2478
2532
  });
2479
- var rmCommand4 = new Command41("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) => {
2533
+ var rmCommand4 = new Command42("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) => {
2480
2534
  const client = await cloudClientFor(cmd.optsWithGlobals());
2481
2535
  try {
2482
2536
  const slug = orgFor(opts, client.target);
@@ -2492,11 +2546,11 @@ var rmCommand4 = new Command41("rm").description("remove a member (or leave: rem
2492
2546
  await client.close();
2493
2547
  }
2494
2548
  });
2495
- var membersCommand = new Command41("members").description("who is in an org and what they may do").addCommand(lsCommand5).addCommand(addCommand2).addCommand(rmCommand4);
2549
+ var membersCommand = new Command42("members").description("who is in an org and what they may do").addCommand(lsCommand5).addCommand(addCommand2).addCommand(rmCommand4);
2496
2550
 
2497
2551
  // src/commands/org/new.ts
2498
- import { Command as Command42 } from "commander";
2499
- var newCommand3 = new Command42("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) => {
2552
+ import { Command as Command43 } from "commander";
2553
+ var newCommand3 = new Command43("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) => {
2500
2554
  const client = await cloudClientFor(cmd.optsWithGlobals());
2501
2555
  try {
2502
2556
  const org = await client.createOrg({ slug, name: opts.name ?? slug });
@@ -2512,8 +2566,8 @@ var newCommand3 = new Command42("new").description("create an org; you become it
2512
2566
  });
2513
2567
 
2514
2568
  // src/commands/org/rm.ts
2515
- import { Command as Command43 } from "commander";
2516
- var rmCommand5 = new Command43("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) => {
2569
+ import { Command as Command44 } from "commander";
2570
+ var rmCommand5 = new Command44("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) => {
2517
2571
  if (!await confirm(`Delete org ${slug}? Its members lose access; its slug stays taken.`, opts.yes ?? false)) {
2518
2572
  info("cancelled");
2519
2573
  process.exitCode = 1;
@@ -2529,8 +2583,8 @@ var rmCommand5 = new Command43("rm").description("delete an org (owners only; re
2529
2583
  });
2530
2584
 
2531
2585
  // src/commands/org/role.ts
2532
- import { Argument, Command as Command44 } from "commander";
2533
- var roleCommand = new Command44("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) => {
2586
+ import { Argument, Command as Command45 } from "commander";
2587
+ var roleCommand = new Command45("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) => {
2534
2588
  const client = await cloudClientFor(cmd.optsWithGlobals());
2535
2589
  try {
2536
2590
  const slug = orgFor(opts, client.target);
@@ -2542,11 +2596,45 @@ var roleCommand = new Command44("role").description("change a member's role (own
2542
2596
  });
2543
2597
 
2544
2598
  // src/commands/org/index.ts
2545
- var orgCommand = new Command45("org").description("orgs: shared slugs that own boxes, with owners, admins and members").addCommand(lsCommand4).addCommand(newCommand3).addCommand(membersCommand).addCommand(roleCommand).addCommand(rmCommand5);
2599
+ var orgCommand = new Command46("org").description("orgs: shared slugs that own boxes, with owners, admins and members").addCommand(lsCommand4).addCommand(newCommand3).addCommand(membersCommand).addCommand(roleCommand).addCommand(rmCommand5);
2546
2600
 
2547
2601
  // src/commands/usage.ts
2548
- import { Command as Command46 } from "commander";
2549
- var usageCommand2 = new Command46("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("-a, --all", "every box on the platform (platform admins only)").option("--json", "print as JSON").action(async (opts, cmd) => {
2602
+ import { Command as Command47 } from "commander";
2603
+
2604
+ // ../shared/src/usage.ts
2605
+ import { z as z4 } from "zod";
2606
+ var PricingSchema = z4.object({
2607
+ currency: z4.string().min(1),
2608
+ vcpuHour: z4.number().nonnegative(),
2609
+ ramGibHour: z4.number().nonnegative(),
2610
+ diskGibHour: z4.number().nonnegative()
2611
+ });
2612
+ var round = (n, places) => Math.round(n * 10 ** places) / 10 ** places;
2613
+ function costOf(t, pricing) {
2614
+ return t.vcpuHours * pricing.vcpuHour + t.ramGibHours * pricing.ramGibHour + t.diskGibHours * pricing.diskGibHour;
2615
+ }
2616
+ function sumUsage(items, pricing) {
2617
+ const sum = {
2618
+ seconds: { running: 0, standby: 0, hibernated: 0, stopped: 0 },
2619
+ vcpuHours: 0,
2620
+ ramGibHours: 0,
2621
+ diskGibHours: 0
2622
+ };
2623
+ for (const t of items) {
2624
+ for (const s of Object.keys(sum.seconds)) sum.seconds[s] += t.seconds[s];
2625
+ sum.vcpuHours += t.vcpuHours;
2626
+ sum.ramGibHours += t.ramGibHours;
2627
+ sum.diskGibHours += t.diskGibHours;
2628
+ }
2629
+ sum.vcpuHours = round(sum.vcpuHours, 3);
2630
+ sum.ramGibHours = round(sum.ramGibHours, 3);
2631
+ sum.diskGibHours = round(sum.diskGibHours, 3);
2632
+ if (pricing !== void 0) sum.cost = { amount: round(costOf(sum, pricing), 4), currency: pricing.currency };
2633
+ return sum;
2634
+ }
2635
+
2636
+ // src/commands/usage.ts
2637
+ var usageCommand2 = new Command47("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("-a, --all", "every box on the platform (platform admins only)").option("--by-user", "one line per user who made the boxes (e.g. who spent what in --org)").option("--json", "print as JSON").action(async (opts, cmd) => {
2550
2638
  for (const [name, value] of [
2551
2639
  ["since", opts.since],
2552
2640
  ["until", opts.until]
@@ -2556,15 +2644,17 @@ var usageCommand2 = new Command46("usage").description("what your boxes used thi
2556
2644
  const client = await clientFor(cmd.optsWithGlobals());
2557
2645
  try {
2558
2646
  if (client.target.kind === "local" && cmd.optsWithGlobals().org) throw new KvboxError("--org needs a control plane: kvbox login, or drop --org");
2647
+ if (client.target.kind === "local" && opts.byUser) throw new KvboxError("--by-user needs a control plane: kvbox login, or drop --by-user");
2559
2648
  const report = await client.usageReport(opts.since, opts.until, opts.all);
2649
+ const perUser = opts.byUser ? byUser(report) : void 0;
2560
2650
  if (opts.json) {
2561
- json(report);
2651
+ json(perUser ?? report);
2562
2652
  return;
2563
2653
  }
2564
2654
  const cloud = client.target.kind === "cloud" ? { me: client.target.user } : void 0;
2565
2655
  info(`${day2(report.window.from)} to ${day2(report.window.to)}`);
2566
2656
  if (report.boxes.length === 0) info("no usage in this window");
2567
- else table(usageRows(report, cloud));
2657
+ else table(perUser ? userRows(perUser) : usageRows(report, cloud));
2568
2658
  if (cloud) {
2569
2659
  const line = await creditLine(client, cloud.me);
2570
2660
  if (line !== void 0) info(line);
@@ -2617,10 +2707,50 @@ function usageRows(report, cloud) {
2617
2707
  });
2618
2708
  return rows;
2619
2709
  }
2710
+ function byUser(report) {
2711
+ const groups = /* @__PURE__ */ new Map();
2712
+ for (const b of report.boxes) {
2713
+ const user = b.createdBy ?? null;
2714
+ const list = groups.get(user);
2715
+ if (list === void 0) groups.set(user, [b]);
2716
+ else list.push(b);
2717
+ }
2718
+ const currency = report.totals.cost?.currency;
2719
+ const users = [...groups].map(([user, boxes]) => {
2720
+ const { cost: _none, ...sum } = sumUsage(boxes);
2721
+ const u = { user, boxes: boxes.length, ...sum };
2722
+ if (currency !== void 0) u.cost = { amount: round(boxes.reduce((n, b) => n + (b.cost?.amount ?? 0), 0), 4), currency };
2723
+ return u;
2724
+ });
2725
+ users.sort((a, b) => a.user === null ? 1 : b.user === null ? -1 : a.user.localeCompare(b.user));
2726
+ return { window: report.window, users, totals: report.totals };
2727
+ }
2728
+ function userRows(r) {
2729
+ const priced = r.totals.cost !== void 0;
2730
+ const cells = (t) => ({
2731
+ running: duration(t.seconds.running),
2732
+ "vcpu-h": hours(t.vcpuHours),
2733
+ "ram GiB-h": hours(t.ramGibHours),
2734
+ "disk GiB-h": hours(t.diskGibHours)
2735
+ });
2736
+ const rows = r.users.map((u) => ({
2737
+ user: u.user ?? color.dim("unknown"),
2738
+ boxes: String(u.boxes),
2739
+ ...cells(u),
2740
+ ...priced ? { cost: money(u.cost?.amount ?? 0, r.totals.cost.currency) } : {}
2741
+ }));
2742
+ rows.push({
2743
+ user: color.bold("total"),
2744
+ boxes: String(r.users.reduce((n, u) => n + u.boxes, 0)),
2745
+ ...cells(r.totals),
2746
+ ...priced ? { cost: `${money(r.totals.cost.amount, r.totals.cost.currency)} ${color.dim("(estimated)")}` } : {}
2747
+ });
2748
+ return rows;
2749
+ }
2620
2750
 
2621
2751
  // src/commands/credit.ts
2622
- import { Command as Command47 } from "commander";
2623
- var creditCommand = new Command47("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) => {
2752
+ import { Command as Command48 } from "commander";
2753
+ var creditCommand = new Command48("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) => {
2624
2754
  const client = await clientFor(cmd.optsWithGlobals(), { autoStart: false });
2625
2755
  try {
2626
2756
  if (client.target.kind === "local") throw new KvboxError("credit lives on the control plane: kvbox login first");
@@ -2649,7 +2779,7 @@ function creditRow(b, me2) {
2649
2779
 
2650
2780
  // src/program.ts
2651
2781
  function buildProgram() {
2652
- const program = new Command48("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();
2782
+ const program = new Command49("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();
2653
2783
  program.addCommand(vmCommand);
2654
2784
  program.addCommand(snapshotsCommand);
2655
2785
  program.addCommand(serverCommand);