@kvbox/cli 0.5.5 → 0.5.7
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 +173 -11
- package/dist/kvbox.js.map +1 -1
- package/package.json +1 -1
package/dist/kvbox.js
CHANGED
|
@@ -81,11 +81,11 @@ function age(iso, now = Date.now()) {
|
|
|
81
81
|
if (min < 60) return `${min}m ago`;
|
|
82
82
|
const hr = Math.floor(min / 60);
|
|
83
83
|
if (hr < 24) return `${hr}h ago`;
|
|
84
|
-
const
|
|
85
|
-
if (
|
|
86
|
-
const month = Math.floor(
|
|
84
|
+
const day3 = Math.floor(hr / 24);
|
|
85
|
+
if (day3 < 30) return `${day3}d ago`;
|
|
86
|
+
const month = Math.floor(day3 / 30);
|
|
87
87
|
if (month < 12) return `${month}mo ago`;
|
|
88
|
-
return `${Math.floor(
|
|
88
|
+
return `${Math.floor(day3 / 365)}y ago`;
|
|
89
89
|
}
|
|
90
90
|
function duration(seconds) {
|
|
91
91
|
const s = Math.max(0, Math.round(seconds));
|
|
@@ -147,7 +147,7 @@ async function spin(message, work, opts = {}) {
|
|
|
147
147
|
}
|
|
148
148
|
|
|
149
149
|
// src/program.ts
|
|
150
|
-
import { Command as
|
|
150
|
+
import { Command as Command38 } from "commander";
|
|
151
151
|
|
|
152
152
|
// ../shared/src/version.ts
|
|
153
153
|
import { createRequire } from "module";
|
|
@@ -162,7 +162,7 @@ function versionOf(srcFileUrl, injected) {
|
|
|
162
162
|
}
|
|
163
163
|
|
|
164
164
|
// src/version.ts
|
|
165
|
-
var VERSION = versionOf(import.meta.url, true ? "0.5.
|
|
165
|
+
var VERSION = versionOf(import.meta.url, true ? "0.5.7" : void 0);
|
|
166
166
|
|
|
167
167
|
// src/commands/vm/index.ts
|
|
168
168
|
import { Command as Command15 } from "commander";
|
|
@@ -510,10 +510,48 @@ var MachineUsageSchema = z2.object({
|
|
|
510
510
|
estimatedStops: z2.number().int(),
|
|
511
511
|
historyFrom: z2.string().nullable()
|
|
512
512
|
});
|
|
513
|
+
var UsageStatusSchema = z2.enum([...BoxStatusSchema.options, "removed"]);
|
|
514
|
+
var BoxUsageSchema = MachineUsageSchema.omit({ window: true, status: true }).extend({
|
|
515
|
+
/** The host's box id; a name removed and recreated appears twice, with two ids. */
|
|
516
|
+
id: z2.string(),
|
|
517
|
+
status: UsageStatusSchema,
|
|
518
|
+
/** Through a control plane: whether the owner slug is a user's or an org's (what `kvbox ls` shows). */
|
|
519
|
+
ownerKind: z2.enum(["user", "org"]).optional()
|
|
520
|
+
});
|
|
521
|
+
var UsageReportSchema = z2.object({
|
|
522
|
+
window: z2.object({ from: z2.string(), to: z2.string() }),
|
|
523
|
+
boxes: z2.array(BoxUsageSchema),
|
|
524
|
+
totals: z2.object({
|
|
525
|
+
seconds: MachineUsageSchema.shape.seconds,
|
|
526
|
+
vcpuHours: z2.number(),
|
|
527
|
+
ramGibHours: z2.number(),
|
|
528
|
+
diskGibHours: z2.number(),
|
|
529
|
+
cost: z2.object({ amount: z2.number(), currency: z2.string() }).optional()
|
|
530
|
+
})
|
|
531
|
+
});
|
|
532
|
+
var UsageEventRowSchema = z2.object({
|
|
533
|
+
id: z2.number().int(),
|
|
534
|
+
boxId: z2.string(),
|
|
535
|
+
owner: z2.string(),
|
|
536
|
+
name: z2.string(),
|
|
537
|
+
at: z2.string(),
|
|
538
|
+
state: z2.enum(["running", "standby", "hibernated", "stopped"]),
|
|
539
|
+
vcpu: z2.number().int(),
|
|
540
|
+
memMib: z2.number().int(),
|
|
541
|
+
diskGib: z2.number().int(),
|
|
542
|
+
by: z2.string()
|
|
543
|
+
});
|
|
544
|
+
var UsageEventsResponseSchema = z2.object({
|
|
545
|
+
ledger: z2.string().min(1),
|
|
546
|
+
events: z2.array(UsageEventRowSchema),
|
|
547
|
+
next: z2.number().int()
|
|
548
|
+
});
|
|
513
549
|
var ApiErrorSchema = z2.object({
|
|
514
550
|
error: z2.string()
|
|
515
551
|
});
|
|
516
552
|
var DEFAULT_CLOUD_URL = "https://api.kvbox.sh";
|
|
553
|
+
var ACTIVATED_BY = ["card", "admin"];
|
|
554
|
+
var ActivatedBySchema = z2.enum(ACTIVATED_BY);
|
|
517
555
|
var CloudUserSchema = z2.object({
|
|
518
556
|
/** The handle: what hosts file boxes under (`<username>@<control plane>`). Lowercase, no `@` or `/`. */
|
|
519
557
|
username: z2.string(),
|
|
@@ -522,7 +560,14 @@ var CloudUserSchema = z2.object({
|
|
|
522
560
|
/** Login identity, unique. */
|
|
523
561
|
email: z2.string(),
|
|
524
562
|
role: z2.enum(["admin", "user"]),
|
|
525
|
-
createdAt: z2.string()
|
|
563
|
+
createdAt: z2.string(),
|
|
564
|
+
/**
|
|
565
|
+
* False until a card is on file or an admin activates the account (#74): then only `/me` and
|
|
566
|
+
* `/logout` answer. Admins are never gated. A control plane older than #74 does not send these and
|
|
567
|
+
* gates nobody, hence the defaults: a newer CLI must still parse its answers.
|
|
568
|
+
*/
|
|
569
|
+
active: z2.boolean().default(true),
|
|
570
|
+
activatedBy: ActivatedBySchema.nullable().default(null)
|
|
526
571
|
});
|
|
527
572
|
var ORG_ROLES = ["owner", "admin", "member"];
|
|
528
573
|
var OrgRoleSchema = z2.enum(ORG_ROLES);
|
|
@@ -530,7 +575,10 @@ var CloudOrgSchema = z2.object({
|
|
|
530
575
|
slug: z2.string(),
|
|
531
576
|
/** Display name; the slug never changes, this may. */
|
|
532
577
|
name: z2.string(),
|
|
533
|
-
createdAt: z2.string()
|
|
578
|
+
createdAt: z2.string(),
|
|
579
|
+
/** An org pays for itself (#74): off until it has a card or an admin activates it; its boxes are 402. Defaults as on CloudUser. */
|
|
580
|
+
active: z2.boolean().default(true),
|
|
581
|
+
activatedBy: ActivatedBySchema.nullable().default(null)
|
|
534
582
|
});
|
|
535
583
|
var OrgMembershipSchema = CloudOrgSchema.extend({ role: OrgRoleSchema });
|
|
536
584
|
var OrgMemberSchema = z2.object({
|
|
@@ -551,6 +599,32 @@ var AddMemberRequestSchema = z2.object({
|
|
|
551
599
|
});
|
|
552
600
|
var SetRoleRequestSchema = z2.object({ role: OrgRoleSchema });
|
|
553
601
|
var MeResponseSchema = CloudUserSchema.extend({ orgs: z2.array(OrgMembershipSchema) });
|
|
602
|
+
var BalanceSchema = z2.object({
|
|
603
|
+
slug: z2.string(),
|
|
604
|
+
balance: z2.number(),
|
|
605
|
+
currency: z2.string(),
|
|
606
|
+
topUps: z2.number(),
|
|
607
|
+
spent: z2.number(),
|
|
608
|
+
/** The first moment usage counts from: the slug's first top-up; null when it has none. */
|
|
609
|
+
since: z2.string().nullable(),
|
|
610
|
+
/** When the slug ran out and its boxes were hibernated (the `sweep-credit` job); null in good standing. Absent from a control plane older than this field. */
|
|
611
|
+
suspendedAt: z2.string().nullable().default(null)
|
|
612
|
+
});
|
|
613
|
+
var TopUpRequestSchema = z2.object({
|
|
614
|
+
slug: z2.string().min(1),
|
|
615
|
+
/** In the control plane's currency; negative corrects a mistake. */
|
|
616
|
+
amount: z2.number().finite(),
|
|
617
|
+
note: z2.string().max(200).optional()
|
|
618
|
+
});
|
|
619
|
+
var TopUpSchema = z2.object({
|
|
620
|
+
id: z2.string(),
|
|
621
|
+
slug: z2.string(),
|
|
622
|
+
amount: z2.number(),
|
|
623
|
+
currency: z2.string(),
|
|
624
|
+
source: z2.enum(["admin", "card", "trial"]),
|
|
625
|
+
note: z2.string().nullable(),
|
|
626
|
+
at: z2.string()
|
|
627
|
+
});
|
|
554
628
|
var LoginRequestSchema = z2.object({
|
|
555
629
|
email: z2.string().min(1),
|
|
556
630
|
password: z2.string().min(1),
|
|
@@ -784,6 +858,18 @@ var ServerClient = class {
|
|
|
784
858
|
const query = since === void 0 ? "" : `?since=${encodeURIComponent(since)}`;
|
|
785
859
|
return this.call("GET", `/machines/${encodeURIComponent(name)}/usage${query}`, MachineUsageSchema);
|
|
786
860
|
}
|
|
861
|
+
/** Every box of the caller over the window (#41 on a host, #42 through a control plane); dates as the user wrote them. */
|
|
862
|
+
usageReport(since, until) {
|
|
863
|
+
const q = new URLSearchParams();
|
|
864
|
+
if (since !== void 0) q.set("since", since);
|
|
865
|
+
if (until !== void 0) q.set("until", until);
|
|
866
|
+
const query = q.size === 0 ? "" : `?${q.toString()}`;
|
|
867
|
+
return this.call("GET", `/usage${query}`, UsageReportSchema);
|
|
868
|
+
}
|
|
869
|
+
/** The prepaid balance of the caller's slug, or of `--org`'s (#76); a control plane only. */
|
|
870
|
+
balance() {
|
|
871
|
+
return this.call("GET", "/billing/balance", BalanceSchema);
|
|
872
|
+
}
|
|
787
873
|
deleteMachine(name) {
|
|
788
874
|
return this.call("DELETE", `/machines/${encodeURIComponent(name)}`, z3.undefined());
|
|
789
875
|
}
|
|
@@ -1343,9 +1429,10 @@ var usageCommand = new Command13("usage").description("how long a box ran, slept
|
|
|
1343
1429
|
var day = (iso) => iso.slice(0, 10);
|
|
1344
1430
|
var SYMBOLS = { EUR: "\u20AC", USD: "$", GBP: "\xA3" };
|
|
1345
1431
|
function money(amount, currency) {
|
|
1346
|
-
const
|
|
1432
|
+
const sign = amount < 0 ? "-" : "";
|
|
1433
|
+
const n = Math.abs(amount).toFixed(2);
|
|
1347
1434
|
const sym = SYMBOLS[currency.toUpperCase()];
|
|
1348
|
-
return sym === void 0 ? `${n} ${currency}` : `${sym}${n}`;
|
|
1435
|
+
return sym === void 0 ? `${sign}${n} ${currency}` : `${sign}${sym}${n}`;
|
|
1349
1436
|
}
|
|
1350
1437
|
function block(u) {
|
|
1351
1438
|
const estimated = u.estimatedStops === 0 ? "" : color.dim(` (${u.estimatedStops} stop time${u.estimatedStops === 1 ? "" : "s"} estimated)`);
|
|
@@ -1925,9 +2012,83 @@ var roleCommand = new Command35("role").description("change a member's role (own
|
|
|
1925
2012
|
// src/commands/org/index.ts
|
|
1926
2013
|
var orgCommand = new Command36("org").description("orgs: shared slugs that own boxes, with owners, admins and members").addCommand(lsCommand3).addCommand(newCommand3).addCommand(membersCommand).addCommand(roleCommand).addCommand(rmCommand4);
|
|
1927
2014
|
|
|
2015
|
+
// src/commands/usage.ts
|
|
2016
|
+
import { Command as Command37 } from "commander";
|
|
2017
|
+
var usageCommand2 = new Command37("usage").description("what your boxes used this month (or --since/--until): running time, vCPU-h, RAM and disk GiB-h, cost").option("--since <date>", "count from this date, e.g. 2026-10-01 (default: the 1st of this month)").option("--until <date>", "count up to this date (default: now)").option("--org <slug>", "one org's boxes only").option("--json", "print as JSON").action(async (opts, cmd) => {
|
|
2018
|
+
for (const [name, value] of [
|
|
2019
|
+
["since", opts.since],
|
|
2020
|
+
["until", opts.until]
|
|
2021
|
+
]) {
|
|
2022
|
+
if (value !== void 0 && Number.isNaN(Date.parse(value))) throw new KvboxError(`--${name} ${value}: not a date; write it like 2026-10-19 or 2026-10-19T08:00:00Z`);
|
|
2023
|
+
}
|
|
2024
|
+
const client = await clientFor(cmd.optsWithGlobals());
|
|
2025
|
+
try {
|
|
2026
|
+
if (client.target.kind === "local" && cmd.optsWithGlobals().org) throw new KvboxError("--org needs a control plane: kvbox login, or drop --org");
|
|
2027
|
+
const report = await client.usageReport(opts.since, opts.until);
|
|
2028
|
+
if (opts.json) {
|
|
2029
|
+
json(report);
|
|
2030
|
+
return;
|
|
2031
|
+
}
|
|
2032
|
+
const cloud = client.target.kind === "cloud" ? { me: client.target.user } : void 0;
|
|
2033
|
+
info(`${day2(report.window.from)} to ${day2(report.window.to)}`);
|
|
2034
|
+
if (report.boxes.length === 0) info("no usage in this window");
|
|
2035
|
+
else table(usageRows(report, cloud));
|
|
2036
|
+
if (cloud) {
|
|
2037
|
+
const line = await creditLine(client, cloud.me);
|
|
2038
|
+
if (line !== void 0) info(line);
|
|
2039
|
+
}
|
|
2040
|
+
} finally {
|
|
2041
|
+
await client.close();
|
|
2042
|
+
}
|
|
2043
|
+
});
|
|
2044
|
+
var day2 = (iso) => iso.slice(0, 10);
|
|
2045
|
+
async function creditLine(client, me2) {
|
|
2046
|
+
let b;
|
|
2047
|
+
try {
|
|
2048
|
+
b = await client.balance();
|
|
2049
|
+
} catch (err) {
|
|
2050
|
+
if (err instanceof KvboxError) return void 0;
|
|
2051
|
+
throw err;
|
|
2052
|
+
}
|
|
2053
|
+
const whose = b.slug === me2 ? "private" : b.slug;
|
|
2054
|
+
const left = money(b.balance, b.currency);
|
|
2055
|
+
const line = `credit: ${b.balance > 0 ? left : color.red(left)} left ${color.dim(`(${whose})`)}`;
|
|
2056
|
+
return b.suspendedAt ? `${line} ${color.red("out of credit: boxes hibernated until you top up")}` : line;
|
|
2057
|
+
}
|
|
2058
|
+
function ownerCell2(b, me2) {
|
|
2059
|
+
const slug = slugOf(b.owner);
|
|
2060
|
+
if (slug === me2) return "private";
|
|
2061
|
+
return b.ownerKind === void 0 ? slug : `${slug} ${color.dim(b.ownerKind)}`;
|
|
2062
|
+
}
|
|
2063
|
+
function usageRows(report, cloud) {
|
|
2064
|
+
const priced = report.totals.cost !== void 0;
|
|
2065
|
+
const rows = report.boxes.map((b) => ({
|
|
2066
|
+
...cloud ? { owner: ownerCell2(b, cloud.me) } : {},
|
|
2067
|
+
name: boxName(b.name),
|
|
2068
|
+
status: b.status === "removed" ? color.dim("removed") : status(b.status),
|
|
2069
|
+
running: duration(b.seconds.running),
|
|
2070
|
+
"vcpu-h": hours(b.vcpuHours),
|
|
2071
|
+
"ram GiB-h": hours(b.ramGibHours),
|
|
2072
|
+
"disk GiB-h": hours(b.diskGibHours),
|
|
2073
|
+
...priced ? { cost: money(b.cost?.amount ?? 0, report.totals.cost.currency) } : {}
|
|
2074
|
+
}));
|
|
2075
|
+
const t = report.totals;
|
|
2076
|
+
rows.push({
|
|
2077
|
+
...cloud ? { owner: "" } : {},
|
|
2078
|
+
name: color.bold("total"),
|
|
2079
|
+
status: "",
|
|
2080
|
+
running: duration(t.seconds.running),
|
|
2081
|
+
"vcpu-h": hours(t.vcpuHours),
|
|
2082
|
+
"ram GiB-h": hours(t.ramGibHours),
|
|
2083
|
+
"disk GiB-h": hours(t.diskGibHours),
|
|
2084
|
+
...priced ? { cost: `${money(t.cost.amount, t.cost.currency)} ${color.dim("(estimated)")}` } : {}
|
|
2085
|
+
});
|
|
2086
|
+
return rows;
|
|
2087
|
+
}
|
|
2088
|
+
|
|
1928
2089
|
// src/program.ts
|
|
1929
2090
|
function buildProgram() {
|
|
1930
|
-
const program = new
|
|
2091
|
+
const program = new Command38("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();
|
|
1931
2092
|
program.addCommand(vmCommand);
|
|
1932
2093
|
program.addCommand(snapshotsCommand);
|
|
1933
2094
|
program.addCommand(serverCommand);
|
|
@@ -1935,6 +2096,7 @@ function buildProgram() {
|
|
|
1935
2096
|
program.addCommand(logoutCommand);
|
|
1936
2097
|
program.addCommand(whoamiCommand);
|
|
1937
2098
|
program.addCommand(orgCommand);
|
|
2099
|
+
program.addCommand(usageCommand2);
|
|
1938
2100
|
for (const cmd of vmAliases) program.addCommand(cmd);
|
|
1939
2101
|
return program;
|
|
1940
2102
|
}
|