@kvbox/cli 0.5.6 → 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 CHANGED
@@ -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.6" : void 0);
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";
@@ -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),
@@ -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`'s (#76); a control plane only. */
870
+ balance() {
871
+ return this.call("GET", "/billing/balance", BalanceSchema);
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 n = amount.toFixed(2);
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
- info("no usage in this window");
1993
- return;
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";