@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 +231 -101
- package/dist/kvbox.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
14
|
-
|
|
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
|
|
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.
|
|
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
|
|
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 "
|
|
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
|
|
1391
|
-
var hibernateCommand = new
|
|
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
|
|
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
|
|
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
|
|
1445
|
-
var pauseCommand = new
|
|
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
|
|
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
|
|
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
|
|
1620
|
+
import { Command as Command14 } from "commander";
|
|
1567
1621
|
|
|
1568
1622
|
// src/commands/vm/proxy/add.ts
|
|
1569
|
-
import { Command as
|
|
1570
|
-
var addCommand = new
|
|
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
|
|
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
|
|
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
|
|
1647
|
-
var releaseCommand = new
|
|
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
|
|
1667
|
-
var rmCommand = new
|
|
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
|
|
1700
|
-
var setPortCommand = new
|
|
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
|
|
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
|
|
1723
|
-
var rescaleCommand = new
|
|
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
|
|
1769
|
-
var resumeCommand = new
|
|
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
|
|
1782
|
-
var rmCommand2 = new
|
|
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
|
|
1802
|
-
var setCommand = new
|
|
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
|
|
1822
|
-
var startCommand = new
|
|
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
|
|
1835
|
-
var stopCommand = new
|
|
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
|
|
1848
|
-
var topCommand = new
|
|
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
|
|
1909
|
-
var updateCommand = new
|
|
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
|
|
1932
|
-
var usageCommand = new
|
|
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
|
|
1980
|
-
var wakeCommand = new
|
|
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
|
|
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
|
|
2051
|
+
import { Command as Command33 } from "commander";
|
|
1998
2052
|
|
|
1999
2053
|
// src/commands/server/join.ts
|
|
2000
|
-
import { Command as
|
|
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
|
|
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
|
|
2065
|
-
var leaveCommand = new
|
|
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
|
|
2075
|
-
var relayCommand = new
|
|
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
|
|
2081
|
-
var startCommand2 = new
|
|
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
|
|
2098
|
-
var statusCommand = new
|
|
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
|
|
2131
|
-
var stopCommand2 = new
|
|
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
|
|
2140
|
-
var trustCaCommand = new
|
|
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
|
|
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
|
|
2187
|
-
var loginCommand = new
|
|
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
|
|
2286
|
-
var logoutCommand = new
|
|
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
|
|
2324
|
-
var whoamiCommand = new
|
|
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
|
|
2394
|
+
import { Command as Command40 } from "commander";
|
|
2341
2395
|
|
|
2342
2396
|
// src/commands/snapshots/ls.ts
|
|
2343
|
-
import { Command as
|
|
2344
|
-
var lsCommand3 = new
|
|
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
|
|
2376
|
-
var newCommand2 = new
|
|
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
|
|
2389
|
-
var rmCommand3 = new
|
|
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
|
|
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
|
|
2463
|
+
import { Command as Command46 } from "commander";
|
|
2410
2464
|
|
|
2411
2465
|
// src/commands/org/ls.ts
|
|
2412
|
-
import { Command as
|
|
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
|
|
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
|
|
2455
|
-
var lsCommand5 = new
|
|
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
|
|
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
|
|
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
|
|
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
|
|
2499
|
-
var newCommand3 = new
|
|
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
|
|
2516
|
-
var rmCommand5 = new
|
|
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
|
|
2533
|
-
var roleCommand = new
|
|
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
|
|
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
|
|
2549
|
-
|
|
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
|
|
2623
|
-
var creditCommand = new
|
|
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
|
|
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);
|