@kvbox/cli 0.5.6 → 0.5.8
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 +100 -13
- package/dist/kvbox.js.map +1 -1
- package/package.json +1 -1
package/dist/kvbox.js
CHANGED
|
@@ -147,7 +147,7 @@ async function spin(message, work, opts = {}) {
|
|
|
147
147
|
}
|
|
148
148
|
|
|
149
149
|
// src/program.ts
|
|
150
|
-
import { Command as
|
|
150
|
+
import { Command as Command39 } 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.8" : void 0);
|
|
166
166
|
|
|
167
167
|
// src/commands/vm/index.ts
|
|
168
168
|
import { Command as Command15 } from "commander";
|
|
@@ -550,6 +550,8 @@ var ApiErrorSchema = z2.object({
|
|
|
550
550
|
error: z2.string()
|
|
551
551
|
});
|
|
552
552
|
var DEFAULT_CLOUD_URL = "https://api.kvbox.sh";
|
|
553
|
+
var ACTIVATED_BY = ["card", "admin"];
|
|
554
|
+
var ActivatedBySchema = z2.enum(ACTIVATED_BY);
|
|
553
555
|
var CloudUserSchema = z2.object({
|
|
554
556
|
/** The handle: what hosts file boxes under (`<username>@<control plane>`). Lowercase, no `@` or `/`. */
|
|
555
557
|
username: z2.string(),
|
|
@@ -558,7 +560,14 @@ var CloudUserSchema = z2.object({
|
|
|
558
560
|
/** Login identity, unique. */
|
|
559
561
|
email: z2.string(),
|
|
560
562
|
role: z2.enum(["admin", "user"]),
|
|
561
|
-
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)
|
|
562
571
|
});
|
|
563
572
|
var ORG_ROLES = ["owner", "admin", "member"];
|
|
564
573
|
var OrgRoleSchema = z2.enum(ORG_ROLES);
|
|
@@ -566,7 +575,10 @@ var CloudOrgSchema = z2.object({
|
|
|
566
575
|
slug: z2.string(),
|
|
567
576
|
/** Display name; the slug never changes, this may. */
|
|
568
577
|
name: z2.string(),
|
|
569
|
-
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)
|
|
570
582
|
});
|
|
571
583
|
var OrgMembershipSchema = CloudOrgSchema.extend({ role: OrgRoleSchema });
|
|
572
584
|
var OrgMemberSchema = z2.object({
|
|
@@ -587,6 +599,32 @@ var AddMemberRequestSchema = z2.object({
|
|
|
587
599
|
});
|
|
588
600
|
var SetRoleRequestSchema = z2.object({ role: OrgRoleSchema });
|
|
589
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
|
+
});
|
|
590
628
|
var LoginRequestSchema = z2.object({
|
|
591
629
|
email: z2.string().min(1),
|
|
592
630
|
password: z2.string().min(1),
|
|
@@ -735,11 +773,11 @@ var ServerClient = class {
|
|
|
735
773
|
base;
|
|
736
774
|
autoStart;
|
|
737
775
|
startedOnce = false;
|
|
738
|
-
async call(method, path, schema, body) {
|
|
776
|
+
async call(method, path, schema, body, org = this.target.kind === "cloud" ? this.target.org : void 0) {
|
|
739
777
|
const headers = {};
|
|
740
778
|
if (body !== void 0) headers["content-type"] = "application/json";
|
|
741
779
|
if (this.target.kind === "cloud" && this.target.token !== "") headers["authorization"] = `Bearer ${this.target.token}`;
|
|
742
|
-
if (this.target.kind === "cloud" &&
|
|
780
|
+
if (this.target.kind === "cloud" && org !== void 0) headers["x-kvbox-org"] = org;
|
|
743
781
|
let res;
|
|
744
782
|
try {
|
|
745
783
|
res = await request(this.base + path, {
|
|
@@ -828,6 +866,10 @@ var ServerClient = class {
|
|
|
828
866
|
const query = q.size === 0 ? "" : `?${q.toString()}`;
|
|
829
867
|
return this.call("GET", `/usage${query}`, UsageReportSchema);
|
|
830
868
|
}
|
|
869
|
+
/** The prepaid balance of the caller's slug, or of `org` (default: `--org`'s) (#76); a control plane only. */
|
|
870
|
+
balance(org) {
|
|
871
|
+
return this.call("GET", "/billing/balance", BalanceSchema, void 0, org);
|
|
872
|
+
}
|
|
831
873
|
deleteMachine(name) {
|
|
832
874
|
return this.call("DELETE", `/machines/${encodeURIComponent(name)}`, z3.undefined());
|
|
833
875
|
}
|
|
@@ -1387,9 +1429,10 @@ var usageCommand = new Command13("usage").description("how long a box ran, slept
|
|
|
1387
1429
|
var day = (iso) => iso.slice(0, 10);
|
|
1388
1430
|
var SYMBOLS = { EUR: "\u20AC", USD: "$", GBP: "\xA3" };
|
|
1389
1431
|
function money(amount, currency) {
|
|
1390
|
-
const
|
|
1432
|
+
const sign = amount < 0 ? "-" : "";
|
|
1433
|
+
const n = Math.abs(amount).toFixed(2);
|
|
1391
1434
|
const sym = SYMBOLS[currency.toUpperCase()];
|
|
1392
|
-
return sym === void 0 ? `${n} ${currency}` : `${sym}${n}`;
|
|
1435
|
+
return sym === void 0 ? `${sign}${n} ${currency}` : `${sign}${sym}${n}`;
|
|
1393
1436
|
}
|
|
1394
1437
|
function block(u) {
|
|
1395
1438
|
const estimated = u.estimatedStops === 0 ? "" : color.dim(` (${u.estimatedStops} stop time${u.estimatedStops === 1 ? "" : "s"} estimated)`);
|
|
@@ -1988,16 +2031,30 @@ var usageCommand2 = new Command37("usage").description("what your boxes used thi
|
|
|
1988
2031
|
}
|
|
1989
2032
|
const cloud = client.target.kind === "cloud" ? { me: client.target.user } : void 0;
|
|
1990
2033
|
info(`${day2(report.window.from)} to ${day2(report.window.to)}`);
|
|
1991
|
-
if (report.boxes.length === 0)
|
|
1992
|
-
|
|
1993
|
-
|
|
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);
|
|
1994
2039
|
}
|
|
1995
|
-
table(usageRows(report, cloud));
|
|
1996
2040
|
} finally {
|
|
1997
2041
|
await client.close();
|
|
1998
2042
|
}
|
|
1999
2043
|
});
|
|
2000
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
|
+
}
|
|
2001
2058
|
function ownerCell2(b, me2) {
|
|
2002
2059
|
const slug = slugOf(b.owner);
|
|
2003
2060
|
if (slug === me2) return "private";
|
|
@@ -2029,9 +2086,38 @@ function usageRows(report, cloud) {
|
|
|
2029
2086
|
return rows;
|
|
2030
2087
|
}
|
|
2031
2088
|
|
|
2089
|
+
// src/commands/credit.ts
|
|
2090
|
+
import { Command as Command38 } from "commander";
|
|
2091
|
+
var creditCommand = new Command38("credit").description("the prepaid credit left on your account and on each of your orgs").option("--org <slug>", "one org only").option("--json", "print as JSON").action(async (opts, cmd) => {
|
|
2092
|
+
const client = await clientFor(cmd.optsWithGlobals(), { autoStart: false });
|
|
2093
|
+
try {
|
|
2094
|
+
if (client.target.kind === "local") throw new KvboxError("credit lives on the control plane: kvbox login first");
|
|
2095
|
+
const me2 = client.target.user;
|
|
2096
|
+
const balances = client.target.org !== void 0 ? [await client.balance()] : await Promise.all([void 0, ...(await client.listOrgs()).map((o) => o.slug)].map((org) => client.balance(org)));
|
|
2097
|
+
if (opts.json) {
|
|
2098
|
+
json(balances);
|
|
2099
|
+
return;
|
|
2100
|
+
}
|
|
2101
|
+
table(balances.map((b) => creditRow(b, me2)));
|
|
2102
|
+
} finally {
|
|
2103
|
+
await client.close();
|
|
2104
|
+
}
|
|
2105
|
+
});
|
|
2106
|
+
function creditRow(b, me2) {
|
|
2107
|
+
const left = money(b.balance, b.currency);
|
|
2108
|
+
return {
|
|
2109
|
+
owner: b.slug === me2 ? "private" : boxName(b.slug),
|
|
2110
|
+
credit: b.balance > 0 ? left : color.red(left),
|
|
2111
|
+
"topped up": money(b.topUps, b.currency),
|
|
2112
|
+
spent: money(b.spent, b.currency),
|
|
2113
|
+
// Suspended (#76): the sweep hibernated the boxes at zero; a top-up brings them back.
|
|
2114
|
+
state: b.suspendedAt ? color.red("suspended") : b.since === null ? color.dim("never topped up") : "ok"
|
|
2115
|
+
};
|
|
2116
|
+
}
|
|
2117
|
+
|
|
2032
2118
|
// src/program.ts
|
|
2033
2119
|
function buildProgram() {
|
|
2034
|
-
const program = new
|
|
2120
|
+
const program = new Command39("kvbox").description("self-hosted Firecracker microVMs with Docker-like ergonomics").version(VERSION, "-v, --version").option("--local", "use this machine's kvbox-server over its socket, ignoring kvbox login").option("--org <slug>", "the org: where kvbox new creates, and which box a name means when it is not one of yours; before the command here, or after it").showHelpAfterError().enablePositionalOptions();
|
|
2035
2121
|
program.addCommand(vmCommand);
|
|
2036
2122
|
program.addCommand(snapshotsCommand);
|
|
2037
2123
|
program.addCommand(serverCommand);
|
|
@@ -2040,6 +2126,7 @@ function buildProgram() {
|
|
|
2040
2126
|
program.addCommand(whoamiCommand);
|
|
2041
2127
|
program.addCommand(orgCommand);
|
|
2042
2128
|
program.addCommand(usageCommand2);
|
|
2129
|
+
program.addCommand(creditCommand);
|
|
2043
2130
|
for (const cmd of vmAliases) program.addCommand(cmd);
|
|
2044
2131
|
return program;
|
|
2045
2132
|
}
|