@videan/cli 0.3.1 → 0.4.1

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/index.js CHANGED
@@ -14,6 +14,9 @@ import os from "node:os";
14
14
  var DEFAULT_API_URL = "https://api.videan.xyz/v1";
15
15
  var APP_DOMAIN = process.env.CLOUD_APP_DOMAIN || "videan.xyz";
16
16
  var MAX_FILE_SIZE_BYTES = 50 * 1024 * 1024;
17
+ var APP_PORT = 3e3;
18
+ var LAMBDA_ADAPTER = `COPY --from=public.ecr.aws/awsguru/aws-lambda-adapter:0.9.1 /lambda-adapter /opt/extensions/lambda-adapter
19
+ ENV AWS_LWA_PORT=${APP_PORT}`;
17
20
  var FARGATE_SIZES = {
18
21
  256: { min: 512, max: 2048, step: 512 },
19
22
  512: { min: 1024, max: 4096, step: 1024 },
@@ -170,7 +173,9 @@ var AppsNamespace = class {
170
173
  storage_enabled: opts.storage,
171
174
  email_enabled: opts.email,
172
175
  auth_required: opts.authRequired,
173
- visibility: opts.visibility
176
+ visibility: opts.visibility,
177
+ stack: opts.stack,
178
+ mode: opts.mode
174
179
  };
175
180
  const res = await this.client.request("POST", "/apps", body);
176
181
  return res.data;
@@ -199,6 +204,8 @@ var AppsNamespace = class {
199
204
  if (changes.postgres !== void 0) body.postgres_enabled = changes.postgres;
200
205
  if (changes.storage !== void 0) body.storage_enabled = changes.storage;
201
206
  if (changes.email !== void 0) body.email_enabled = changes.email;
207
+ if (changes.stack !== void 0) body.stack = changes.stack;
208
+ if (changes.mode !== void 0) body.mode = changes.mode;
202
209
  const res = await this.client.request(
203
210
  "PATCH",
204
211
  `/apps/${appId}`,
@@ -677,6 +684,12 @@ var OrgsNamespace = class {
677
684
  }
678
685
  };
679
686
 
687
+ // ../shared/src/types.ts
688
+ var APP_MODES = ["container", "serverless"];
689
+ function isAppMode(value) {
690
+ return APP_MODES.includes(value);
691
+ }
692
+
680
693
  // ../shared/src/constants.ts
681
694
  var SUPPORTED_RUNTIMES = [
682
695
  "node-20",
@@ -694,6 +707,11 @@ function appUrl(slug) {
694
707
  var CONFIG_FILE_NAME = "cloud.yaml";
695
708
  var MAX_FILE_SIZE_BYTES2 = 50 * 1024 * 1024;
696
709
 
710
+ // ../shared/src/detect.ts
711
+ var APP_PORT2 = 3e3;
712
+ var LAMBDA_ADAPTER2 = `COPY --from=public.ecr.aws/awsguru/aws-lambda-adapter:0.9.1 /lambda-adapter /opt/extensions/lambda-adapter
713
+ ENV AWS_LWA_PORT=${APP_PORT2}`;
714
+
697
715
  // ../shared/src/sizing.ts
698
716
  var FARGATE_SIZES2 = {
699
717
  256: { min: 512, max: 2048, step: 512 },
@@ -715,7 +733,11 @@ var PLANS2 = {
715
733
  includedUsageCents: 300,
716
734
  maxApps: 3,
717
735
  maxInstancesPerApp: 1,
718
- // One app at this size, running all month, is inside the included usage.
736
+ // A *ceiling*, not a promise of free compute: a container task at even
737
+ // 256/512 running all month is ~$16 of usage against the $3 allowance.
738
+ // That is why apps created on this plan default to serverless mode, which
739
+ // meters nothing while idle -- the free tier is "free while it sleeps",
740
+ // and a container app on this plan is a deliberate, metered choice.
719
741
  maxCpu: 512,
720
742
  maxMemory: 1024,
721
743
  maxStorageMb: 1024,
@@ -965,6 +987,15 @@ This platform puts a sign-in gate in front of apps unless they are public.
965
987
 
966
988
  Secrets: \`videan secrets set KEY=value\` (UPPERCASE keys) \u2014 injected as env
967
989
  vars on the next deploy. Never commit credentials to this repo instead.
990
+
991
+ ## Execution modes
992
+
993
+ Apps run as a **container** (default: always on, no cold starts) or
994
+ **serverless** (scales to zero, cold start after idle \u2014 good for internal
995
+ tools with sporadic traffic). Set at create (\`videan apps create --mode
996
+ serverless\`) or later (\`videan apps set --mode \u2026\`, applied by the next
997
+ deploy). Serverless refuses cron, background jobs, WebSockets and custom
998
+ Dockerfiles; container mode supports all of them.
968
999
  `;
969
1000
  var POINTER = "\n## Videan\n\nThis project deploys to Videan \u2014 see VIDEAN.md for the platform contract (port 3000, auth headers, deploy commands) before writing server or login code.\n";
970
1001
  async function writeVideanMd(dir) {
@@ -1436,39 +1467,79 @@ var rollbackCommand = new Command8("rollback").description("Rollback to the prev
1436
1467
 
1437
1468
  // src/commands/secrets.ts
1438
1469
  import { Command as Command9 } from "commander";
1439
- import chalk10 from "chalk";
1470
+ import chalk11 from "chalk";
1471
+
1472
+ // src/prompt.ts
1440
1473
  import inquirer3 from "inquirer";
1474
+ import chalk10 from "chalk";
1475
+ async function prompt(questions, nonInteractiveHint) {
1476
+ if (!process.stdin.isTTY) {
1477
+ console.error(
1478
+ chalk10.red(`This command needs a terminal to prompt. ${nonInteractiveHint}`)
1479
+ );
1480
+ process.exit(1);
1481
+ }
1482
+ try {
1483
+ return await inquirer3.prompt(questions);
1484
+ } catch (err) {
1485
+ if (isStreamClosed(err)) {
1486
+ console.error(chalk10.red(`Input ended before the prompt was answered. ${nonInteractiveHint}`));
1487
+ process.exit(1);
1488
+ }
1489
+ throw err;
1490
+ }
1491
+ }
1492
+ function isStreamClosed(err) {
1493
+ const e = err;
1494
+ return e?.code === "ERR_USE_AFTER_CLOSE" || /closed/i.test(e?.message ?? "");
1495
+ }
1496
+ function installPromptCrashGuard() {
1497
+ process.on("uncaughtException", (err) => {
1498
+ if (isStreamClosed(err)) {
1499
+ console.error(
1500
+ chalk10.red(
1501
+ "Input stream closed mid-prompt. Re-run with the answers as flags \u2014 every prompt has one (see --help)."
1502
+ )
1503
+ );
1504
+ process.exit(1);
1505
+ }
1506
+ throw err;
1507
+ });
1508
+ }
1509
+
1510
+ // src/commands/secrets.ts
1441
1511
  var secretsCommand = new Command9("secrets").description("Manage app secrets");
1442
1512
  secretsCommand.command("set <key> [value]").description("Set a secret").option("-a, --app <id>", "App ID").action(async (key, value, opts) => {
1443
1513
  const apiKey = getApiKey();
1444
1514
  requireApiKey(apiKey);
1445
1515
  const appId = opts.app || getCurrentApp();
1446
1516
  if (!appId) {
1447
- console.error(chalk10.red("No app specified."));
1517
+ console.error(chalk11.red("No app specified."));
1448
1518
  process.exit(1);
1449
1519
  }
1450
1520
  if (!value) {
1451
- const answers = await inquirer3.prompt([
1452
- { type: "password", name: "value", message: `Value for ${key}:` }
1453
- ]);
1521
+ const answers = await prompt(
1522
+ [{ type: "password", name: "value", message: `Value for ${key}:` }],
1523
+ `Pass the value as an argument: videan secrets set ${key} <value>`
1524
+ );
1454
1525
  value = answers.value;
1455
1526
  }
1456
1527
  const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
1457
1528
  await client4.secrets.set(appId, key, value);
1458
- console.log(chalk10.green(`Secret "${key}" set successfully.`));
1529
+ console.log(chalk11.green(`Secret "${key}" set successfully.`));
1459
1530
  });
1460
1531
  secretsCommand.command("list").description("List all secrets").option("-a, --app <id>", "App ID").action(async (opts) => {
1461
1532
  const apiKey = getApiKey();
1462
1533
  requireApiKey(apiKey);
1463
1534
  const appId = opts.app || getCurrentApp();
1464
1535
  if (!appId) {
1465
- console.error(chalk10.red("No app specified."));
1536
+ console.error(chalk11.red("No app specified."));
1466
1537
  process.exit(1);
1467
1538
  }
1468
1539
  const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
1469
1540
  const secrets = await client4.secrets.list(appId);
1470
1541
  if (secrets.length === 0) {
1471
- console.log(chalk10.gray("No secrets configured."));
1542
+ console.log(chalk11.gray("No secrets configured."));
1472
1543
  return;
1473
1544
  }
1474
1545
  console.log(formatRow(["KEY", "UPDATED"], [30, 24]));
@@ -1481,28 +1552,36 @@ secretsCommand.command("remove <key>").description("Remove a secret").option("-a
1481
1552
  requireApiKey(apiKey);
1482
1553
  const appId = opts.app || getCurrentApp();
1483
1554
  if (!appId) {
1484
- console.error(chalk10.red("No app specified."));
1555
+ console.error(chalk11.red("No app specified."));
1485
1556
  process.exit(1);
1486
1557
  }
1487
1558
  const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
1488
1559
  await client4.secrets.delete(appId, key);
1489
- console.log(chalk10.green(`Secret "${key}" removed.`));
1560
+ console.log(chalk11.green(`Secret "${key}" removed.`));
1490
1561
  });
1491
1562
 
1492
1563
  // src/commands/apps.ts
1493
1564
  import { Command as Command10 } from "commander";
1494
- import chalk11 from "chalk";
1495
- import inquirer4 from "inquirer";
1565
+ import chalk12 from "chalk";
1496
1566
  var appsCommand = new Command10("apps").description("Manage apps");
1497
1567
  function accessOption(value) {
1498
1568
  if (value === void 0) return void 0;
1499
1569
  const tier = value.trim().toLowerCase();
1500
1570
  if (!isVisibility(tier)) {
1501
- console.error(chalk11.red(`Unknown --access value "${value}". Use one of: ${VISIBILITIES.join(", ")}`));
1571
+ console.error(chalk12.red(`Unknown --access value "${value}". Use one of: ${VISIBILITIES.join(", ")}`));
1502
1572
  process.exit(1);
1503
1573
  }
1504
1574
  return tier;
1505
1575
  }
1576
+ function modeOption(value) {
1577
+ if (value === void 0) return void 0;
1578
+ const mode = value.trim().toLowerCase();
1579
+ if (!isAppMode(mode)) {
1580
+ console.error(chalk12.red(`Unknown --mode value "${value}". Use one of: ${APP_MODES.join(", ")}`));
1581
+ process.exit(1);
1582
+ }
1583
+ return mode;
1584
+ }
1506
1585
  function describeAccess(app) {
1507
1586
  const tier = app.visibility ? visibilityOf(app.visibility) : app.authRequired ? "org" : "public";
1508
1587
  return `${tier} \u2014 ${VISIBILITY_LABELS[tier].toLowerCase()}`;
@@ -1514,7 +1593,7 @@ appsCommand.command("list").description("List all apps").option("-o, --org <id>"
1514
1593
  const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
1515
1594
  const apps = await client4.apps.list(orgId);
1516
1595
  if (apps.length === 0) {
1517
- console.log(chalk11.gray("No apps yet. Create one with `videan apps create`."));
1596
+ console.log(chalk12.gray("No apps yet. Create one with `videan apps create`."));
1518
1597
  return;
1519
1598
  }
1520
1599
  const widths = [20, 24, 10, 44];
@@ -1523,23 +1602,31 @@ appsCommand.command("list").description("List all apps").option("-o, --org <id>"
1523
1602
  console.log(formatRow([app.name, app.id, app.status, appUrl(app.slug)], widths));
1524
1603
  }
1525
1604
  });
1526
- appsCommand.command("create").description("Create a new app").option("-n, --name <name>", "App name").option("-o, --org <id>", "Organization ID").option("--postgres", "Provision a managed Postgres database").option("--storage", "Give the app an object-storage prefix").option("--email", "Let the app send mail through the platform").option("--public", "Serve the app without the platform login gate (same as --access public)").option("--access <tier>", `Who may open the app: ${VISIBILITIES.join(" | ")} (default: ${DEFAULT_VISIBILITY})`).action(
1605
+ appsCommand.command("create").description("Create a new app").option("-n, --name <name>", "App name").option("-o, --org <id>", "Organization ID").option("--postgres", "Provision a managed Postgres database").option("--storage", "Give the app an object-storage prefix").option("--email", "Let the app send mail through the platform").option("--public", "Serve the app without the platform login gate (same as --access public)").option("--access <tier>", `Who may open the app: ${VISIBILITIES.join(" | ")} (default: ${DEFAULT_VISIBILITY})`).option(
1606
+ "--stack <label>",
1607
+ "Group this app with its siblings (set the same label on a split frontend and backend)"
1608
+ ).option(
1609
+ "--mode <mode>",
1610
+ `How the app runs: ${APP_MODES.join(" | ")} (default: container). serverless scales to zero but runs no cron, jobs or WebSockets`
1611
+ ).action(
1527
1612
  async (opts) => {
1528
1613
  const apiKey = getApiKey();
1529
1614
  requireApiKey(apiKey);
1530
1615
  const orgId = requireOrg(opts.org || getDefaultOrg());
1531
1616
  let name = opts.name;
1532
1617
  if (!name) {
1533
- const answers = await inquirer4.prompt([
1534
- { type: "input", name: "name", message: "App name:" }
1535
- ]);
1618
+ const answers = await prompt(
1619
+ [{ type: "input", name: "name", message: "App name:" }],
1620
+ "Pass the name: videan apps create --name <name>"
1621
+ );
1536
1622
  name = answers.name;
1537
1623
  }
1538
1624
  if (!name) {
1539
- console.error(chalk11.red("An app name is required."));
1625
+ console.error(chalk12.red("An app name is required."));
1540
1626
  process.exit(1);
1541
1627
  }
1542
1628
  const visibility = accessOption(opts.access) ?? (opts.public ? "public" : void 0);
1629
+ const mode = modeOption(opts.mode);
1543
1630
  const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
1544
1631
  const app = await client4.apps.create({
1545
1632
  name,
@@ -1547,35 +1634,40 @@ appsCommand.command("create").description("Create a new app").option("-n, --name
1547
1634
  postgres: opts.postgres ?? false,
1548
1635
  storage: opts.storage ?? false,
1549
1636
  email: opts.email ?? false,
1550
- visibility
1637
+ visibility,
1638
+ stack: opts.stack,
1639
+ mode
1551
1640
  });
1552
1641
  setCurrentApp(app.id);
1553
- console.log(chalk11.green(`App "${app.name}" created (${app.id})`));
1554
- console.log(chalk11.gray(` Access: ${describeAccess(app)}`));
1555
- console.log(chalk11.gray(` URL after first deploy: ${appUrl(app.slug)}`));
1556
- console.log(chalk11.gray(" Push code with `videan deploy` \u2014 the runtime is auto-detected."));
1642
+ console.log(chalk12.green(`App "${app.name}" created (${app.id})`));
1643
+ console.log(chalk12.gray(` Mode: ${app.mode ?? "container"}`));
1644
+ console.log(chalk12.gray(` Access: ${describeAccess(app)}`));
1645
+ console.log(chalk12.gray(` URL after first deploy: ${appUrl(app.slug)}`));
1646
+ console.log(chalk12.gray(" Push code with `videan deploy` \u2014 the runtime is auto-detected."));
1557
1647
  }
1558
1648
  );
1559
- appsCommand.command("set").description("Change an app's settings (scale, rate limit, gate, capabilities)").option("-a, --app <id>", "App ID").option("-n, --name <name>", "Rename the app (the URL keeps its original slug)").option("--cpu <units>", "CPU units per instance (256 = 0.25 vCPU)").option("--memory <mib>", "Memory per instance, in MiB").option("--instances <n>", "Number of instances to run").option("--rate-limit <rpm>", "Requests per minute per client").option("--no-rate-limit", "Remove the rate limit").option("--access <tier>", `Who may open the app: ${VISIBILITIES.join(" | ")}`).option("--public", "Serve without the platform login gate (same as --access public)").option("--private", "Require platform login (leaves the tier alone if it is already gated)").option("--postgres", "Provision a managed Postgres database").option("--storage", "Give the app an object-storage prefix").option("--email", "Let the app send mail through the platform").option("--json", "Print the raw response").action(
1649
+ appsCommand.command("set").description("Change an app's settings (scale, rate limit, gate, capabilities)").option("-a, --app <id>", "App ID").option("-n, --name <name>", "Rename the app (the URL keeps its original slug)").option("--cpu <units>", "CPU units per instance (256 = 0.25 vCPU)").option("--memory <mib>", "Memory per instance, in MiB").option("--instances <n>", "Number of instances to run").option("--rate-limit <rpm>", "Requests per minute per client").option("--no-rate-limit", "Remove the rate limit").option("--access <tier>", `Who may open the app: ${VISIBILITIES.join(" | ")}`).option("--public", "Serve without the platform login gate (same as --access public)").option("--private", "Require platform login (leaves the tier alone if it is already gated)").option("--postgres", "Provision a managed Postgres database").option("--storage", "Give the app an object-storage prefix").option("--email", "Let the app send mail through the platform").option("--stack <label>", "Group this app with its siblings under a shared label").option("--no-stack", "Remove the app from its stack").option("--mode <mode>", `How the app runs: ${APP_MODES.join(" | ")} (applies on the next deploy or restart)`).option("--json", "Print the raw response").action(
1560
1650
  async (opts) => {
1561
1651
  const apiKey = getApiKey();
1562
1652
  requireApiKey(apiKey);
1563
1653
  const appId = opts.app || getCurrentApp();
1564
1654
  if (!appId) {
1565
- console.error(chalk11.red("No app specified. Use --app or link an app."));
1655
+ console.error(chalk12.red("No app specified. Use --app or link an app."));
1566
1656
  process.exit(1);
1567
1657
  }
1568
1658
  if (opts.public && opts.private) {
1569
- console.error(chalk11.red("--public and --private contradict each other."));
1659
+ console.error(chalk12.red("--public and --private contradict each other."));
1570
1660
  process.exit(1);
1571
1661
  }
1572
1662
  const access = accessOption(opts.access);
1573
1663
  if (access && (opts.public || opts.private)) {
1574
- console.error(chalk11.red("--access already says who may open the app; drop --public/--private."));
1664
+ console.error(chalk12.red("--access already says who may open the app; drop --public/--private."));
1575
1665
  process.exit(1);
1576
1666
  }
1667
+ const mode = modeOption(opts.mode);
1577
1668
  const changes = {};
1578
1669
  if (opts.name) changes.name = opts.name;
1670
+ if (mode) changes.mode = mode;
1579
1671
  if (opts.cpu !== void 0) changes.cpu = intOption("--cpu", opts.cpu);
1580
1672
  if (opts.memory !== void 0) changes.memory = intOption("--memory", opts.memory);
1581
1673
  if (opts.instances !== void 0) {
@@ -1591,8 +1683,10 @@ appsCommand.command("set").description("Change an app's settings (scale, rate li
1591
1683
  if (opts.postgres) changes.postgres = true;
1592
1684
  if (opts.storage) changes.storage = true;
1593
1685
  if (opts.email) changes.email = true;
1686
+ if (opts.stack === false) changes.stack = null;
1687
+ else if (typeof opts.stack === "string") changes.stack = opts.stack;
1594
1688
  if (Object.keys(changes).length === 0) {
1595
- console.error(chalk11.red("Nothing to change. Pass at least one option."));
1689
+ console.error(chalk12.red("Nothing to change. Pass at least one option."));
1596
1690
  process.exit(1);
1597
1691
  }
1598
1692
  const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
@@ -1601,7 +1695,8 @@ appsCommand.command("set").description("Change an app's settings (scale, rate li
1601
1695
  console.log(JSON.stringify(app, null, 2));
1602
1696
  return;
1603
1697
  }
1604
- console.log(chalk11.green(`Updated ${app.name} (${app.id})`));
1698
+ console.log(chalk12.green(`Updated ${app.name} (${app.id})`));
1699
+ console.log(` Mode: ${app.mode ?? "container"}`);
1605
1700
  console.log(` Access: ${describeAccess(app)}`);
1606
1701
  console.log(` Scale: ${app.cpu ?? "default"} CPU / ${app.memory ?? "default"} MiB`);
1607
1702
  console.log(
@@ -1609,7 +1704,7 @@ appsCommand.command("set").description("Change an app's settings (scale, rate li
1609
1704
  );
1610
1705
  if (app.restartRequired) {
1611
1706
  console.log(
1612
- chalk11.yellow("\n These settings apply to the next deploy. Run `videan deploy` to pick them up.")
1707
+ chalk12.yellow("\n These settings apply to the next deploy. Run `videan deploy` to pick them up.")
1613
1708
  );
1614
1709
  }
1615
1710
  }
@@ -1617,37 +1712,42 @@ appsCommand.command("set").description("Change an app's settings (scale, rate li
1617
1712
  function intOption(flag, raw) {
1618
1713
  const n = Number(raw);
1619
1714
  if (!Number.isInteger(n) || n <= 0) {
1620
- console.error(chalk11.red(`${flag} expects a positive whole number, got "${raw}".`));
1715
+ console.error(chalk12.red(`${flag} expects a positive whole number, got "${raw}".`));
1621
1716
  process.exit(1);
1622
1717
  }
1623
1718
  return n;
1624
1719
  }
1625
- appsCommand.command("delete <id>").description("Delete an app and all its resources").action(async (id) => {
1720
+ appsCommand.command("delete <id>").description("Delete an app and all its resources").option("--yes", "Skip the confirmation prompt").action(async (id, opts) => {
1626
1721
  const apiKey = getApiKey();
1627
1722
  requireApiKey(apiKey);
1628
- const { confirm } = await inquirer4.prompt([
1629
- {
1630
- type: "confirm",
1631
- name: "confirm",
1632
- message: `Delete app ${id}? This stops the app and destroys its database, secrets and snapshots.`,
1633
- default: false
1634
- }
1635
- ]);
1636
- if (!confirm) return;
1723
+ if (!opts.yes) {
1724
+ const { confirm } = await prompt(
1725
+ [
1726
+ {
1727
+ type: "confirm",
1728
+ name: "confirm",
1729
+ message: `Delete app ${id}? This stops the app and destroys its database, secrets and snapshots.`,
1730
+ default: false
1731
+ }
1732
+ ],
1733
+ "Confirm with a flag: videan apps delete <id> --yes"
1734
+ );
1735
+ if (!confirm) return;
1736
+ }
1637
1737
  const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
1638
1738
  const { warnings } = await client4.apps.delete(id);
1639
- console.log(chalk11.green("App deleted."));
1739
+ console.log(chalk12.green("App deleted."));
1640
1740
  if (warnings.length > 0) {
1641
- console.log(chalk11.yellow("\nSome resources could not be released:"));
1741
+ console.log(chalk12.yellow("\nSome resources could not be released:"));
1642
1742
  for (const w of warnings) {
1643
- console.log(chalk11.yellow(` ${w.step}: ${w.error || "unknown error"}`));
1743
+ console.log(chalk12.yellow(` ${w.step}: ${w.error || "unknown error"}`));
1644
1744
  }
1645
1745
  }
1646
1746
  });
1647
1747
 
1648
1748
  // src/commands/status.ts
1649
1749
  import { Command as Command11 } from "commander";
1650
- import chalk12 from "chalk";
1750
+ import chalk13 from "chalk";
1651
1751
  function accessLine(app) {
1652
1752
  const tier = app.visibility ? visibilityOf(app.visibility) : app.authRequired ? "org" : "public";
1653
1753
  return `${tier} \u2014 ${VISIBILITY_LABELS[tier].toLowerCase()}`;
@@ -1657,42 +1757,41 @@ var statusCommand = new Command11("status").description("Show current app status
1657
1757
  requireApiKey(apiKey);
1658
1758
  const appId = opts.app || getCurrentApp();
1659
1759
  if (!appId) {
1660
- console.error(chalk12.red("No app specified. Use --app or link an app."));
1760
+ console.error(chalk13.red("No app specified. Use --app or link an app."));
1661
1761
  process.exit(1);
1662
1762
  }
1663
1763
  const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
1664
1764
  const app = await client4.apps.get(appId);
1665
1765
  const health = await client4.apps.health(appId);
1666
- console.log(chalk12.bold(app.name));
1766
+ console.log(chalk13.bold(app.name));
1667
1767
  console.log(` ID: ${app.id}`);
1668
1768
  console.log(` Created: ${app.createdAt}`);
1669
1769
  console.log(` Postgres: ${app.postgresEnabled ? "enabled" : "disabled"}`);
1670
1770
  console.log(` Access: ${accessLine(app)}`);
1671
1771
  console.log(` State: ${formatStatus(health.state)}`);
1672
- console.log(` Healthy: ${health.healthy ? chalk12.green("yes") : chalk12.red("no")}`);
1673
- console.log(` URL: ${chalk12.cyan(health.url || appUrl(app.slug))}`);
1772
+ console.log(` Healthy: ${health.healthy ? chalk13.green("yes") : chalk13.red("no")}`);
1773
+ console.log(` URL: ${chalk13.cyan(health.url || appUrl(app.slug))}`);
1674
1774
  const task = health.task;
1675
1775
  if (task) {
1676
1776
  if (task.exit_code !== void 0 && task.exit_code !== null) {
1677
1777
  const code = String(task.exit_code);
1678
- console.log(` Exit code: ${task.exit_code === 0 ? code : chalk12.red(code)}`);
1778
+ console.log(` Exit code: ${task.exit_code === 0 ? code : chalk13.red(code)}`);
1679
1779
  }
1680
1780
  if (task.stopped_reason) {
1681
- console.log(` Stopped: ${chalk12.yellow(task.stopped_reason)}`);
1781
+ console.log(` Stopped: ${chalk13.yellow(task.stopped_reason)}`);
1682
1782
  }
1683
1783
  if (task.container_reason) {
1684
- console.log(` Detail: ${chalk12.yellow(task.container_reason)}`);
1784
+ console.log(` Detail: ${chalk13.yellow(task.container_reason)}`);
1685
1785
  }
1686
1786
  }
1687
1787
  if (!health.healthy) {
1688
- console.log(chalk12.gray("\nRun `videan logs` to see why."));
1788
+ console.log(chalk13.gray("\nRun `videan logs` to see why."));
1689
1789
  }
1690
1790
  });
1691
1791
 
1692
1792
  // src/commands/keys.ts
1693
1793
  import { Command as Command12 } from "commander";
1694
- import chalk13 from "chalk";
1695
- import inquirer5 from "inquirer";
1794
+ import chalk14 from "chalk";
1696
1795
  var keysCommand = new Command12("keys").description("Manage API keys for programmatic access");
1697
1796
  keysCommand.command("create <name>").description("Create an API key").option("-o, --org <id>", "Organization ID").option("-e, --expires <days>", "Expire the key after N days").option(
1698
1797
  "--app <id>",
@@ -1705,7 +1804,7 @@ keysCommand.command("create <name>").description("Create an API key").option("-o
1705
1804
  if (opts.expires !== void 0) {
1706
1805
  expiresInDays = Number(opts.expires);
1707
1806
  if (!Number.isInteger(expiresInDays) || expiresInDays < 1) {
1708
- console.error(chalk13.red("--expires must be a whole number of days, at least 1."));
1807
+ console.error(chalk14.red("--expires must be a whole number of days, at least 1."));
1709
1808
  process.exit(1);
1710
1809
  }
1711
1810
  }
@@ -1716,22 +1815,22 @@ keysCommand.command("create <name>").description("Create an API key").option("-o
1716
1815
  expiresInDays,
1717
1816
  appId: opts.app
1718
1817
  });
1719
- console.log(chalk13.green(`Created key "${key.name}" (${key.id})`));
1818
+ console.log(chalk14.green(`Created key "${key.name}" (${key.id})`));
1720
1819
  console.log(`
1721
- ${chalk13.bold(key.key)}
1820
+ ${chalk14.bold(key.key)}
1722
1821
  `);
1723
- console.log(chalk13.yellow("Copy it now. It cannot be shown again."));
1822
+ console.log(chalk14.yellow("Copy it now. It cannot be shown again."));
1724
1823
  if (key.appId) {
1725
1824
  console.log(
1726
- chalk13.gray(`Service token, scoped to app ${key.appId}. It cannot reach anything else.`)
1825
+ chalk14.gray(`Service token, scoped to app ${key.appId}. It cannot reach anything else.`)
1727
1826
  );
1728
1827
  } else {
1729
1828
  console.log(
1730
- chalk13.gray("Acts as you across every app in the org -- do not deploy it inside an app.")
1829
+ chalk14.gray("Acts as you across every app in the org -- do not deploy it inside an app.")
1731
1830
  );
1732
1831
  }
1733
1832
  if (key.expiresAt) {
1734
- console.log(chalk13.gray(`Expires ${key.expiresAt}`));
1833
+ console.log(chalk14.gray(`Expires ${key.expiresAt}`));
1735
1834
  }
1736
1835
  });
1737
1836
  keysCommand.command("list").description("List API keys").option("-o, --org <id>", "Organization ID").action(async (opts) => {
@@ -1741,13 +1840,13 @@ keysCommand.command("list").description("List API keys").option("-o, --org <id>"
1741
1840
  const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
1742
1841
  const keys = await client4.apiKeys.list(orgId);
1743
1842
  if (keys.length === 0) {
1744
- console.log(chalk13.gray("No API keys. Create one with `videan keys create <name>`."));
1843
+ console.log(chalk14.gray("No API keys. Create one with `videan keys create <name>`."));
1745
1844
  return;
1746
1845
  }
1747
1846
  const widths = [24, 24, 16, 22, 12, 24];
1748
1847
  console.log(formatRow(["NAME", "ID", "PREFIX", "LAST USED", "STATE", "SCOPE"], widths));
1749
1848
  for (const key of keys) {
1750
- const state = key.revokedAt ? chalk13.red("revoked") : key.expiresAt && new Date(key.expiresAt) < /* @__PURE__ */ new Date() ? chalk13.yellow("expired") : chalk13.green("active");
1849
+ const state = key.revokedAt ? chalk14.red("revoked") : key.expiresAt && new Date(key.expiresAt) < /* @__PURE__ */ new Date() ? chalk14.yellow("expired") : chalk14.green("active");
1751
1850
  console.log(formatRow(
1752
1851
  [
1753
1852
  key.name,
@@ -1761,21 +1860,24 @@ keysCommand.command("list").description("List API keys").option("-o, --org <id>"
1761
1860
  ));
1762
1861
  }
1763
1862
  });
1764
- keysCommand.command("revoke <id>").description("Revoke an API key").action(async (id) => {
1863
+ keysCommand.command("revoke <id>").description("Revoke an API key").option("--yes", "Skip the confirmation prompt").action(async (id, opts) => {
1765
1864
  const apiKey = getApiKey();
1766
1865
  requireApiKey(apiKey);
1767
- const { confirm } = await inquirer5.prompt([
1768
- { type: "confirm", name: "confirm", message: `Revoke key ${id}?`, default: false }
1769
- ]);
1770
- if (!confirm) return;
1866
+ if (!opts.yes) {
1867
+ const { confirm } = await prompt(
1868
+ [{ type: "confirm", name: "confirm", message: `Revoke key ${id}?`, default: false }],
1869
+ "Confirm with a flag: videan keys revoke <id> --yes"
1870
+ );
1871
+ if (!confirm) return;
1872
+ }
1771
1873
  const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
1772
1874
  await client4.apiKeys.revoke(id);
1773
- console.log(chalk13.green("Key revoked."));
1875
+ console.log(chalk14.green("Key revoked."));
1774
1876
  });
1775
1877
 
1776
1878
  // src/commands/orgs.ts
1777
1879
  import { Command as Command13 } from "commander";
1778
- import chalk14 from "chalk";
1880
+ import chalk15 from "chalk";
1779
1881
  var orgsCommand = new Command13("orgs").description("Manage organizations");
1780
1882
  orgsCommand.command("list").description("List your organizations").action(async () => {
1781
1883
  const apiKey = getApiKey();
@@ -1784,14 +1886,14 @@ orgsCommand.command("list").description("List your organizations").action(async
1784
1886
  const orgs = await client4.orgs.list();
1785
1887
  const current = getDefaultOrg();
1786
1888
  if (orgs.length === 0) {
1787
- console.log(chalk14.gray("No organizations. Create one with `videan orgs create <name>`."));
1889
+ console.log(chalk15.gray("No organizations. Create one with `videan orgs create <name>`."));
1788
1890
  return;
1789
1891
  }
1790
1892
  const widths = [24, 24, 10, 8];
1791
1893
  console.log(formatRow(["NAME", "ID", "PLAN", ""], widths));
1792
1894
  for (const org of orgs) {
1793
1895
  console.log(formatRow(
1794
- [org.name, org.id, org.plan, org.id === current ? chalk14.green("default") : ""],
1896
+ [org.name, org.id, org.plan, org.id === current ? chalk15.green("default") : ""],
1795
1897
  widths
1796
1898
  ));
1797
1899
  }
@@ -1802,7 +1904,7 @@ orgsCommand.command("create <name>").description("Create an organization").actio
1802
1904
  const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
1803
1905
  const org = await client4.orgs.create(name);
1804
1906
  if (!getDefaultOrg()) setDefaultOrg(org.id);
1805
- console.log(chalk14.green(`Organization "${org.name}" created (${org.id})`));
1907
+ console.log(chalk15.green(`Organization "${org.name}" created (${org.id})`));
1806
1908
  });
1807
1909
  orgsCommand.command("use <id>").description("Set the default organization for this machine").action(async (id) => {
1808
1910
  const apiKey = getApiKey();
@@ -1810,7 +1912,7 @@ orgsCommand.command("use <id>").description("Set the default organization for th
1810
1912
  const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
1811
1913
  const org = await client4.orgs.get(id);
1812
1914
  setDefaultOrg(org.id);
1813
- console.log(chalk14.green(`Default organization set to "${org.name}".`));
1915
+ console.log(chalk15.green(`Default organization set to "${org.name}".`));
1814
1916
  });
1815
1917
  orgsCommand.command("members").description("List organization members").option("-o, --org <id>", "Organization ID").action(async (opts) => {
1816
1918
  const apiKey = getApiKey();
@@ -1833,12 +1935,12 @@ orgsCommand.command("invite <email>").description("Invite a member by email").op
1833
1935
  const orgId = requireOrg(opts.org || getDefaultOrg());
1834
1936
  const client4 = new CloudClient({ apiKey, baseUrl: getBaseUrl() });
1835
1937
  await client4.orgs.invite(orgId, email);
1836
- console.log(chalk14.green(`Invited ${email}.`));
1938
+ console.log(chalk15.green(`Invited ${email}.`));
1837
1939
  });
1838
1940
 
1839
1941
  // src/commands/storage.ts
1840
1942
  import { Command as Command14 } from "commander";
1841
- import chalk15 from "chalk";
1943
+ import chalk16 from "chalk";
1842
1944
  import fs5 from "fs-extra";
1843
1945
  import path5 from "node:path";
1844
1946
  var storageCommand = new Command14("storage").description("Store files for an app");
@@ -1850,7 +1952,7 @@ function client() {
1850
1952
  function requireApp(opt) {
1851
1953
  const appId = opt || getCurrentApp();
1852
1954
  if (!appId) {
1853
- console.error(chalk15.red("No app specified. Use --app or link an app."));
1955
+ console.error(chalk16.red("No app specified. Use --app or link an app."));
1854
1956
  process.exit(1);
1855
1957
  }
1856
1958
  return appId;
@@ -1873,7 +1975,7 @@ storageCommand.command("ls [prefix]").description("List stored objects").option(
1873
1975
  return;
1874
1976
  }
1875
1977
  if (objects.length === 0) {
1876
- console.log(chalk15.gray("No objects stored. Upload one with `videan storage put <file>`."));
1978
+ console.log(chalk16.gray("No objects stored. Upload one with `videan storage put <file>`."));
1877
1979
  return;
1878
1980
  }
1879
1981
  const widths = [52, 12, 24];
@@ -1882,13 +1984,13 @@ storageCommand.command("ls [prefix]").description("List stored objects").option(
1882
1984
  console.log(formatRow([o.key, human(o.size), o.lastModified || "-"], widths));
1883
1985
  }
1884
1986
  const total = objects.reduce((sum, o) => sum + o.size, 0);
1885
- console.log(chalk15.dim(`
1987
+ console.log(chalk16.dim(`
1886
1988
  ${objects.length} object(s), ${human(total)}`));
1887
1989
  });
1888
1990
  storageCommand.command("put <file> [key]").description("Upload a file (key defaults to the file's name)").option("-a, --app <id>", "App ID").option("-t, --type <mime>", "Content type").action(async (file, key, opts) => {
1889
1991
  const appId = requireApp(opts.app);
1890
1992
  if (!await fs5.pathExists(file)) {
1891
- console.error(chalk15.red(`No such file: ${file}`));
1993
+ console.error(chalk16.red(`No such file: ${file}`));
1892
1994
  process.exit(1);
1893
1995
  }
1894
1996
  const body = await fs5.readFile(file);
@@ -1897,27 +1999,27 @@ storageCommand.command("put <file> [key]").description("Upload a file (key defau
1897
1999
  contentType: opts.type
1898
2000
  });
1899
2001
  console.log(
1900
- chalk15.green(`Uploaded ${objectKey}`) + chalk15.dim(` (${human(body.length)})`)
2002
+ chalk16.green(`Uploaded ${objectKey}`) + chalk16.dim(` (${human(body.length)})`)
1901
2003
  );
1902
- if (stored.etag) console.log(chalk15.dim(`etag ${stored.etag}`));
2004
+ if (stored.etag) console.log(chalk16.dim(`etag ${stored.etag}`));
1903
2005
  });
1904
2006
  storageCommand.command("get <key> [file]").description("Download an object (defaults to the key's basename)").option("-a, --app <id>", "App ID").action(async (key, file, opts) => {
1905
2007
  const appId = requireApp(opts.app);
1906
2008
  const bytes = await client().storage.download(appId, key);
1907
2009
  const dest = file || path5.basename(key);
1908
2010
  await fs5.writeFile(dest, bytes);
1909
- console.log(chalk15.green(`Wrote ${dest}`) + chalk15.dim(` (${human(bytes.length)})`));
2011
+ console.log(chalk16.green(`Wrote ${dest}`) + chalk16.dim(` (${human(bytes.length)})`));
1910
2012
  });
1911
2013
  storageCommand.command("rm <key>").description("Delete an object").option("-a, --app <id>", "App ID").action(async (key, opts) => {
1912
2014
  const appId = requireApp(opts.app);
1913
2015
  await client().storage.delete(appId, key);
1914
- console.log(chalk15.green(`Deleted ${key}`));
2016
+ console.log(chalk16.green(`Deleted ${key}`));
1915
2017
  });
1916
2018
  storageCommand.command("url <key>").description("Print a temporary link to an object").option("-a, --app <id>", "App ID").option("-e, --expires <seconds>", "Lifetime in seconds (30-3600)", "900").option("--upload", "Sign an upload (PUT) URL instead of a download").option("-d, --download <filename>", "Serve as a download with this filename").action(async (key, opts) => {
1917
2019
  const appId = requireApp(opts.app);
1918
2020
  const expiresIn = Number(opts.expires);
1919
2021
  if (!Number.isInteger(expiresIn) || expiresIn < 30 || expiresIn > 3600) {
1920
- console.error(chalk15.red("--expires must be a whole number of seconds between 30 and 3600."));
2022
+ console.error(chalk16.red("--expires must be a whole number of seconds between 30 and 3600."));
1921
2023
  process.exit(1);
1922
2024
  }
1923
2025
  const signed = await client().storage.presign(appId, key, {
@@ -1927,7 +2029,7 @@ storageCommand.command("url <key>").description("Print a temporary link to an ob
1927
2029
  });
1928
2030
  console.log(signed.url);
1929
2031
  console.error(
1930
- chalk15.yellow(
2032
+ chalk16.yellow(
1931
2033
  `This ${signed.method} link works for ${expiresIn}s for anyone who has it.`
1932
2034
  )
1933
2035
  );
@@ -1940,7 +2042,7 @@ storageCommand.command("usage").description("Total objects and bytes stored").op
1940
2042
 
1941
2043
  // src/commands/email.ts
1942
2044
  import { Command as Command15 } from "commander";
1943
- import chalk16 from "chalk";
2045
+ import chalk17 from "chalk";
1944
2046
  import fs6 from "fs-extra";
1945
2047
  var emailCommand = new Command15("email").description("Send email from an app");
1946
2048
  function client2() {
@@ -1951,7 +2053,7 @@ function client2() {
1951
2053
  function requireApp2(opt) {
1952
2054
  const appId = opt || getCurrentApp();
1953
2055
  if (!appId) {
1954
- console.error(chalk16.red("No app specified. Use --app or link an app."));
2056
+ console.error(chalk17.red("No app specified. Use --app or link an app."));
1955
2057
  process.exit(1);
1956
2058
  }
1957
2059
  return appId;
@@ -1967,7 +2069,7 @@ emailCommand.command("send").description("Send an email").requiredOption("-t, --
1967
2069
  else text = contents;
1968
2070
  }
1969
2071
  if (!text && !html) {
1970
- console.error(chalk16.red("Nothing to send. Pass --text, --html or --body-file."));
2072
+ console.error(chalk17.red("Nothing to send. Pass --text, --html or --body-file."));
1971
2073
  process.exit(1);
1972
2074
  }
1973
2075
  const sent = await client2().email.send(appId, {
@@ -1981,12 +2083,12 @@ emailCommand.command("send").description("Send an email").requiredOption("-t, --
1981
2083
  console.log(JSON.stringify(sent, null, 2));
1982
2084
  return;
1983
2085
  }
1984
- console.log(chalk16.green("Sent"));
2086
+ console.log(chalk17.green("Sent"));
1985
2087
  console.log(` From: ${sent.from}`);
1986
2088
  console.log(` To: ${sent.to.join(", ")}`);
1987
2089
  console.log(` Message: ${sent.messageId ?? "(no id returned)"}`);
1988
2090
  console.log(
1989
- chalk16.dim(
2091
+ chalk17.dim(
1990
2092
  ` Quota: ${sent.quota.sent}/${sent.quota.quota} today, resets ${sent.quota.resetsAt}`
1991
2093
  )
1992
2094
  );
@@ -2001,12 +2103,12 @@ emailCommand.command("quota").description("Show today's sending allowance").opti
2001
2103
  console.log(`Sent today: ${quota.sent}`);
2002
2104
  console.log(`Daily quota: ${quota.quota}`);
2003
2105
  console.log(`Remaining: ${quota.remaining}`);
2004
- console.log(chalk16.dim(`Resets at: ${quota.resetsAt}`));
2106
+ console.log(chalk17.dim(`Resets at: ${quota.resetsAt}`));
2005
2107
  });
2006
2108
 
2007
2109
  // src/commands/jobs.ts
2008
2110
  import { Command as Command16 } from "commander";
2009
- import chalk17 from "chalk";
2111
+ import chalk18 from "chalk";
2010
2112
  import fs7 from "fs-extra";
2011
2113
  var jobsCommand = new Command16("jobs").description("Enqueue and inspect background jobs");
2012
2114
  function client3() {
@@ -2017,7 +2119,7 @@ function client3() {
2017
2119
  function requireApp3(opt) {
2018
2120
  const appId = opt || getCurrentApp();
2019
2121
  if (!appId) {
2020
- console.error(chalk17.red("No app specified. Use --app or run `videan init` first."));
2122
+ console.error(chalk18.red("No app specified. Use --app or run `videan init` first."));
2021
2123
  process.exit(1);
2022
2124
  }
2023
2125
  return appId;
@@ -2025,20 +2127,20 @@ function requireApp3(opt) {
2025
2127
  function intOption2(flag, raw) {
2026
2128
  const n = Number(raw);
2027
2129
  if (!Number.isInteger(n) || n < 0) {
2028
- console.error(chalk17.red(`${flag} expects a whole number, got "${raw}".`));
2130
+ console.error(chalk18.red(`${flag} expects a whole number, got "${raw}".`));
2029
2131
  process.exit(1);
2030
2132
  }
2031
2133
  return n;
2032
2134
  }
2033
2135
  var STATUS_COLOR = {
2034
- queued: chalk17.yellow,
2035
- running: chalk17.cyan,
2036
- succeeded: chalk17.green,
2037
- dead: chalk17.red,
2038
- cancelled: chalk17.gray
2136
+ queued: chalk18.yellow,
2137
+ running: chalk18.cyan,
2138
+ succeeded: chalk18.green,
2139
+ dead: chalk18.red,
2140
+ cancelled: chalk18.gray
2039
2141
  };
2040
2142
  function statusLabel(status) {
2041
- return (STATUS_COLOR[status] ?? chalk17.white)(status);
2143
+ return (STATUS_COLOR[status] ?? chalk18.white)(status);
2042
2144
  }
2043
2145
  jobsCommand.command("enqueue").description("Queue a job for the app to run").option("-a, --app <id>", "App ID").option("-q, --queue <name>", "Queue name (default: default)").option("-p, --path <path>", "Path on the app to POST to (default: /jobs)").option("-d, --data <json>", "JSON payload").option("-f, --file <path>", "Read the JSON payload from a file").option("--delay <seconds>", "Run no earlier than this many seconds from now").option("--attempts <n>", "Attempts before the job is given up on (default: 3)").option("--timeout <seconds>", "How long the handler gets per attempt (default: 60)").option("--key <key>", "Idempotency key -- a repeat returns the same job").option("--json", "Print the raw response").action(async (opts) => {
2044
2146
  const appId = requireApp3(opts.app);
@@ -2048,7 +2150,7 @@ jobsCommand.command("enqueue").description("Queue a job for the app to run").opt
2048
2150
  try {
2049
2151
  payload = JSON.parse(raw);
2050
2152
  } catch (e) {
2051
- console.error(chalk17.red(`--${opts.file ? "file" : "data"} is not valid JSON: ${e.message}`));
2153
+ console.error(chalk18.red(`--${opts.file ? "file" : "data"} is not valid JSON: ${e.message}`));
2052
2154
  process.exit(1);
2053
2155
  }
2054
2156
  }
@@ -2066,13 +2168,13 @@ jobsCommand.command("enqueue").description("Queue a job for the app to run").opt
2066
2168
  return;
2067
2169
  }
2068
2170
  if (job.deduplicated) {
2069
- console.log(chalk17.yellow(`Already queued as ${job.id} (idempotency key "${opts.key}")`));
2171
+ console.log(chalk18.yellow(`Already queued as ${job.id} (idempotency key "${opts.key}")`));
2070
2172
  } else {
2071
- console.log(chalk17.green(`Queued ${job.id}`));
2173
+ console.log(chalk18.green(`Queued ${job.id}`));
2072
2174
  }
2073
- console.log(chalk17.gray(` ${job.method} ${job.path} on queue "${job.queue}"`));
2175
+ console.log(chalk18.gray(` ${job.method} ${job.path} on queue "${job.queue}"`));
2074
2176
  if (job.runAt && Date.parse(job.runAt) > Date.now() + 1e3) {
2075
- console.log(chalk17.gray(` Runs at ${new Date(job.runAt).toLocaleString()}`));
2177
+ console.log(chalk18.gray(` Runs at ${new Date(job.runAt).toLocaleString()}`));
2076
2178
  }
2077
2179
  });
2078
2180
  jobsCommand.command("list").description("List jobs, newest first").option("-a, --app <id>", "App ID").option("-s, --status <status>", "queued, running, succeeded, dead or cancelled").option("-q, --queue <name>", "Only this queue").option("-n, --limit <n>", "How many to show (default: 20)").option("--json", "Print the raw response").action(async (opts) => {
@@ -2087,17 +2189,17 @@ jobsCommand.command("list").description("List jobs, newest first").option("-a, -
2087
2189
  return;
2088
2190
  }
2089
2191
  const depth = Object.entries(counts).map(([status, n]) => `${statusLabel(status)} ${n}`).join(" ");
2090
- console.log(depth || chalk17.gray("No jobs yet."));
2192
+ console.log(depth || chalk18.gray("No jobs yet."));
2091
2193
  if (jobs.length === 0) return;
2092
2194
  console.log();
2093
2195
  for (const job of jobs) {
2094
- const attempts = job.attempts > 0 ? chalk17.gray(` ${job.attempts}/${job.maxAttempts}`) : "";
2196
+ const attempts = job.attempts > 0 ? chalk18.gray(` ${job.attempts}/${job.maxAttempts}`) : "";
2095
2197
  console.log(
2096
- `${statusLabel(job.status).padEnd(20)} ${chalk17.white(job.id)} ${chalk17.gray(
2198
+ `${statusLabel(job.status).padEnd(20)} ${chalk18.white(job.id)} ${chalk18.gray(
2097
2199
  `${job.queue} ${job.path}`
2098
2200
  )}${attempts}`
2099
2201
  );
2100
- if (job.error) console.log(chalk17.red(` ${job.error}`));
2202
+ if (job.error) console.log(chalk18.red(` ${job.error}`));
2101
2203
  }
2102
2204
  });
2103
2205
  jobsCommand.command("get <job-id>").description("Show one job in full").option("-a, --app <id>", "App ID").option("--json", "Print the raw response").action(async (jobId, opts) => {
@@ -2106,17 +2208,17 @@ jobsCommand.command("get <job-id>").description("Show one job in full").option("
2106
2208
  console.log(JSON.stringify(job, null, 2));
2107
2209
  return;
2108
2210
  }
2109
- console.log(`${chalk17.white(job.id)} ${statusLabel(job.status)}`);
2110
- console.log(chalk17.gray(` ${job.method} ${job.path} on queue "${job.queue}"`));
2111
- console.log(chalk17.gray(` Attempts: ${job.attempts}/${job.maxAttempts}`));
2112
- if (job.statusCode) console.log(chalk17.gray(` Last response: HTTP ${job.statusCode}`));
2113
- if (job.durationMs) console.log(chalk17.gray(` Took: ${job.durationMs}ms`));
2114
- if (job.error) console.log(chalk17.red(` Error: ${job.error}`));
2211
+ console.log(`${chalk18.white(job.id)} ${statusLabel(job.status)}`);
2212
+ console.log(chalk18.gray(` ${job.method} ${job.path} on queue "${job.queue}"`));
2213
+ console.log(chalk18.gray(` Attempts: ${job.attempts}/${job.maxAttempts}`));
2214
+ if (job.statusCode) console.log(chalk18.gray(` Last response: HTTP ${job.statusCode}`));
2215
+ if (job.durationMs) console.log(chalk18.gray(` Took: ${job.durationMs}ms`));
2216
+ if (job.error) console.log(chalk18.red(` Error: ${job.error}`));
2115
2217
  if (job.status === "queued" && job.attempts > 0) {
2116
- console.log(chalk17.yellow(` Retrying at ${new Date(job.runAt).toLocaleString()}`));
2218
+ console.log(chalk18.yellow(` Retrying at ${new Date(job.runAt).toLocaleString()}`));
2117
2219
  }
2118
2220
  if (job.payload !== null && job.payload !== void 0) {
2119
- console.log(chalk17.gray(" Payload:"));
2221
+ console.log(chalk18.gray(" Payload:"));
2120
2222
  console.log(
2121
2223
  JSON.stringify(job.payload, null, 2).split("\n").map((l) => ` ${l}`).join("\n")
2122
2224
  );
@@ -2124,16 +2226,16 @@ jobsCommand.command("get <job-id>").description("Show one job in full").option("
2124
2226
  });
2125
2227
  jobsCommand.command("retry <job-id>").description("Run a dead job again, from a clean attempt count").option("-a, --app <id>", "App ID").action(async (jobId, opts) => {
2126
2228
  const job = await client3().jobs.retry(requireApp3(opts.app), jobId);
2127
- console.log(chalk17.green(`Requeued ${job.id}`));
2229
+ console.log(chalk18.green(`Requeued ${job.id}`));
2128
2230
  });
2129
2231
  jobsCommand.command("cancel <job-id>").description("Cancel a job that has not started yet").option("-a, --app <id>", "App ID").action(async (jobId, opts) => {
2130
2232
  const job = await client3().jobs.cancel(requireApp3(opts.app), jobId);
2131
- console.log(chalk17.green(`Cancelled ${job.id}`));
2233
+ console.log(chalk18.green(`Cancelled ${job.id}`));
2132
2234
  });
2133
2235
 
2134
2236
  // src/commands/usage.ts
2135
2237
  import { Command as Command17 } from "commander";
2136
- import chalk18 from "chalk";
2238
+ import chalk19 from "chalk";
2137
2239
  var usageCommand = new Command17("usage").description("Metered usage and cost for the current month").option("-o, --org <id>", "Organization ID").option("-a, --app <id>", "Show one app's share instead of the whole org").option("-p, --period <YYYY-MM>", "Month to report on (default: the month in progress)").option("--json", "Print the raw response").action(async (opts) => {
2138
2240
  const apiKey = getApiKey();
2139
2241
  requireApiKey(apiKey);
@@ -2142,61 +2244,61 @@ var usageCommand = new Command17("usage").description("Metered usage and cost fo
2142
2244
  const appId = opts.app || getCurrentApp();
2143
2245
  const report2 = await client4.apps.usage(appId, opts.period);
2144
2246
  if (opts.json) return void console.log(JSON.stringify(report2, null, 2));
2145
- console.log(chalk18.dim(`${period(report2.periodStart, report2.periodEnd)} app ${appId}`));
2247
+ console.log(chalk19.dim(`${period(report2.periodStart, report2.periodEnd)} app ${appId}`));
2146
2248
  console.log();
2147
2249
  printMetrics(report2.metrics);
2148
2250
  console.log();
2149
2251
  for (const line of report2.lines) {
2150
- console.log(` ${chalk18.dim(line.label.padEnd(22))} ${money(line.cents)}`);
2252
+ console.log(` ${chalk19.dim(line.label.padEnd(22))} ${money(line.cents)}`);
2151
2253
  }
2152
- console.log(` ${chalk18.bold("Share of the bill".padEnd(22))} ${chalk18.bold(report2.cost)}`);
2254
+ console.log(` ${chalk19.bold("Share of the bill".padEnd(22))} ${chalk19.bold(report2.cost)}`);
2153
2255
  console.log();
2154
2256
  console.log(
2155
- chalk18.dim("At list price, with no plan allowance applied \u2014 see `videan usage` for the org total.")
2257
+ chalk19.dim("At list price, with no plan allowance applied \u2014 see `videan usage` for the org total.")
2156
2258
  );
2157
2259
  return;
2158
2260
  }
2159
2261
  const orgId = opts.org || getDefaultOrg();
2160
2262
  if (!orgId) {
2161
- console.error(chalk18.red("No org specified. Use --org, or --app for a single app."));
2263
+ console.error(chalk19.red("No org specified. Use --org, or --app for a single app."));
2162
2264
  process.exit(1);
2163
2265
  }
2164
2266
  const report = await client4.orgs.usage(orgId, opts.period);
2165
2267
  if (opts.json) return void console.log(JSON.stringify(report, null, 2));
2166
2268
  const inv = report.invoice;
2167
2269
  console.log(
2168
- chalk18.dim(`${period(report.periodStart, report.periodEnd)} org ${orgId} plan ${report.plan}`)
2270
+ chalk19.dim(`${period(report.periodStart, report.periodEnd)} org ${orgId} plan ${report.plan}`)
2169
2271
  );
2170
2272
  console.log();
2171
2273
  printMetrics(report.metrics);
2172
2274
  if (report.apps.length > 0) {
2173
2275
  console.log();
2174
- console.log(chalk18.bold("By app"));
2276
+ console.log(chalk19.bold("By app"));
2175
2277
  for (const app of [...report.apps].sort((a, b) => b.cents - a.cents)) {
2176
2278
  const name = app.slug || app.name || app.appId || "(deleted apps)";
2177
- console.log(` ${chalk18.dim(String(name).padEnd(24))} ${money(app.cents)}`);
2279
+ console.log(` ${chalk19.dim(String(name).padEnd(24))} ${money(app.cents)}`);
2178
2280
  }
2179
2281
  }
2180
2282
  console.log();
2181
2283
  for (const line of inv.lines) {
2182
2284
  console.log(
2183
- ` ${chalk18.dim(line.label.padEnd(22))} ${money(line.cents)} ${chalk18.dim(
2285
+ ` ${chalk19.dim(line.label.padEnd(22))} ${money(line.cents)} ${chalk19.dim(
2184
2286
  `(${line.quantity} \xD7 ${line.unitPriceCents}\xA2)`
2185
2287
  )}`
2186
2288
  );
2187
2289
  }
2188
- console.log(` ${chalk18.dim("Usage".padEnd(22))} ${money(inv.usageCents)}`);
2290
+ console.log(` ${chalk19.dim("Usage".padEnd(22))} ${money(inv.usageCents)}`);
2189
2291
  if (inv.includedUsageCents > 0) {
2190
2292
  console.log(
2191
- ` ${chalk18.dim("Included".padEnd(22))} ${chalk18.green(`-${money(Math.min(inv.usageCents, inv.includedUsageCents))}`)}`
2293
+ ` ${chalk19.dim("Included".padEnd(22))} ${chalk19.green(`-${money(Math.min(inv.usageCents, inv.includedUsageCents))}`)}`
2192
2294
  );
2193
2295
  }
2194
2296
  if (inv.baseCents > 0) {
2195
- console.log(` ${chalk18.dim("Plan".padEnd(22))} ${money(inv.baseCents)}`);
2297
+ console.log(` ${chalk19.dim("Plan".padEnd(22))} ${money(inv.baseCents)}`);
2196
2298
  }
2197
- console.log(` ${chalk18.bold("Total".padEnd(22))} ${chalk18.bold(report.total)}`);
2299
+ console.log(` ${chalk19.bold("Total".padEnd(22))} ${chalk19.bold(report.total)}`);
2198
2300
  console.log();
2199
- console.log(chalk18.dim("Usage to date. The period is priced as it stands right now."));
2301
+ console.log(chalk19.dim("Usage to date. The period is priced as it stands right now."));
2200
2302
  });
2201
2303
  usageCommand.command("plans").description("The plan catalogue and what each one allows").option("--json", "Print the raw response").action(async (opts) => {
2202
2304
  const client4 = new CloudClient({ apiKey: getApiKey() || "", baseUrl: getBaseUrl() });
@@ -2204,11 +2306,11 @@ usageCommand.command("plans").description("The plan catalogue and what each one
2204
2306
  if (opts.json) return void console.log(JSON.stringify(plans, null, 2));
2205
2307
  for (const plan of plans) {
2206
2308
  console.log(
2207
- `${chalk18.bold(plan.name)} ${chalk18.dim(`(${plan.id})`)} ${plan.base}/mo` + (plan.includedUsageCents > 0 ? chalk18.dim(`, ${plan.includedUsage} usage included`) : "")
2309
+ `${chalk19.bold(plan.name)} ${chalk19.dim(`(${plan.id})`)} ${plan.base}/mo` + (plan.includedUsageCents > 0 ? chalk19.dim(`, ${plan.includedUsage} usage included`) : "")
2208
2310
  );
2209
2311
  const l = plan.limits;
2210
2312
  console.log(
2211
- chalk18.dim(
2313
+ chalk19.dim(
2212
2314
  ` ${l.apps} apps \xB7 ${l.instancesPerApp} instances/app \xB7 up to ${l.maxCpu} CPU / ${l.maxMemory} MB \xB7 ${l.maxStorageMb} MB storage${l.postgres ? " \xB7 postgres" : ""}${l.customDomains ? " \xB7 custom domains" : ""}`
2213
2315
  )
2214
2316
  );
@@ -2217,11 +2319,11 @@ usageCommand.command("plans").description("The plan catalogue and what each one
2217
2319
  });
2218
2320
  function printMetrics(metrics) {
2219
2321
  if (metrics.length === 0) {
2220
- console.log(chalk18.dim(" Nothing metered in this period yet."));
2322
+ console.log(chalk19.dim(" Nothing metered in this period yet."));
2221
2323
  return;
2222
2324
  }
2223
2325
  for (const m of metrics) {
2224
- console.log(` ${chalk18.dim(m.metric.padEnd(22))} ${round(m.quantity)}`);
2326
+ console.log(` ${chalk19.dim(m.metric.padEnd(22))} ${round(m.quantity)}`);
2225
2327
  }
2226
2328
  }
2227
2329
  function round(n) {
@@ -2236,8 +2338,9 @@ function period(start, end) {
2236
2338
  }
2237
2339
 
2238
2340
  // src/index.ts
2341
+ installPromptCrashGuard();
2239
2342
  var program = new Command18();
2240
- program.name("videan").description("Videan CLI \u2014 deploy apps in seconds").version("0.1.0");
2343
+ program.name("videan").description("Videan CLI \u2014 deploy apps in seconds").version("0.4.1");
2241
2344
  program.addCommand(loginCommand);
2242
2345
  program.addCommand(logoutCommand);
2243
2346
  program.addCommand(initCommand);