doable-cli 0.1.3 → 0.1.4

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.
Files changed (2) hide show
  1. package/dist/index.js +606 -542
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -4,8 +4,8 @@ import os from 'os';
4
4
  import { readFile } from 'fs/promises';
5
5
  import { fileURLToPath } from 'url';
6
6
  import path7 from 'path';
7
- import chalk4 from 'chalk';
8
- import ora9 from 'ora';
7
+ import chalk5 from 'chalk';
8
+ import ora10 from 'ora';
9
9
  import open from 'open';
10
10
  import Conf from 'conf';
11
11
  import fs6 from 'fs';
@@ -88,6 +88,9 @@ function getToken() {
88
88
  function setToken(token) {
89
89
  config.set("token", token);
90
90
  }
91
+ function clearToken() {
92
+ config.delete("token");
93
+ }
91
94
  function getApiUrl() {
92
95
  return config.get("apiUrl") || "http://localhost:3200";
93
96
  }
@@ -242,6 +245,14 @@ function devicePoll(deviceCode) {
242
245
  device_code: deviceCode
243
246
  });
244
247
  }
248
+ async function authLogout() {
249
+ try {
250
+ await request("POST", "/v1/auth/logout");
251
+ return true;
252
+ } catch {
253
+ return false;
254
+ }
255
+ }
245
256
  function listProjects() {
246
257
  return request("GET", "/v1/projects");
247
258
  }
@@ -417,20 +428,20 @@ function listAddonTables(addonId) {
417
428
  // src/commands/login.ts
418
429
  function registerLoginCommand(program2) {
419
430
  program2.command("login").description("Authenticate with Doable").action(async () => {
420
- const spinner = ora9("Starting login flow...").start();
431
+ const spinner = ora10("Starting login flow...").start();
421
432
  let startResp;
422
433
  try {
423
434
  startResp = await deviceStart();
424
435
  } catch (err) {
425
436
  if (err instanceof ApiError && err.status === 0) {
426
437
  spinner.fail("Cannot connect to Doable API.");
427
- console.error(chalk4.red(err.message));
438
+ console.error(chalk5.red(err.message));
428
439
  } else {
429
440
  spinner.fail("Failed to start login flow.");
430
441
  if (err instanceof ApiError || err instanceof Error) {
431
- console.error(chalk4.red(err.message));
442
+ console.error(chalk5.red(err.message));
432
443
  } else {
433
- console.error(chalk4.red(String(err)));
444
+ console.error(chalk5.red(String(err)));
434
445
  }
435
446
  }
436
447
  process.exit(1);
@@ -438,21 +449,32 @@ function registerLoginCommand(program2) {
438
449
  spinner.stop();
439
450
  const { device_code, user_code, verification_url, expires_in, poll_interval } = startResp;
440
451
  console.log();
441
- console.log(chalk4.bold(" Login to Doable"));
452
+ console.log(chalk5.bold(" Login to Doable"));
442
453
  console.log();
443
- console.log(` Your code: ${chalk4.cyan.bold(user_code)}`);
454
+ console.log(` Your code: ${chalk5.cyan.bold(user_code)}`);
444
455
  console.log();
445
456
  console.log(` Open this URL to authenticate:`);
446
- console.log(` ${chalk4.underline(verification_url)}`);
457
+ console.log(` ${chalk5.underline(verification_url)}`);
447
458
  console.log();
448
- try {
449
- await open(verification_url);
450
- console.log(chalk4.dim(" (Browser opened automatically)"));
451
- } catch {
452
- console.log(chalk4.dim(" (Open the URL above in your browser)"));
459
+ if (process.stdin.isTTY && !process.env.DOABLE_NO_BROWSER) {
460
+ process.stdout.write(
461
+ chalk5.dim(" Press Enter to open your browser, or Ctrl+C to abort...")
462
+ );
463
+ await waitForEnter();
464
+ process.stdout.write("\r\x1B[2K");
465
+ }
466
+ if (process.env.DOABLE_NO_BROWSER) {
467
+ console.log(chalk5.dim(" (Open the URL above in your browser)"));
468
+ } else {
469
+ try {
470
+ await open(verification_url);
471
+ console.log(chalk5.dim(" (Browser opened)"));
472
+ } catch {
473
+ console.log(chalk5.dim(" (Open the URL above in your browser)"));
474
+ }
453
475
  }
454
476
  console.log();
455
- const pollSpinner = ora9("Waiting for authentication...").start();
477
+ const pollSpinner = ora10("Waiting for authentication...").start();
456
478
  const pollIntervalMs = (poll_interval || 2) * 1e3;
457
479
  const deadline = Date.now() + expires_in * 1e3;
458
480
  while (Date.now() < deadline) {
@@ -461,30 +483,30 @@ function registerLoginCommand(program2) {
461
483
  const pollResp = await devicePoll(device_code);
462
484
  if (pollResp.status === "complete" && pollResp.token) {
463
485
  setToken(pollResp.token);
464
- pollSpinner.succeed(chalk4.green("Logged in."));
486
+ pollSpinner.succeed(chalk5.green("Logged in."));
465
487
  try {
466
488
  const me = await getMe();
467
489
  const first = firstNameOrHandle(me.user.name, me.user.email);
468
490
  console.log();
469
- console.log(chalk4.bold(` Welcome, ${first}.`));
491
+ console.log(chalk5.bold(` Welcome, ${first}.`));
470
492
  if (me.account?.name) {
471
- console.log(chalk4.dim(` Account: ${me.account.name}`));
493
+ console.log(chalk5.dim(` Account: ${me.account.name}`));
472
494
  }
473
495
  console.log();
474
- console.log(chalk4.dim(" Next:"));
496
+ console.log(chalk5.dim(" Next:"));
475
497
  console.log(
476
- chalk4.dim(
477
- " " + chalk4.cyan("doable deploy") + " from inside a project"
498
+ chalk5.dim(
499
+ " " + chalk5.cyan("doable deploy") + " from inside a project"
478
500
  )
479
501
  );
480
502
  console.log(
481
- chalk4.dim(
482
- " " + chalk4.cyan("doable setup") + " configure Claude Code / Cursor / Codex"
503
+ chalk5.dim(
504
+ " " + chalk5.cyan("doable setup") + " configure Claude Code / Cursor / Codex"
483
505
  )
484
506
  );
485
507
  console.log(
486
- chalk4.dim(
487
- " " + chalk4.cyan("doable status") + " see all your projects"
508
+ chalk5.dim(
509
+ " " + chalk5.cyan("doable status") + " see all your projects"
488
510
  )
489
511
  );
490
512
  } catch {
@@ -507,9 +529,9 @@ function registerLoginCommand(program2) {
507
529
  }
508
530
  pollSpinner.fail("Login failed.");
509
531
  if (err instanceof ApiError || err instanceof Error) {
510
- console.error(chalk4.red(err.message));
532
+ console.error(chalk5.red(err.message));
511
533
  } else {
512
- console.error(chalk4.red(String(err)));
534
+ console.error(chalk5.red(String(err)));
513
535
  }
514
536
  process.exit(1);
515
537
  }
@@ -518,6 +540,21 @@ function registerLoginCommand(program2) {
518
540
  process.exit(1);
519
541
  });
520
542
  }
543
+ function waitForEnter() {
544
+ return new Promise((resolve) => {
545
+ if (!process.stdin.isTTY) {
546
+ resolve();
547
+ return;
548
+ }
549
+ const onData = () => {
550
+ process.stdin.removeListener("data", onData);
551
+ process.stdin.pause();
552
+ resolve();
553
+ };
554
+ process.stdin.resume();
555
+ process.stdin.once("data", onData);
556
+ });
557
+ }
521
558
  function sleep(ms) {
522
559
  return new Promise((resolve) => setTimeout(resolve, ms));
523
560
  }
@@ -535,6 +572,32 @@ function firstNameOrHandle(name, email) {
535
572
  }
536
573
  return "friend";
537
574
  }
575
+ function registerLogoutCommand(program2) {
576
+ program2.command("logout").description("Sign out of Doable on this machine").action(async () => {
577
+ const token = getToken();
578
+ if (!token) {
579
+ console.log(chalk5.dim("Already signed out."));
580
+ return;
581
+ }
582
+ const spinner = ora10("Signing out...").start();
583
+ const revoked = await authLogout();
584
+ clearToken();
585
+ if (revoked) {
586
+ spinner.succeed(chalk5.green("Signed out."));
587
+ } else {
588
+ spinner.warn(
589
+ chalk5.yellow(
590
+ "Signed out locally, but couldn't reach Doable to revoke the token server-side."
591
+ )
592
+ );
593
+ console.error(
594
+ chalk5.dim(
595
+ " The token on this machine is gone. If you're worried about the dangling server-side token, rotate it from the dashboard once you're back online."
596
+ )
597
+ );
598
+ }
599
+ });
600
+ }
538
601
  var POSTGRES_NODE_PACKAGES = [
539
602
  "pg",
540
603
  "postgres",
@@ -680,10 +743,10 @@ async function promptOne(projectId, detection) {
680
743
  const SKIP = "skip";
681
744
  console.log();
682
745
  console.log(
683
- chalk4.bold(` Detected ${kindLabel} usage`) + chalk4.dim(` (${detection.source})`)
746
+ chalk5.bold(` Detected ${kindLabel} usage`) + chalk5.dim(` (${detection.source})`)
684
747
  );
685
748
  console.log(
686
- chalk4.dim(
749
+ chalk5.dim(
687
750
  ` No ${detection.envKey} is set on this project, and no native addon is attached yet.`
688
751
  )
689
752
  );
@@ -710,7 +773,7 @@ async function promptOne(projectId, detection) {
710
773
  ]);
711
774
  if (choice === SKIP) {
712
775
  console.log(
713
- chalk4.dim(
776
+ chalk5.dim(
714
777
  ` OK \u2014 continuing the deploy. Set ${detection.envKey} anytime with \`doable addon attach\`.`
715
778
  )
716
779
  );
@@ -726,7 +789,7 @@ async function promptOne(projectId, detection) {
726
789
  validate: (v) => v.trim().length > 0 ? true : "required"
727
790
  }
728
791
  ]);
729
- const spinner2 = ora9(`Saving ${detection.envKey}...`).start();
792
+ const spinner2 = ora10(`Saving ${detection.envKey}...`).start();
730
793
  try {
731
794
  await putEnvVars(projectId, { [detection.envKey]: url.trim() });
732
795
  spinner2.succeed(`${detection.envKey} attached.`);
@@ -739,8 +802,8 @@ async function promptOne(projectId, detection) {
739
802
  const engine = detection.kind;
740
803
  const version = engine === "postgres" ? "16" : "7";
741
804
  console.log();
742
- console.log(chalk4.bold(` Provisioning Doable-managed ${kindLabel}`));
743
- const creating = ora9("Requesting addon...").start();
805
+ console.log(chalk5.bold(` Provisioning Doable-managed ${kindLabel}`));
806
+ const creating = ora10("Requesting addon...").start();
744
807
  let addon;
745
808
  try {
746
809
  addon = await createAddon(projectId, {
@@ -753,7 +816,7 @@ async function promptOne(projectId, detection) {
753
816
  throw err;
754
817
  }
755
818
  creating.succeed(`Addon ${addon.id.slice(0, 8)} created.`);
756
- const spinner = ora9("Waiting for container to start...").start();
819
+ const spinner = ora10("Waiting for container to start...").start();
757
820
  const deadline = Date.now() + 5 * 60 * 1e3;
758
821
  let current = addon;
759
822
  while (Date.now() < deadline) {
@@ -774,10 +837,10 @@ async function promptOne(projectId, detection) {
774
837
  if (current.status === "failed") {
775
838
  spinner.fail("Provisioning failed.");
776
839
  if (current.statusMessage) {
777
- console.log(chalk4.red(` ${current.statusMessage}`));
840
+ console.log(chalk5.red(` ${current.statusMessage}`));
778
841
  }
779
842
  console.log(
780
- chalk4.dim(
843
+ chalk5.dim(
781
844
  " Continuing deploy without the addon. You can retry with `doable addon attach --native`."
782
845
  )
783
846
  );
@@ -1135,7 +1198,7 @@ function registerPreviewCommand(program2) {
1135
1198
  await runPreview(opts);
1136
1199
  } catch (err) {
1137
1200
  if (err instanceof Error) {
1138
- console.error(chalk4.red(`Error: ${err.message}`));
1201
+ console.error(chalk5.red(`Error: ${err.message}`));
1139
1202
  }
1140
1203
  process.exit(1);
1141
1204
  }
@@ -1152,14 +1215,14 @@ async function runPreview(opts) {
1152
1215
  }
1153
1216
  const apiUrl = getApiUrl();
1154
1217
  console.log();
1155
- console.log(chalk4.bold(" Instant preview deploy. No account needed."));
1218
+ console.log(chalk5.bold(" Instant preview deploy. No account needed."));
1156
1219
  console.log(
1157
- chalk4.dim(
1220
+ chalk5.dim(
1158
1221
  " Heads up: previews are read-only. To update code, add env vars, or\n connect a domain, claim it into an account first (link at the end)."
1159
1222
  )
1160
1223
  );
1161
1224
  console.log();
1162
- const spinner = ora9("Scanning project...").start();
1225
+ const spinner = ora10("Scanning project...").start();
1163
1226
  const detected = await detectRuntime(projectDir, nodeDetectFS);
1164
1227
  spinner.succeed(`Detected: ${detected.framework || detected.runtime}`);
1165
1228
  let email = opts.email;
@@ -1175,7 +1238,7 @@ async function runPreview(opts) {
1175
1238
  email = inputEmail;
1176
1239
  }
1177
1240
  }
1178
- const createSpinner = ora9("Creating preview...").start();
1241
+ const createSpinner = ora10("Creating preview...").start();
1179
1242
  const createRes = await fetch(`${apiUrl}/v1/preview`, {
1180
1243
  method: "POST",
1181
1244
  headers: { "Content-Type": "application/json" },
@@ -1192,7 +1255,7 @@ async function runPreview(opts) {
1192
1255
  }
1193
1256
  const preview = await createRes.json();
1194
1257
  createSpinner.succeed("Preview created");
1195
- const bundleSpinner = ora9("Bundling project...").start();
1258
+ const bundleSpinner = ora10("Bundling project...").start();
1196
1259
  const tar2 = await import('tar');
1197
1260
  const { tmpdir } = await import('os');
1198
1261
  const bundlePath = path7.join(tmpdir(), `doable-preview-${Date.now()}.tar.gz`);
@@ -1252,7 +1315,7 @@ async function runPreview(opts) {
1252
1315
  throw new Error(`Bundle too large (${sizeMB} MB). Preview limit is 100 MB.`);
1253
1316
  }
1254
1317
  bundleSpinner.succeed(`Bundled ${sizeMB} MB`);
1255
- const uploadSpinner = ora9("Uploading...").start();
1318
+ const uploadSpinner = ora10("Uploading...").start();
1256
1319
  const bundleBuffer = fs6.readFileSync(bundlePath);
1257
1320
  fs6.unlinkSync(bundlePath);
1258
1321
  const uploadRes = await fetch(preview.uploadUrl, {
@@ -1265,7 +1328,7 @@ async function runPreview(opts) {
1265
1328
  throw new Error(`Upload failed: HTTP ${uploadRes.status}`);
1266
1329
  }
1267
1330
  uploadSpinner.succeed("Uploaded");
1268
- const buildSpinner = ora9("Triggering build...").start();
1331
+ const buildSpinner = ora10("Triggering build...").start();
1269
1332
  const completeRes = await fetch(`${apiUrl}/v1/preview/complete`, {
1270
1333
  method: "POST",
1271
1334
  headers: { "Content-Type": "application/json" },
@@ -1281,7 +1344,7 @@ async function runPreview(opts) {
1281
1344
  }
1282
1345
  const deploy = await completeRes.json();
1283
1346
  buildSpinner.succeed(`Build #${deploy.number} queued`);
1284
- const pollSpinner = ora9("Building and deploying\u2026").start();
1347
+ const pollSpinner = ora10("Building and deploying\u2026").start();
1285
1348
  let finalStatus = "queued";
1286
1349
  let lastLabel = "";
1287
1350
  for (let i = 0; i < 300; i++) {
@@ -1310,17 +1373,17 @@ async function runPreview(opts) {
1310
1373
  }
1311
1374
  console.log();
1312
1375
  if (finalStatus === "healthy") {
1313
- console.log(chalk4.green.bold(" Preview live!"));
1376
+ console.log(chalk5.green.bold(" Preview live!"));
1314
1377
  console.log();
1315
- console.log(` URL: ${chalk4.underline.cyan(preview.previewUrl)}`);
1316
- console.log(` Share: ${chalk4.cyan(`https://doable.do/s/${deploy.deploymentId}`)}`);
1317
- console.log(` Expires: ${chalk4.dim("in 8 hours")}`);
1378
+ console.log(` URL: ${chalk5.underline.cyan(preview.previewUrl)}`);
1379
+ console.log(` Share: ${chalk5.cyan(`https://doable.do/s/${deploy.deploymentId}`)}`);
1380
+ console.log(` Expires: ${chalk5.dim("in 8 hours")}`);
1318
1381
  console.log();
1319
- console.log(chalk4.dim(" Share either link. Anyone can see your app."));
1382
+ console.log(chalk5.dim(" Share either link. Anyone can see your app."));
1320
1383
  console.log();
1321
1384
  console.log(` To update this app (new deploy, env vars, domain): claim it first.`);
1322
- console.log(` ${chalk4.cyan(`https://doable.do/signup#claim=${preview.claimToken}`)}`);
1323
- console.log(chalk4.dim(` Keep this link private. Anyone who opens it can claim the project.`));
1385
+ console.log(` ${chalk5.cyan(`https://doable.do/signup#claim=${preview.claimToken}`)}`);
1386
+ console.log(chalk5.dim(` Keep this link private. Anyone who opens it can claim the project.`));
1324
1387
  console.log();
1325
1388
  try {
1326
1389
  const projectName = path7.basename(projectDir);
@@ -1338,8 +1401,8 @@ async function runPreview(opts) {
1338
1401
  } catch {
1339
1402
  }
1340
1403
  } else {
1341
- console.log(chalk4.red(` Preview deploy ${finalStatus}.`));
1342
- console.log(chalk4.dim(` Check status: ${apiUrl}/v1/preview/${preview.claimToken}/status`));
1404
+ console.log(chalk5.red(` Preview deploy ${finalStatus}.`));
1405
+ console.log(chalk5.dim(` Check status: ${apiUrl}/v1/preview/${preview.claimToken}/status`));
1343
1406
  console.log();
1344
1407
  }
1345
1408
  const previewJsonPath = path7.join(projectDir, ".doable-preview.json");
@@ -1621,7 +1684,7 @@ async function promptAmbiguousDeployRoot(projectDir, nonInteractive) {
1621
1684
  if (choices.length <= 1) return;
1622
1685
  if (nonInteractive) {
1623
1686
  console.warn(
1624
- chalk4.yellow(
1687
+ chalk5.yellow(
1625
1688
  ` Multiple deployable app roots found (${choices.join(", ")}). Add {"root":"apps/web"} to .doable.json or run interactively once.`
1626
1689
  )
1627
1690
  );
@@ -1638,7 +1701,7 @@ async function promptAmbiguousDeployRoot(projectDir, nonInteractive) {
1638
1701
  ]);
1639
1702
  const payload = { ...existing, root: answer.root };
1640
1703
  fs6.writeFileSync(path7.join(projectDir, ".doable.json"), JSON.stringify(payload, null, 2) + "\n", "utf-8");
1641
- console.log(chalk4.dim(` Saved deploy root to .doable.json (${answer.root}).`));
1704
+ console.log(chalk5.dim(` Saved deploy root to .doable.json (${answer.root}).`));
1642
1705
  }
1643
1706
  async function resolveTargetOption(target) {
1644
1707
  const trimmed = target?.trim();
@@ -1676,7 +1739,7 @@ async function promptTargetOption(explicitTarget, nonInteractive) {
1676
1739
  message: "Where should this project run?",
1677
1740
  choices: [
1678
1741
  {
1679
- name: chalk4.green("Doable Cloud") + chalk4.dim(" (recommended for first deploy)"),
1742
+ name: chalk5.green("Doable Cloud") + chalk5.dim(" (recommended for first deploy)"),
1680
1743
  value: CLOUD
1681
1744
  },
1682
1745
  new inquirer3.Separator(" \u2500\u2500 your servers \u2500\u2500"),
@@ -1700,16 +1763,16 @@ function registerDeployCommand(program2) {
1700
1763
  } catch (err) {
1701
1764
  if (err instanceof ApiError) {
1702
1765
  if (err.status === 401) {
1703
- console.error(chalk4.red("Not authenticated. Run `doable login` first."));
1766
+ console.error(chalk5.red("Not authenticated. Run `doable login` first."));
1704
1767
  } else if (err.status === 0) {
1705
- console.error(chalk4.red(`Connection error: ${err.message}`));
1768
+ console.error(chalk5.red(`Connection error: ${err.message}`));
1706
1769
  } else {
1707
- console.error(chalk4.red(`Error: ${err.message}`));
1770
+ console.error(chalk5.red(`Error: ${err.message}`));
1708
1771
  }
1709
1772
  } else if (err instanceof Error) {
1710
- console.error(chalk4.red(`Error: ${err.message}`));
1773
+ console.error(chalk5.red(`Error: ${err.message}`));
1711
1774
  } else {
1712
- console.error(chalk4.red(`Unexpected error: ${String(err)}`));
1775
+ console.error(chalk5.red(`Unexpected error: ${String(err)}`));
1713
1776
  }
1714
1777
  process.exit(1);
1715
1778
  }
@@ -1725,10 +1788,10 @@ async function runDeploy(opts) {
1725
1788
  const existingToken = getToken();
1726
1789
  if (!existingToken && process.stdin.isTTY) {
1727
1790
  console.log();
1728
- console.log(chalk4.bold(" You're not signed in to Doable."));
1791
+ console.log(chalk5.bold(" You're not signed in to Doable."));
1729
1792
  console.log();
1730
1793
  console.log(
1731
- chalk4.dim(
1794
+ chalk5.dim(
1732
1795
  " Both options are free. Pick based on what you want:"
1733
1796
  )
1734
1797
  );
@@ -1742,13 +1805,13 @@ async function runDeploy(opts) {
1742
1805
  message: "How do you want to deploy?",
1743
1806
  choices: [
1744
1807
  {
1745
- name: chalk4.green("Sign in / sign up ") + chalk4.dim.italic(
1808
+ name: chalk5.green("Sign in / sign up ") + chalk5.dim.italic(
1746
1809
  "(recommended \u2014 you own the project, can redeploy, add env vars / domain)"
1747
1810
  ),
1748
1811
  value: SIGN_IN
1749
1812
  },
1750
1813
  {
1751
- name: chalk4.yellow("8-hour preview ") + chalk4.dim.italic(
1814
+ name: chalk5.yellow("8-hour preview ") + chalk5.dim.italic(
1752
1815
  "(no signup; can't be modified after deploy unless claimed first)"
1753
1816
  ),
1754
1817
  value: QUICK_PREVIEW
@@ -1761,9 +1824,9 @@ async function runDeploy(opts) {
1761
1824
  return;
1762
1825
  }
1763
1826
  console.log();
1764
- console.log(chalk4.bold(" Next:"));
1765
- console.log(` 1. Run ${chalk4.cyan("doable login")} (opens your browser)`);
1766
- console.log(` 2. Re-run ${chalk4.cyan("doable deploy")}`);
1827
+ console.log(chalk5.bold(" Next:"));
1828
+ console.log(` 1. Run ${chalk5.cyan("doable login")} (opens your browser)`);
1829
+ console.log(` 2. Re-run ${chalk5.cyan("doable deploy")}`);
1767
1830
  console.log();
1768
1831
  return;
1769
1832
  }
@@ -1781,7 +1844,7 @@ async function runDeploy(opts) {
1781
1844
  if (projectId) cameFromDoableJson = true;
1782
1845
  } catch {
1783
1846
  console.warn(
1784
- chalk4.yellow(`Warning: Could not parse ${doableJsonPath}. Ignoring.`)
1847
+ chalk5.yellow(`Warning: Could not parse ${doableJsonPath}. Ignoring.`)
1785
1848
  );
1786
1849
  }
1787
1850
  }
@@ -1796,10 +1859,10 @@ async function runDeploy(opts) {
1796
1859
  const estimate = formatBuildEstimate(detected.runtime);
1797
1860
  const runtimeLabel = detected.framework ? `${detected.runtime} (${detected.framework})` : detected.runtime;
1798
1861
  console.log();
1799
- console.log(chalk4.bold(` Deploy ${chalk4.cyan(proposedName)}`));
1800
- console.log(chalk4.dim(` ${detected.reason}`));
1862
+ console.log(chalk5.bold(` Deploy ${chalk5.cyan(proposedName)}`));
1863
+ console.log(chalk5.dim(` ${detected.reason}`));
1801
1864
  console.log(
1802
- chalk4.dim(
1865
+ chalk5.dim(
1803
1866
  ` Runtime: ${runtimeLabel} \xB7 First build: ${estimate} \xB7 Builds on Doable's servers (no local Docker)`
1804
1867
  )
1805
1868
  );
@@ -1818,7 +1881,7 @@ async function runDeploy(opts) {
1818
1881
  // Lead with the permanent project path — user owns it, can
1819
1882
  // update, add env vars / domains / addons, roll back. This
1820
1883
  // is the right default for 95% of deploys.
1821
- name: chalk4.green(`Create new: ${proposedName} (${runtimeLabel})`),
1884
+ name: chalk5.green(`Create new: ${proposedName} (${runtimeLabel})`),
1822
1885
  value: CREATE_NEW
1823
1886
  }
1824
1887
  ];
@@ -1834,7 +1897,7 @@ async function runDeploy(opts) {
1834
1897
  choices.push(
1835
1898
  new inquirer3.Separator(" \u2500\u2500 or quick throwaway share \u2500\u2500"),
1836
1899
  {
1837
- name: chalk4.dim("8-hour preview ") + chalk4.dim.italic("(can't be modified unless claimed later)"),
1900
+ name: chalk5.dim("8-hour preview ") + chalk5.dim.italic("(can't be modified unless claimed later)"),
1838
1901
  value: QUICK_PREVIEW
1839
1902
  }
1840
1903
  );
@@ -1857,7 +1920,7 @@ async function runDeploy(opts) {
1857
1920
  }
1858
1921
  if (autoCreate && !projectId) {
1859
1922
  const resolvedTarget = await promptTargetOption(opts.target, nonInteractive);
1860
- const spinner = ora9(`Creating project ${proposedName}...`).start();
1923
+ const spinner = ora10(`Creating project ${proposedName}...`).start();
1861
1924
  try {
1862
1925
  const created = await createProject({
1863
1926
  name: proposedName,
@@ -1867,7 +1930,7 @@ async function runDeploy(opts) {
1867
1930
  ...resolvedTarget ? { target: resolvedTarget } : {}
1868
1931
  });
1869
1932
  projectId = created.project.id;
1870
- spinner.succeed(`Created project ${chalk4.cyan(created.project.name)}.`);
1933
+ spinner.succeed(`Created project ${chalk5.cyan(created.project.name)}.`);
1871
1934
  } catch (err) {
1872
1935
  spinner.fail(`Failed to create project.`);
1873
1936
  throw err;
@@ -1883,8 +1946,8 @@ async function runDeploy(opts) {
1883
1946
  writeDoableJsonFile(projectDir, project.id, project.slug);
1884
1947
  }
1885
1948
  console.log(
1886
- chalk4.bold(`
1887
- Deploying ${chalk4.cyan(project.name)} (${project.slug})
1949
+ chalk5.bold(`
1950
+ Deploying ${chalk5.cyan(project.name)} (${project.slug})
1888
1951
  `)
1889
1952
  );
1890
1953
  try {
@@ -1893,7 +1956,7 @@ async function runDeploy(opts) {
1893
1956
  });
1894
1957
  } catch (err) {
1895
1958
  console.warn(
1896
- chalk4.yellow(
1959
+ chalk5.yellow(
1897
1960
  ` (Addon setup skipped: ${err instanceof Error ? err.message : String(err)})`
1898
1961
  )
1899
1962
  );
@@ -1906,12 +1969,12 @@ async function runDeploy(opts) {
1906
1969
  });
1907
1970
  } catch (err) {
1908
1971
  console.warn(
1909
- chalk4.yellow(
1972
+ chalk5.yellow(
1910
1973
  ` (Env sync skipped: ${err instanceof Error ? err.message : String(err)})`
1911
1974
  )
1912
1975
  );
1913
1976
  }
1914
- const bundleSpinner = ora9("Bundling project...").start();
1977
+ const bundleSpinner = ora10("Bundling project...").start();
1915
1978
  const bundlePath = path7.join(projectDir, ".doable-bundle.tar.gz");
1916
1979
  try {
1917
1980
  const ig = ignore();
@@ -1988,7 +2051,7 @@ async function runDeploy(opts) {
1988
2051
  const HARD_MAX_BUNDLE_MB = 500;
1989
2052
  if (sizeMb > HARD_MAX_BUNDLE_MB) {
1990
2053
  console.error(
1991
- chalk4.red(
2054
+ chalk5.red(
1992
2055
  `Bundle size (${sizeMb.toFixed(1)} MB) exceeds the maximum allowed (${HARD_MAX_BUNDLE_MB} MB). Add large files to .doableignore or .gitignore and try again.`
1993
2056
  )
1994
2057
  );
@@ -2001,7 +2064,7 @@ async function runDeploy(opts) {
2001
2064
  const maxMb = limits?.maxBundleSizeMb;
2002
2065
  if (maxMb && sizeMb > maxMb) {
2003
2066
  console.error(
2004
- chalk4.red(
2067
+ chalk5.red(
2005
2068
  `Bundle size (${sizeMb.toFixed(1)} MB) exceeds your plan limit (${maxMb} MB). Upgrade your plan or reduce your bundle size.`
2006
2069
  )
2007
2070
  );
@@ -2010,7 +2073,7 @@ async function runDeploy(opts) {
2010
2073
  }
2011
2074
  } catch {
2012
2075
  }
2013
- const uploadSpinner = ora9("Uploading bundle\u2026").start();
2076
+ const uploadSpinner = ora10("Uploading bundle\u2026").start();
2014
2077
  const initResp = await uploadInit({
2015
2078
  projectId: project.id,
2016
2079
  filename: "bundle.tar.gz",
@@ -2040,20 +2103,20 @@ async function runDeploy(opts) {
2040
2103
  }
2041
2104
  }
2042
2105
  if (!key) {
2043
- console.error(chalk4.red("Internal error: no upload key resolved."));
2106
+ console.error(chalk5.red("Internal error: no upload key resolved."));
2044
2107
  cleanup(bundlePath);
2045
2108
  process.exit(1);
2046
2109
  }
2047
- const deploySpinner = ora9("Starting deployment...").start();
2110
+ const deploySpinner = ora10("Starting deployment...").start();
2048
2111
  const deployment = await createDeployment(project.id, {
2049
2112
  sourceUploadKey: key,
2050
2113
  sourceSha256: sourceSha256 ?? void 0,
2051
2114
  repoSizeMb: sizeMb,
2052
2115
  referencedEnvKeys
2053
2116
  });
2054
- deploySpinner.succeed(`Deployment started: ${chalk4.dim(deployment.id)}`);
2117
+ deploySpinner.succeed(`Deployment started: ${chalk5.dim(deployment.id)}`);
2055
2118
  console.log();
2056
- console.log(chalk4.dim(" --- Deployment logs ---"));
2119
+ console.log(chalk5.dim(" --- Deployment logs ---"));
2057
2120
  console.log();
2058
2121
  const finalStatus = await streamDeploymentEvents(deployment.id);
2059
2122
  console.log();
@@ -2090,15 +2153,15 @@ async function runDeploy(opts) {
2090
2153
  }
2091
2154
  const isFirstDeploy = finalDeploy.number === 1;
2092
2155
  console.log(
2093
- chalk4.green.bold(
2156
+ chalk5.green.bold(
2094
2157
  isFirstDeploy ? " Your app is live." : " Live."
2095
2158
  )
2096
2159
  );
2097
2160
  if (liveUrl) {
2098
- console.log(` ${osc8Link(liveUrl, chalk4.underline.cyan(liveUrl))}`);
2161
+ console.log(` ${osc8Link(liveUrl, chalk5.underline.cyan(liveUrl))}`);
2099
2162
  }
2100
2163
  console.log(
2101
- chalk4.dim(
2164
+ chalk5.dim(
2102
2165
  durationSec < 30 ? ` Took ${durationSec}s. Nice.` : ` Took ${durationSec}s.`
2103
2166
  )
2104
2167
  );
@@ -2107,7 +2170,7 @@ async function runDeploy(opts) {
2107
2170
  printQr(liveUrl);
2108
2171
  }
2109
2172
  const shareUrl = `https://doable.do/s/${deployment.id}`;
2110
- console.log(` ${chalk4.dim("Share:")} ${chalk4.cyan(shareUrl)}`);
2173
+ console.log(` ${chalk5.dim("Share:")} ${chalk5.cyan(shareUrl)}`);
2111
2174
  console.log();
2112
2175
  let domainPromptShown = false;
2113
2176
  if (isFirstDeploy && activeDomain?.hostname.endsWith(".doable.do") && process.stdout.isTTY) {
@@ -2116,10 +2179,10 @@ async function runDeploy(opts) {
2116
2179
  }
2117
2180
  const tips = [];
2118
2181
  if (!domainPromptShown && !finalProject.domains?.some((d) => d.type === "custom" || d.type === "purchased")) {
2119
- tips.push(`Add a custom domain: ${chalk4.cyan("doable domain search yourname.com")}`);
2182
+ tips.push(`Add a custom domain: ${chalk5.cyan("doable domain search yourname.com")}`);
2120
2183
  }
2121
2184
  if (!finalProject.webhookUrl) {
2122
- tips.push(`Get Slack notifications: ${chalk4.cyan("doable.do/projects/" + project.id + " > Settings > Webhook")}`);
2185
+ tips.push(`Get Slack notifications: ${chalk5.cyan("doable.do/projects/" + project.id + " > Settings > Webhook")}`);
2123
2186
  }
2124
2187
  if (finalProject.referencedEnvKeys?.length > 0) {
2125
2188
  try {
@@ -2127,15 +2190,15 @@ async function runDeploy(opts) {
2127
2190
  const setKeys = new Set(envVars.map((v) => v.key));
2128
2191
  const missing = finalProject.referencedEnvKeys.filter((k) => !setKeys.has(k));
2129
2192
  if (missing.length > 0) {
2130
- tips.push(`Your code references ${chalk4.yellow(missing.slice(0, 3).join(", "))} but ${missing.length === 1 ? "it's" : "they're"} not set`);
2193
+ tips.push(`Your code references ${chalk5.yellow(missing.slice(0, 3).join(", "))} but ${missing.length === 1 ? "it's" : "they're"} not set`);
2131
2194
  }
2132
2195
  } catch {
2133
2196
  }
2134
2197
  }
2135
2198
  if (tips.length > 0) {
2136
- console.log(chalk4.dim(" Next steps:"));
2199
+ console.log(chalk5.dim(" Next steps:"));
2137
2200
  for (const tip of tips.slice(0, 2)) {
2138
- console.log(chalk4.dim(` ${tip}`));
2201
+ console.log(chalk5.dim(` ${tip}`));
2139
2202
  }
2140
2203
  console.log();
2141
2204
  }
@@ -2153,12 +2216,12 @@ async function runDeploy(opts) {
2153
2216
  const logText = tail.map((e) => e.message).join("\n");
2154
2217
  const subcode = classifyBuildFailure(logText);
2155
2218
  const subcodeLabel = subcode.replace(/_/g, " ");
2156
- console.error(chalk4.red.bold(` Deploy failed. ${capitalize(subcodeLabel)}.`));
2157
- console.error(chalk4.dim(` Status: ${finalStatus}`));
2158
- console.error(chalk4.dim(" Your app is still running on the previous version."));
2219
+ console.error(chalk5.red.bold(` Deploy failed. ${capitalize(subcodeLabel)}.`));
2220
+ console.error(chalk5.dim(` Status: ${finalStatus}`));
2221
+ console.error(chalk5.dim(" Your app is still running on the previous version."));
2159
2222
  console.error();
2160
2223
  if (tail.length > 0) {
2161
- console.error(chalk4.dim(" Last log lines:"));
2224
+ console.error(chalk5.dim(" Last log lines:"));
2162
2225
  for (const ev of tail) {
2163
2226
  printDeploymentEvent(ev.level, ev.message, ev.meta, true);
2164
2227
  }
@@ -2166,13 +2229,13 @@ async function runDeploy(opts) {
2166
2229
  }
2167
2230
  const hint = buildFailureHint(subcode);
2168
2231
  if (hint) {
2169
- console.error(chalk4.cyan(` Fix hint: ${hint}`));
2232
+ console.error(chalk5.cyan(` Fix hint: ${hint}`));
2170
2233
  console.error();
2171
2234
  }
2172
2235
  console.error(
2173
- chalk4.dim(" Need a closer look? ") + chalk4.cyan(`doable doctor ${deployment.id}`)
2236
+ chalk5.dim(" Need a closer look? ") + chalk5.cyan(`doable doctor ${deployment.id}`)
2174
2237
  );
2175
- console.error(chalk4.dim(` Dashboard: ${chalk4.cyan(`doable.do/deployments/${deployment.id}`)}`));
2238
+ console.error(chalk5.dim(` Dashboard: ${chalk5.cyan(`doable.do/deployments/${deployment.id}`)}`));
2176
2239
  console.log();
2177
2240
  process.exit(1);
2178
2241
  }
@@ -2222,8 +2285,8 @@ async function runDeployFromRepo(opts) {
2222
2285
  );
2223
2286
  }
2224
2287
  console.log();
2225
- console.log(chalk4.bold(` Deploying ${chalk4.cyan(project.name)} (${project.slug})`));
2226
- console.log(chalk4.dim(` From: ${project.repoUrl} (branch: ${project.repoBranch || "main"})`));
2288
+ console.log(chalk5.bold(` Deploying ${chalk5.cyan(project.name)} (${project.slug})`));
2289
+ console.log(chalk5.dim(` From: ${project.repoUrl} (branch: ${project.repoBranch || "main"})`));
2227
2290
  console.log();
2228
2291
  try {
2229
2292
  await promptAddonSetup(projectDir, project.id, {
@@ -2231,16 +2294,16 @@ async function runDeployFromRepo(opts) {
2231
2294
  });
2232
2295
  } catch (err) {
2233
2296
  console.warn(
2234
- chalk4.yellow(
2297
+ chalk5.yellow(
2235
2298
  ` (Addon setup skipped: ${err instanceof Error ? err.message : String(err)})`
2236
2299
  )
2237
2300
  );
2238
2301
  }
2239
- const deploySpinner = ora9("Triggering deploy from GitHub...").start();
2302
+ const deploySpinner = ora10("Triggering deploy from GitHub...").start();
2240
2303
  const deployment = await createDeployment(project.id, { fromRepo: true });
2241
- deploySpinner.succeed(`Deployment started: ${chalk4.dim(deployment.id)}`);
2304
+ deploySpinner.succeed(`Deployment started: ${chalk5.dim(deployment.id)}`);
2242
2305
  console.log();
2243
- console.log(chalk4.dim(" --- Deployment logs ---"));
2306
+ console.log(chalk5.dim(" --- Deployment logs ---"));
2244
2307
  console.log();
2245
2308
  const finalStatus = await streamDeploymentEvents(deployment.id);
2246
2309
  console.log();
@@ -2252,19 +2315,19 @@ async function runDeployFromRepo(opts) {
2252
2315
  const startTime = new Date(finalDeploy.createdAt).getTime();
2253
2316
  const endTime = finalDeploy.deployFinishedAt ? new Date(finalDeploy.deployFinishedAt).getTime() : Date.now();
2254
2317
  const durationSec = Math.round((endTime - startTime) / 1e3);
2255
- console.log(chalk4.green.bold(" Live."));
2318
+ console.log(chalk5.green.bold(" Live."));
2256
2319
  if (liveUrl) {
2257
- console.log(` ${chalk4.underline.cyan(liveUrl)}`);
2320
+ console.log(` ${chalk5.underline.cyan(liveUrl)}`);
2258
2321
  }
2259
- console.log(chalk4.dim(` Deployed in ${durationSec}s`));
2322
+ console.log(chalk5.dim(` Deployed in ${durationSec}s`));
2260
2323
  console.log();
2261
2324
  const isFirstDeploy = finalDeploy.number === 1;
2262
2325
  if (isFirstDeploy && activeDomain?.hostname.endsWith(".doable.do") && process.stdout.isTTY) {
2263
2326
  await offerCustomDomain(project, activeDomain.hostname);
2264
2327
  }
2265
2328
  } else {
2266
- console.error(chalk4.red.bold(" Deploy didn't land."));
2267
- console.error(chalk4.red(` Status: ${finalStatus}`));
2329
+ console.error(chalk5.red.bold(" Deploy didn't land."));
2330
+ console.error(chalk5.red(` Status: ${finalStatus}`));
2268
2331
  console.log();
2269
2332
  process.exit(1);
2270
2333
  }
@@ -2359,31 +2422,31 @@ async function promptEnvSync(projectDir, projectId, opts) {
2359
2422
  if (verbose) {
2360
2423
  console.log();
2361
2424
  console.log(
2362
- chalk4.bold(
2425
+ chalk5.bold(
2363
2426
  ` Found ${missingOnServer.length} key${plural} in ${foundFiles.join(", ")} not yet set on the server:`
2364
2427
  )
2365
2428
  );
2366
2429
  for (const key of missingOnServer) {
2367
2430
  const cls = classifySecret(localKeys[key]);
2368
- const badge = cls ? chalk4.yellow(` [${cls.label}]`) : "";
2369
- console.log(` ${chalk4.cyan(key)}${badge}`);
2431
+ const badge = cls ? chalk5.yellow(` [${cls.label}]`) : "";
2432
+ console.log(` ${chalk5.cyan(key)}${badge}`);
2370
2433
  }
2371
2434
  console.log(
2372
- chalk4.dim(
2435
+ chalk5.dim(
2373
2436
  " Values are encrypted at rest with DOABLE_KMS_MASTER_KEY and never returned by the API."
2374
2437
  )
2375
2438
  );
2376
2439
  console.log();
2377
2440
  } else {
2378
- const preview = missingOnServer.slice(0, 3).map((k) => chalk4.cyan(k)).join(", ");
2441
+ const preview = missingOnServer.slice(0, 3).map((k) => chalk5.cyan(k)).join(", ");
2379
2442
  const more = missingOnServer.length > 3 ? ` +${missingOnServer.length - 3} more` : "";
2380
2443
  console.log(
2381
- chalk4.dim(` env: `) + `${missingOnServer.length} new key${plural} from ${foundFiles.join(", ")} \u2014 ${preview}${more}`
2444
+ chalk5.dim(` env: `) + `${missingOnServer.length} new key${plural} from ${foundFiles.join(", ")} \u2014 ${preview}${more}`
2382
2445
  );
2383
2446
  }
2384
2447
  if (opts.nonInteractive) {
2385
2448
  console.log(
2386
- chalk4.yellow(
2449
+ chalk5.yellow(
2387
2450
  " (Non-interactive run \u2014 skipping upload. Set these with `doable env set KEY=VALUE` or via the dashboard.)"
2388
2451
  )
2389
2452
  );
@@ -2400,7 +2463,7 @@ async function promptEnvSync(projectDir, projectId, opts) {
2400
2463
  if (uploadEnv) {
2401
2464
  const toUpload = {};
2402
2465
  for (const k of missingOnServer) toUpload[k] = localKeys[k];
2403
- const spin = ora9("Encrypting and uploading...").start();
2466
+ const spin = ora10("Encrypting and uploading...").start();
2404
2467
  try {
2405
2468
  const resp = await putEnvVars(projectId, toUpload);
2406
2469
  spin.succeed(
@@ -2427,41 +2490,41 @@ async function promptEnvSync(projectDir, projectId, opts) {
2427
2490
  const more = report.runtime.length > 3 ? ` +${report.runtime.length - 3} more` : "";
2428
2491
  if (verbose) {
2429
2492
  console.log(
2430
- chalk4.yellow(
2493
+ chalk5.yellow(
2431
2494
  ` \u26A0 ${report.runtime.length} env key${report.runtime.length === 1 ? " is" : "s are"} referenced in source but not set:`
2432
2495
  )
2433
2496
  );
2434
2497
  for (const k of report.runtime) {
2435
- console.log(` ${chalk4.yellow(k)}`);
2498
+ console.log(` ${chalk5.yellow(k)}`);
2436
2499
  }
2437
2500
  console.log(
2438
- chalk4.dim(
2501
+ chalk5.dim(
2439
2502
  " The deploy will continue. Your app may crash on first request if any of these are required."
2440
2503
  )
2441
2504
  );
2442
2505
  console.log();
2443
2506
  } else {
2444
2507
  console.log(
2445
- chalk4.yellow(` \u26A0 env: ${report.runtime.length} referenced but not set: ${firstThree}${more}`)
2508
+ chalk5.yellow(` \u26A0 env: ${report.runtime.length} referenced but not set: ${firstThree}${more}`)
2446
2509
  );
2447
2510
  }
2448
2511
  }
2449
2512
  if (report.buildTime.length > 0) {
2450
2513
  if (verbose) {
2451
2514
  console.log(
2452
- chalk4.yellow(
2515
+ chalk5.yellow(
2453
2516
  ` \u26A0 ${report.buildTime.length} NEXT_PUBLIC_* key${report.buildTime.length === 1 ? "" : "s"} referenced \u2014 these are inlined at build time, so setting them after deploy is a no-op. Set them BEFORE your next build.`
2454
2517
  )
2455
2518
  );
2456
2519
  for (const k of report.buildTime) {
2457
- console.log(` ${chalk4.yellow(k)}`);
2520
+ console.log(` ${chalk5.yellow(k)}`);
2458
2521
  }
2459
2522
  console.log();
2460
2523
  } else {
2461
2524
  const firstThree = report.buildTime.slice(0, 3).join(", ");
2462
2525
  const more = report.buildTime.length > 3 ? ` +${report.buildTime.length - 3} more` : "";
2463
2526
  console.log(
2464
- chalk4.yellow(` \u26A0 env (build-time): ${report.buildTime.length} NEXT_PUBLIC_* referenced but not set: ${firstThree}${more}`)
2527
+ chalk5.yellow(` \u26A0 env (build-time): ${report.buildTime.length} NEXT_PUBLIC_* referenced but not set: ${firstThree}${more}`)
2465
2528
  );
2466
2529
  }
2467
2530
  }
@@ -2502,7 +2565,7 @@ async function offerCustomDomain(project, subdomain) {
2502
2565
  return;
2503
2566
  }
2504
2567
  const msg = err instanceof Error ? err.message : String(err);
2505
- console.log(chalk4.dim(` (domain prompt skipped: ${msg})`));
2568
+ console.log(chalk5.dim(` (domain prompt skipped: ${msg})`));
2506
2569
  console.log();
2507
2570
  }
2508
2571
  }
@@ -2517,22 +2580,22 @@ async function runBuyFlow(project) {
2517
2580
  }
2518
2581
  ]);
2519
2582
  const hostname = domainName.trim().toLowerCase();
2520
- const searchSpinner = ora9(`Checking ${chalk4.bold(hostname)}...`).start();
2583
+ const searchSpinner = ora10(`Checking ${chalk5.bold(hostname)}...`).start();
2521
2584
  let result;
2522
2585
  try {
2523
2586
  result = await searchDomain(hostname);
2524
2587
  } catch (err) {
2525
2588
  searchSpinner.fail("Could not reach the domain registrar.");
2526
2589
  const msg = err instanceof Error ? err.message : String(err);
2527
- console.log(chalk4.dim(` ${msg}`));
2590
+ console.log(chalk5.dim(` ${msg}`));
2528
2591
  console.log();
2529
2592
  return;
2530
2593
  }
2531
2594
  searchSpinner.stop();
2532
2595
  if (!result.available) {
2533
- console.log(` ${chalk4.red("Not available.")} Try a different name or TLD.`);
2596
+ console.log(` ${chalk5.red("Not available.")} Try a different name or TLD.`);
2534
2597
  console.log(
2535
- chalk4.dim(` Hint: doable domain search <name> lets you poke around.`)
2598
+ chalk5.dim(` Hint: doable domain search <name> lets you poke around.`)
2536
2599
  );
2537
2600
  console.log();
2538
2601
  return;
@@ -2552,13 +2615,13 @@ async function runBuyFlow(project) {
2552
2615
  return searchUrl;
2553
2616
  })();
2554
2617
  console.log();
2555
- console.log(chalk4.bold(` Register ${hostname} via Namecheap${priceLine}`));
2618
+ console.log(chalk5.bold(` Register ${hostname} via Namecheap${priceLine}`));
2556
2619
  console.log();
2557
- console.log(` ${chalk4.cyan(namecheapUrl)}`);
2620
+ console.log(` ${chalk5.cyan(namecheapUrl)}`);
2558
2621
  console.log();
2559
- console.log(chalk4.dim(" Doable earns a small commission on this referral; price to you is unchanged."));
2560
- console.log(chalk4.dim(" Once registered, attach it to this project with:"));
2561
- console.log(` ${chalk4.cyan(`doable domain connect ${hostname} --project ${project.slug}`)}`);
2622
+ console.log(chalk5.dim(" Doable earns a small commission on this referral; price to you is unchanged."));
2623
+ console.log(chalk5.dim(" Once registered, attach it to this project with:"));
2624
+ console.log(` ${chalk5.cyan(`doable domain connect ${hostname} --project ${project.slug}`)}`);
2562
2625
  console.log();
2563
2626
  }
2564
2627
  async function runConnectFlow(project) {
@@ -2571,8 +2634,8 @@ async function runConnectFlow(project) {
2571
2634
  }
2572
2635
  ]);
2573
2636
  const cleanHostname = hostname.trim().toLowerCase();
2574
- const spinner = ora9(
2575
- `Reserving ${chalk4.bold(cleanHostname)} and generating verification record...`
2637
+ const spinner = ora10(
2638
+ `Reserving ${chalk5.bold(cleanHostname)} and generating verification record...`
2576
2639
  ).start();
2577
2640
  let resp;
2578
2641
  try {
@@ -2583,27 +2646,27 @@ async function runConnectFlow(project) {
2583
2646
  } catch (err) {
2584
2647
  spinner.fail("Couldn't reserve the domain.");
2585
2648
  const msg = err instanceof Error ? err.message : String(err);
2586
- console.log(chalk4.dim(` ${msg}`));
2649
+ console.log(chalk5.dim(` ${msg}`));
2587
2650
  console.log();
2588
2651
  return;
2589
2652
  }
2590
- spinner.succeed(`Reserved ${chalk4.bold(resp.domain.hostname)}.`);
2653
+ spinner.succeed(`Reserved ${chalk5.bold(resp.domain.hostname)}.`);
2591
2654
  console.log();
2592
- console.log(chalk4.bold(" Add this TXT record at your DNS provider:"));
2655
+ console.log(chalk5.bold(" Add this TXT record at your DNS provider:"));
2593
2656
  console.log();
2594
- console.log(` ${chalk4.dim("Type: ")}${chalk4.cyan("TXT")}`);
2595
- console.log(` ${chalk4.dim("Name: ")}${chalk4.cyan(resp.verification.name)}`);
2596
- console.log(` ${chalk4.dim("Value: ")}${chalk4.cyan(resp.verification.value)}`);
2657
+ console.log(` ${chalk5.dim("Type: ")}${chalk5.cyan("TXT")}`);
2658
+ console.log(` ${chalk5.dim("Name: ")}${chalk5.cyan(resp.verification.name)}`);
2659
+ console.log(` ${chalk5.dim("Value: ")}${chalk5.cyan(resp.verification.value)}`);
2597
2660
  console.log();
2598
2661
  console.log(
2599
- chalk4.dim(
2600
- ` Once propagated (usually 1-10 min), run: ${chalk4.cyan(
2662
+ chalk5.dim(
2663
+ ` Once propagated (usually 1-10 min), run: ${chalk5.cyan(
2601
2664
  `doable domain verify ${cleanHostname}`
2602
2665
  )}`
2603
2666
  )
2604
2667
  );
2605
2668
  console.log(
2606
- chalk4.dim(
2669
+ chalk5.dim(
2607
2670
  " Doable auto-issues TLS the moment verification succeeds."
2608
2671
  )
2609
2672
  );
@@ -2650,19 +2713,19 @@ async function streamDeploymentEvents(deploymentId) {
2650
2713
  });
2651
2714
  } catch (fetchErr) {
2652
2715
  const msg = fetchErr instanceof Error ? fetchErr.message : String(fetchErr);
2653
- console.warn(chalk4.yellow(`Could not connect to log stream (${msg}). Polling deployment status instead.`));
2716
+ console.warn(chalk5.yellow(`Could not connect to log stream (${msg}). Polling deployment status instead.`));
2654
2717
  const err = new Error("SSE unavailable");
2655
2718
  err.name = "SSEUnavailable";
2656
2719
  throw err;
2657
2720
  }
2658
2721
  if (!res.ok) {
2659
- console.warn(chalk4.yellow(`Could not connect to log stream (${res.statusText || `HTTP ${res.status}`}). Polling deployment status instead.`));
2722
+ console.warn(chalk5.yellow(`Could not connect to log stream (${res.statusText || `HTTP ${res.status}`}). Polling deployment status instead.`));
2660
2723
  const err = new Error("SSE unavailable");
2661
2724
  err.name = "SSEUnavailable";
2662
2725
  throw err;
2663
2726
  }
2664
2727
  if (!res.body) {
2665
- console.warn(chalk4.yellow("Log stream had no response body. Polling deployment status instead."));
2728
+ console.warn(chalk5.yellow("Log stream had no response body. Polling deployment status instead."));
2666
2729
  const err = new Error("SSE unavailable");
2667
2730
  err.name = "SSEUnavailable";
2668
2731
  throw err;
@@ -2707,7 +2770,7 @@ async function streamDeploymentEvents(deploymentId) {
2707
2770
  }
2708
2771
  } catch (err) {
2709
2772
  if (err instanceof Error && (err.name === "AbortError" || err.name === "SSEUnavailable")) ; else {
2710
- console.error(chalk4.red(`Log stream error: ${err instanceof Error ? err.message : String(err)}`));
2773
+ console.error(chalk5.red(`Log stream error: ${err instanceof Error ? err.message : String(err)}`));
2711
2774
  }
2712
2775
  }
2713
2776
  const terminalStatuses = ["healthy", "failed", "canceled", "rolled_back", "suspended"];
@@ -2723,7 +2786,7 @@ async function streamDeploymentEvents(deploymentId) {
2723
2786
  }
2724
2787
  if (!droppedNoticed) {
2725
2788
  console.log(
2726
- chalk4.dim(` (Stream disconnected, build still running. Polling\u2026)`)
2789
+ chalk5.dim(` (Stream disconnected, build still running. Polling\u2026)`)
2727
2790
  );
2728
2791
  droppedNoticed = true;
2729
2792
  }
@@ -2740,13 +2803,13 @@ function processEvent(type, data) {
2740
2803
  const message = parsed.message || data;
2741
2804
  const level = parsed.level || type;
2742
2805
  if (parsed.message === "live_url_ready" && parsed.meta && typeof parsed.meta.liveUrl === "string") {
2743
- console.log(` ${chalk4.green("\u2192")} ${chalk4.underline.cyan(parsed.meta.liveUrl)} ${chalk4.dim("(warming up\u2026)")}`);
2806
+ console.log(` ${chalk5.green("\u2192")} ${chalk5.underline.cyan(parsed.meta.liveUrl)} ${chalk5.dim("(warming up\u2026)")}`);
2744
2807
  return;
2745
2808
  }
2746
2809
  printDeploymentEvent(level, message, parsed.meta);
2747
2810
  } catch {
2748
2811
  if (data.trim()) {
2749
- console.log(` ${chalk4.dim("LOG")} ${data}`);
2812
+ console.log(` ${chalk5.dim("LOG")} ${data}`);
2750
2813
  }
2751
2814
  }
2752
2815
  }
@@ -2755,33 +2818,33 @@ function printDeploymentEvent(level, message, meta, stderr = false) {
2755
2818
  const isOriginalError = meta?.originalError === true;
2756
2819
  const isPlatformFailure = meta?.failureCategory === "platform";
2757
2820
  const isAutoFix = meta?.autoFix === true;
2758
- const suffix = isPlatformFailure ? chalk4.yellow(" (Doable-side)") : "";
2821
+ const suffix = isPlatformFailure ? chalk5.yellow(" (Doable-side)") : "";
2759
2822
  let label;
2760
2823
  switch (level) {
2761
2824
  case "error":
2762
- label = chalk4.red("ERROR");
2825
+ label = chalk5.red("ERROR");
2763
2826
  break;
2764
2827
  case "warning":
2765
2828
  case "warn":
2766
- label = chalk4.yellow("WARN ");
2829
+ label = chalk5.yellow("WARN ");
2767
2830
  break;
2768
2831
  case "success":
2769
2832
  case "done":
2770
2833
  case "complete":
2771
- label = chalk4.green("OK ");
2834
+ label = chalk5.green("OK ");
2772
2835
  break;
2773
2836
  case "step":
2774
- label = chalk4.blue("STEP ");
2837
+ label = chalk5.blue("STEP ");
2775
2838
  break;
2776
2839
  default:
2777
- label = isAutoFix ? chalk4.green("FIX ") : chalk4.dim("LOG ");
2840
+ label = isAutoFix ? chalk5.green("FIX ") : chalk5.dim("LOG ");
2778
2841
  break;
2779
2842
  }
2780
2843
  const lines = message.split(/\r?\n/);
2781
2844
  if (isOriginalError) {
2782
- write(` ${label} ${chalk4.dim(lines[0] ?? "Original error:")}${suffix}`);
2845
+ write(` ${label} ${chalk5.dim(lines[0] ?? "Original error:")}${suffix}`);
2783
2846
  for (const line of lines.slice(1)) {
2784
- write(chalk4.dim(` ${line}`));
2847
+ write(chalk5.dim(` ${line}`));
2785
2848
  }
2786
2849
  return;
2787
2850
  }
@@ -2842,7 +2905,7 @@ function printQr(url) {
2842
2905
  console.log();
2843
2906
  qrcode.generate(url, { small: true }, (output) => {
2844
2907
  for (const line of output.split("\n")) {
2845
- console.log(` ${chalk4.dim(line)}`);
2908
+ console.log(` ${chalk5.dim(line)}`);
2846
2909
  }
2847
2910
  });
2848
2911
  console.log();
@@ -2888,22 +2951,22 @@ function registerProjectsCommand(program2) {
2888
2951
  );
2889
2952
  }
2890
2953
  async function runListProjects() {
2891
- const spinner = ora9("Fetching projects...").start();
2954
+ const spinner = ora10("Fetching projects...").start();
2892
2955
  const projects = await listProjects();
2893
2956
  spinner.stop();
2894
2957
  if (projects.length === 0) {
2895
- console.log(chalk4.dim("\n No projects yet. Create one with `doable projects create`.\n"));
2958
+ console.log(chalk5.dim("\n No projects yet. Create one with `doable projects create`.\n"));
2896
2959
  return;
2897
2960
  }
2898
2961
  console.log();
2899
- console.log(chalk4.bold(" Your Projects"));
2962
+ console.log(chalk5.bold(" Your Projects"));
2900
2963
  console.log();
2901
2964
  const nameWidth = Math.max(6, ...projects.map((p) => p.name.length));
2902
2965
  const slugWidth = Math.max(6, ...projects.map((p) => p.slug.length));
2903
2966
  const runtimeWidth = Math.max(7, ...projects.map((p) => p.runtime.length));
2904
2967
  const header = ` ${pad("NAME", nameWidth)} ${pad("SLUG", slugWidth)} ${pad("RUNTIME", runtimeWidth)} CREATED`;
2905
- console.log(chalk4.dim(header));
2906
- console.log(chalk4.dim(" " + "-".repeat(header.length - 2)));
2968
+ console.log(chalk5.dim(header));
2969
+ console.log(chalk5.dim(" " + "-".repeat(header.length - 2)));
2907
2970
  for (const project of projects) {
2908
2971
  const createdAt = formatDate(project.createdAt);
2909
2972
  const row = ` ${pad(project.name, nameWidth)} ${pad(project.slug, slugWidth)} ${pad(project.runtime, runtimeWidth)} ${createdAt}`;
@@ -2961,7 +3024,7 @@ async function runCreateProject(opts = {}) {
2961
3024
  }
2962
3025
  } else {
2963
3026
  console.log();
2964
- console.log(chalk4.bold(" Create a new project"));
3027
+ console.log(chalk5.bold(" Create a new project"));
2965
3028
  console.log();
2966
3029
  const answers = await inquirer3.prompt(
2967
3030
  [
@@ -3000,7 +3063,7 @@ async function runCreateProject(opts = {}) {
3000
3063
  slug = slug ?? answers.slug;
3001
3064
  runtime = runtime ?? answers.runtime;
3002
3065
  }
3003
- const spinner = ora9("Creating project...").start();
3066
+ const spinner = ora10("Creating project...").start();
3004
3067
  let repoUrl;
3005
3068
  let repoBranch;
3006
3069
  let githubPat;
@@ -3037,20 +3100,20 @@ async function runCreateProject(opts = {}) {
3037
3100
  }
3038
3101
  throw err;
3039
3102
  }
3040
- spinner.succeed(`Project ${chalk4.bold(resp.project.name)} created!`);
3041
- console.log(chalk4.dim(` Slug: ${resp.project.slug}`));
3042
- console.log(chalk4.dim(` ID: ${resp.project.id}`));
3103
+ spinner.succeed(`Project ${chalk5.bold(resp.project.name)} created!`);
3104
+ console.log(chalk5.dim(` Slug: ${resp.project.slug}`));
3105
+ console.log(chalk5.dim(` ID: ${resp.project.id}`));
3043
3106
  if (resp.subdomain) {
3044
- console.log(chalk4.dim(` URL: https://${resp.subdomain}`));
3107
+ console.log(chalk5.dim(` URL: https://${resp.subdomain}`));
3045
3108
  }
3046
3109
  if (repoUrl) {
3047
- console.log(chalk4.dim(` Repo: ${repoUrl} (branch: ${repoBranch || "repo default"}${githubPat ? ", private" : ""})`));
3110
+ console.log(chalk5.dim(` Repo: ${repoUrl} (branch: ${repoBranch || "repo default"}${githubPat ? ", private" : ""})`));
3048
3111
  }
3049
3112
  console.log();
3050
3113
  if (repoUrl) {
3051
- console.log(` Deploy with: ${chalk4.cyan(`doable deploy --from-repo --project ${resp.project.id}`)}`);
3114
+ console.log(` Deploy with: ${chalk5.cyan(`doable deploy --from-repo --project ${resp.project.id}`)}`);
3052
3115
  } else {
3053
- console.log(` Deploy with: ${chalk4.cyan(`doable deploy --project ${resp.project.id}`)}`);
3116
+ console.log(` Deploy with: ${chalk5.cyan(`doable deploy --project ${resp.project.id}`)}`);
3054
3117
  }
3055
3118
  console.log();
3056
3119
  }
@@ -3072,33 +3135,33 @@ function formatDate(isoDate) {
3072
3135
  function handleError(err) {
3073
3136
  if (err instanceof ApiError) {
3074
3137
  if (err.status === 401) {
3075
- console.error(chalk4.red("Not authenticated. Run `doable login` first."));
3138
+ console.error(chalk5.red("Not authenticated. Run `doable login` first."));
3076
3139
  } else if (err.status === 0) {
3077
- console.error(chalk4.red(`Connection error: ${err.message}`));
3140
+ console.error(chalk5.red(`Connection error: ${err.message}`));
3078
3141
  } else if (/project limit reached/i.test(err.message)) {
3079
3142
  handlePlanLimitReached(err.message).finally(() => process.exit(1));
3080
3143
  return;
3081
3144
  } else {
3082
- console.error(chalk4.red(`Error: ${err.message}`));
3145
+ console.error(chalk5.red(`Error: ${err.message}`));
3083
3146
  }
3084
3147
  } else if (err instanceof Error) {
3085
- console.error(chalk4.red(`Error: ${err.message}`));
3148
+ console.error(chalk5.red(`Error: ${err.message}`));
3086
3149
  } else {
3087
- console.error(chalk4.red(`Unexpected error: ${String(err)}`));
3150
+ console.error(chalk5.red(`Unexpected error: ${String(err)}`));
3088
3151
  }
3089
3152
  process.exit(1);
3090
3153
  }
3091
3154
  async function handlePlanLimitReached(apiMessage) {
3092
3155
  const upgradeUrl = "https://doable.do/billing";
3093
3156
  console.log();
3094
- console.log(chalk4.yellow.bold(" You've reached your plan's project limit."));
3157
+ console.log(chalk5.yellow.bold(" You've reached your plan's project limit."));
3095
3158
  console.log();
3096
- console.log(chalk4.dim(` ${apiMessage}`));
3159
+ console.log(chalk5.dim(` ${apiMessage}`));
3097
3160
  console.log();
3098
3161
  console.log(" Upgrade your plan to create more projects:");
3099
- console.log(` ${chalk4.cyan.underline(upgradeUrl)}`);
3162
+ console.log(` ${chalk5.cyan.underline(upgradeUrl)}`);
3100
3163
  console.log();
3101
- console.log(chalk4.dim(" Free: 1 project Starter: 3 projects Pro: 10 projects"));
3164
+ console.log(chalk5.dim(" Free: 1 project Starter: 3 projects Pro: 10 projects"));
3102
3165
  console.log();
3103
3166
  if (!process.stdin.isTTY) return;
3104
3167
  const { open: open4 } = await inquirer3.prompt([
@@ -3114,7 +3177,7 @@ async function handlePlanLimitReached(apiMessage) {
3114
3177
  const { default: openBrowser2 } = await import('open');
3115
3178
  await openBrowser2(upgradeUrl);
3116
3179
  } catch {
3117
- console.log(chalk4.dim(` Open manually: ${upgradeUrl}`));
3180
+ console.log(chalk5.dim(` Open manually: ${upgradeUrl}`));
3118
3181
  }
3119
3182
  }
3120
3183
  }
@@ -3169,37 +3232,37 @@ function registerDomainCommand(program2) {
3169
3232
  });
3170
3233
  }
3171
3234
  async function runSearch(name) {
3172
- const spinner = ora9(`Checking availability for ${chalk4.bold(name)}...`).start();
3235
+ const spinner = ora10(`Checking availability for ${chalk5.bold(name)}...`).start();
3173
3236
  const result = await searchDomain(name);
3174
3237
  spinner.stop();
3175
3238
  console.log();
3176
- console.log(chalk4.bold(` Domain: ${result.domain}`));
3239
+ console.log(chalk5.bold(` Domain: ${result.domain}`));
3177
3240
  console.log();
3178
3241
  if (result.available) {
3179
- console.log(` Status: ${chalk4.green("Available")}`);
3242
+ console.log(` Status: ${chalk5.green("Available")}`);
3180
3243
  if (result.pricing) {
3181
3244
  for (const [tld, prices] of Object.entries(result.pricing)) {
3182
- console.log(` Price (${tld}): ${chalk4.bold(`$${prices.registration}`)}/year (renewal: $${prices.renewal}/year)`);
3245
+ console.log(` Price (${tld}): ${chalk5.bold(`$${prices.registration}`)}/year (renewal: $${prices.renewal}/year)`);
3183
3246
  }
3184
3247
  }
3185
3248
  console.log();
3186
3249
  console.log(
3187
- ` Register at: ${chalk4.cyan(buildNamecheapBuyUrl(result.domain))}`
3250
+ ` Register at: ${chalk5.cyan(buildNamecheapBuyUrl(result.domain))}`
3188
3251
  );
3189
3252
  console.log(
3190
- chalk4.dim(
3253
+ chalk5.dim(
3191
3254
  " (Opens Namecheap. Once you own it, run `doable domain connect` to point it at your project.)"
3192
3255
  )
3193
3256
  );
3194
3257
  } else {
3195
- console.log(` Status: ${chalk4.red("Not available")}`);
3258
+ console.log(` Status: ${chalk5.red("Not available")}`);
3196
3259
  console.log();
3197
- console.log(chalk4.dim(" Try a different name or TLD."));
3260
+ console.log(chalk5.dim(" Try a different name or TLD."));
3198
3261
  }
3199
3262
  console.log();
3200
3263
  }
3201
3264
  async function runBuy(hostname) {
3202
- const searchSpinner = ora9(`Checking availability for ${chalk4.bold(hostname)}...`).start();
3265
+ const searchSpinner = ora10(`Checking availability for ${chalk5.bold(hostname)}...`).start();
3203
3266
  let searchResult;
3204
3267
  try {
3205
3268
  searchResult = await searchDomain(hostname);
@@ -3208,75 +3271,75 @@ async function runBuy(hostname) {
3208
3271
  searchSpinner.stop();
3209
3272
  }
3210
3273
  if (searchResult && !searchResult.available) {
3211
- console.error(chalk4.red(`
3274
+ console.error(chalk5.red(`
3212
3275
  Domain ${hostname} is not available.
3213
3276
  `));
3214
3277
  process.exit(1);
3215
3278
  }
3216
3279
  const url = buildNamecheapBuyUrl(hostname);
3217
3280
  console.log();
3218
- console.log(chalk4.bold(` Register ${hostname} via Namecheap`));
3281
+ console.log(chalk5.bold(` Register ${hostname} via Namecheap`));
3219
3282
  if (searchResult?.pricing) {
3220
3283
  const tld = hostname.split(".").slice(1).join(".");
3221
3284
  const pricing = searchResult.pricing[tld];
3222
3285
  if (pricing) {
3223
3286
  console.log(
3224
- ` Indicative price: ${chalk4.bold(`$${pricing.registration}`)} / year (renewal $${pricing.renewal})`
3287
+ ` Indicative price: ${chalk5.bold(`$${pricing.registration}`)} / year (renewal $${pricing.renewal})`
3225
3288
  );
3226
3289
  }
3227
3290
  }
3228
3291
  console.log();
3229
- console.log(` ${chalk4.cyan(url)}`);
3292
+ console.log(` ${chalk5.cyan(url)}`);
3230
3293
  console.log();
3231
- console.log(chalk4.dim(" Doable earns a small commission on this referral; price to you is unchanged."));
3232
- console.log(chalk4.dim(" Once registered, run:"));
3233
- console.log(` ${chalk4.cyan(`doable domain connect ${hostname} --project <id>`)}`);
3294
+ console.log(chalk5.dim(" Doable earns a small commission on this referral; price to you is unchanged."));
3295
+ console.log(chalk5.dim(" Once registered, run:"));
3296
+ console.log(` ${chalk5.cyan(`doable domain connect ${hostname} --project <id>`)}`);
3234
3297
  console.log();
3235
3298
  tryOpenUrl(url);
3236
3299
  }
3237
3300
  async function runConnect(hostname, projectId) {
3238
- const projectSpinner = ora9("Resolving project...").start();
3301
+ const projectSpinner = ora10("Resolving project...").start();
3239
3302
  const project = await getProject(projectId);
3240
3303
  projectSpinner.stop();
3241
- const connectSpinner = ora9(`Connecting ${chalk4.bold(hostname)}...`).start();
3304
+ const connectSpinner = ora10(`Connecting ${chalk5.bold(hostname)}...`).start();
3242
3305
  const resp = await connectDomain({
3243
3306
  hostname,
3244
3307
  projectId: project.id
3245
3308
  });
3246
- connectSpinner.succeed(`Domain ${chalk4.bold(hostname)} connection started.`);
3309
+ connectSpinner.succeed(`Domain ${chalk5.bold(hostname)} connection started.`);
3247
3310
  console.log();
3248
- console.log(chalk4.bold(" Configure the following DNS record with your registrar:"));
3311
+ console.log(chalk5.bold(" Configure the following DNS record with your registrar:"));
3249
3312
  console.log();
3250
3313
  const v = resp.verification;
3251
3314
  const typeWidth = v.type.length;
3252
3315
  const nameWidth = v.name.length;
3253
3316
  console.log(
3254
- chalk4.dim(` ${pad2("TYPE", typeWidth)} ${pad2("NAME", nameWidth)} VALUE`)
3317
+ chalk5.dim(` ${pad2("TYPE", typeWidth)} ${pad2("NAME", nameWidth)} VALUE`)
3255
3318
  );
3256
- console.log(chalk4.dim(" " + "-".repeat(typeWidth + nameWidth + 40)));
3319
+ console.log(chalk5.dim(" " + "-".repeat(typeWidth + nameWidth + 40)));
3257
3320
  console.log(
3258
3321
  ` ${pad2(v.type, typeWidth)} ${pad2(v.name, nameWidth)} ${v.value}`
3259
3322
  );
3260
3323
  console.log();
3261
3324
  console.log(
3262
- ` After configuring DNS, verify with: ${chalk4.cyan(`doable domain verify ${resp.domain.id}`)}`
3325
+ ` After configuring DNS, verify with: ${chalk5.cyan(`doable domain verify ${resp.domain.id}`)}`
3263
3326
  );
3264
3327
  console.log();
3265
3328
  }
3266
3329
  async function runVerify(domainId) {
3267
- const spinner = ora9(`Verifying DNS for domain ${chalk4.bold(domainId)}...`).start();
3330
+ const spinner = ora10(`Verifying DNS for domain ${chalk5.bold(domainId)}...`).start();
3268
3331
  const resp = await verifyDomain(domainId);
3269
3332
  if (resp.verified) {
3270
- spinner.succeed(`Domain is ${chalk4.green("verified")} and active!`);
3333
+ spinner.succeed(`Domain is ${chalk5.green("verified")} and active!`);
3271
3334
  } else {
3272
- spinner.fail(`Domain DNS verification ${chalk4.red("failed")}.`);
3335
+ spinner.fail(`Domain DNS verification ${chalk5.red("failed")}.`);
3273
3336
  console.log();
3274
3337
  console.log(
3275
- chalk4.yellow(
3338
+ chalk5.yellow(
3276
3339
  " DNS records have not propagated yet. This can take up to 48 hours."
3277
3340
  )
3278
3341
  );
3279
- console.log(` Run ${chalk4.cyan(`doable domain verify ${domainId}`)} again later.`);
3342
+ console.log(` Run ${chalk5.cyan(`doable domain verify ${domainId}`)} again later.`);
3280
3343
  }
3281
3344
  console.log();
3282
3345
  }
@@ -3286,18 +3349,18 @@ function pad2(str, width) {
3286
3349
  function handleError2(err) {
3287
3350
  if (err instanceof ApiError) {
3288
3351
  if (err.status === 401) {
3289
- console.error(chalk4.red("Not authenticated. Run `doable login` first."));
3352
+ console.error(chalk5.red("Not authenticated. Run `doable login` first."));
3290
3353
  } else if (err.status === 0) {
3291
- console.error(chalk4.red(`Connection error: ${err.message}`));
3354
+ console.error(chalk5.red(`Connection error: ${err.message}`));
3292
3355
  } else if (err.status === 404) {
3293
- console.error(chalk4.red(`Not found: ${err.message}`));
3356
+ console.error(chalk5.red(`Not found: ${err.message}`));
3294
3357
  } else {
3295
- console.error(chalk4.red(`Error: ${err.message}`));
3358
+ console.error(chalk5.red(`Error: ${err.message}`));
3296
3359
  }
3297
3360
  } else if (err instanceof Error) {
3298
- console.error(chalk4.red(`Error: ${err.message}`));
3361
+ console.error(chalk5.red(`Error: ${err.message}`));
3299
3362
  } else {
3300
- console.error(chalk4.red(`Unexpected error: ${String(err)}`));
3363
+ console.error(chalk5.red(`Unexpected error: ${String(err)}`));
3301
3364
  }
3302
3365
  process.exit(1);
3303
3366
  }
@@ -3334,24 +3397,24 @@ function registerServerCommand(program2) {
3334
3397
  });
3335
3398
  }
3336
3399
  async function runSetAddonsEnabled(serverId, enabled) {
3337
- const spinner = ora9(
3400
+ const spinner = ora10(
3338
3401
  enabled ? "Enabling addons on this server..." : "Disabling addons on this server..."
3339
3402
  ).start();
3340
3403
  try {
3341
3404
  const updated = await updateServer(serverId, { addonsEnabled: enabled });
3342
3405
  spinner.succeed(
3343
- enabled ? `Addons enabled on ${chalk4.bold(updated.name)}.` : `Addons disabled on ${chalk4.bold(updated.name)}.`
3406
+ enabled ? `Addons enabled on ${chalk5.bold(updated.name)}.` : `Addons disabled on ${chalk5.bold(updated.name)}.`
3344
3407
  );
3345
3408
  console.log();
3346
3409
  if (enabled) {
3347
3410
  console.log(
3348
- chalk4.dim(
3411
+ chalk5.dim(
3349
3412
  " This BYO server will now receive new native addons (Postgres, Redis,\n MongoDB) for your account. Existing addons on cloud servers are not\n moved \u2014 only newly-provisioned ones will go here. Make sure the host\n has enough disk and memory for database workloads."
3350
3413
  )
3351
3414
  );
3352
3415
  } else {
3353
3416
  console.log(
3354
- chalk4.dim(
3417
+ chalk5.dim(
3355
3418
  " New native addons will go to Doable Cloud instead. Any addons already\n running on this server are unaffected \u2014 delete them via the dashboard\n if you want to free up resources."
3356
3419
  )
3357
3420
  );
@@ -3363,52 +3426,52 @@ async function runSetAddonsEnabled(serverId, enabled) {
3363
3426
  }
3364
3427
  }
3365
3428
  async function runAddServer(name) {
3366
- const spinner = ora9("Registering server...").start();
3429
+ const spinner = ora10("Registering server...").start();
3367
3430
  const resp = await addServer({ name });
3368
3431
  const srv = resp.server;
3369
- spinner.succeed(`Server ${chalk4.bold(srv.name)} registered.`);
3432
+ spinner.succeed(`Server ${chalk5.bold(srv.name)} registered.`);
3370
3433
  console.log();
3371
- console.log(chalk4.bold(" Install the Doable agent on your server:"));
3434
+ console.log(chalk5.bold(" Install the Doable agent on your server:"));
3372
3435
  console.log();
3373
3436
  console.log(
3374
- ` ${chalk4.cyan(`curl -sSL https://doable.do/install-agent.sh | sh -s -- --enrollment-token ${resp.enrollmentToken}`)}`
3437
+ ` ${chalk5.cyan(`curl -sSL https://doable.do/install-agent.sh | sh -s -- --enrollment-token ${resp.enrollmentToken}`)}`
3375
3438
  );
3376
3439
  console.log();
3377
3440
  console.log(
3378
- chalk4.dim(" Run this command on the server you want to connect. The agent will")
3441
+ chalk5.dim(" Run this command on the server you want to connect. The agent will")
3379
3442
  );
3380
3443
  console.log(
3381
- chalk4.dim(" register itself and appear as 'online' in `doable server list`.")
3444
+ chalk5.dim(" register itself and appear as 'online' in `doable server list`.")
3382
3445
  );
3383
3446
  console.log();
3384
- console.log(chalk4.dim(` Server ID: ${srv.id}`));
3385
- console.log(chalk4.dim(` Status: ${srv.status}`));
3386
- console.log(chalk4.dim(` Token expires: ${resp.expiresAt}`));
3447
+ console.log(chalk5.dim(` Server ID: ${srv.id}`));
3448
+ console.log(chalk5.dim(` Status: ${srv.status}`));
3449
+ console.log(chalk5.dim(` Token expires: ${resp.expiresAt}`));
3387
3450
  console.log();
3388
3451
  }
3389
3452
  async function runListServers() {
3390
- const spinner = ora9("Fetching servers...").start();
3453
+ const spinner = ora10("Fetching servers...").start();
3391
3454
  const servers = await listServers();
3392
3455
  spinner.stop();
3393
3456
  if (servers.length === 0) {
3394
- console.log(chalk4.dim("\n No servers registered. Add one with `doable server add --name <name>`.\n"));
3457
+ console.log(chalk5.dim("\n No servers registered. Add one with `doable server add --name <name>`.\n"));
3395
3458
  return;
3396
3459
  }
3397
3460
  console.log();
3398
- console.log(chalk4.bold(" Your Servers"));
3461
+ console.log(chalk5.bold(" Your Servers"));
3399
3462
  console.log();
3400
3463
  const nameWidth = Math.max(4, ...servers.map((s) => s.name.length));
3401
3464
  const statusWidth = Math.max(6, ...servers.map((s) => s.status.length));
3402
3465
  const ipWidth = Math.max(10, ...servers.map((s) => (s.publicIp || "--").length));
3403
3466
  const header = ` ${pad3("NAME", nameWidth)} ${pad3("STATUS", statusWidth)} ${pad3("IP ADDRESS", ipWidth)} ${pad3("ADDONS", 8)} LAST SEEN`;
3404
- console.log(chalk4.dim(header));
3405
- console.log(chalk4.dim(" " + "-".repeat(header.length - 2)));
3467
+ console.log(chalk5.dim(header));
3468
+ console.log(chalk5.dim(" " + "-".repeat(header.length - 2)));
3406
3469
  for (const srv of servers) {
3407
3470
  const statusColor = getStatusColor(srv.status);
3408
- const lastSeen = srv.lastSeenAt ? formatDate2(srv.lastSeenAt) : chalk4.dim("never");
3409
- const ip = srv.publicIp || chalk4.dim("--");
3471
+ const lastSeen = srv.lastSeenAt ? formatDate2(srv.lastSeenAt) : chalk5.dim("never");
3472
+ const ip = srv.publicIp || chalk5.dim("--");
3410
3473
  const addonsRaw = srv.type === "doable_cloud" ? "cloud" : srv.addonsEnabled ? "on" : "off";
3411
- const addonsColor = srv.type === "doable_cloud" ? chalk4.green : srv.addonsEnabled ? chalk4.green : chalk4.dim;
3474
+ const addonsColor = srv.type === "doable_cloud" ? chalk5.green : srv.addonsEnabled ? chalk5.green : chalk5.dim;
3412
3475
  const addonsCell = addonsColor(pad3(addonsRaw, 8));
3413
3476
  console.log(
3414
3477
  ` ${pad3(srv.name, nameWidth)} ${statusColor(pad3(srv.status, statusWidth))} ${pad3(String(ip), ipWidth)} ${addonsCell} ${lastSeen}`
@@ -3436,30 +3499,30 @@ function getStatusColor(status) {
3436
3499
  case "online":
3437
3500
  case "connected":
3438
3501
  case "active":
3439
- return chalk4.green;
3502
+ return chalk5.green;
3440
3503
  case "pending":
3441
3504
  case "connecting":
3442
- return chalk4.yellow;
3505
+ return chalk5.yellow;
3443
3506
  case "offline":
3444
3507
  case "disconnected":
3445
- return chalk4.red;
3508
+ return chalk5.red;
3446
3509
  default:
3447
- return chalk4.dim;
3510
+ return chalk5.dim;
3448
3511
  }
3449
3512
  }
3450
3513
  function handleError3(err) {
3451
3514
  if (err instanceof ApiError) {
3452
3515
  if (err.status === 401) {
3453
- console.error(chalk4.red("Not authenticated. Run `doable login` first."));
3516
+ console.error(chalk5.red("Not authenticated. Run `doable login` first."));
3454
3517
  } else if (err.status === 0) {
3455
- console.error(chalk4.red(`Connection error: ${err.message}`));
3518
+ console.error(chalk5.red(`Connection error: ${err.message}`));
3456
3519
  } else {
3457
- console.error(chalk4.red(`Error: ${err.message}`));
3520
+ console.error(chalk5.red(`Error: ${err.message}`));
3458
3521
  }
3459
3522
  } else if (err instanceof Error) {
3460
- console.error(chalk4.red(`Error: ${err.message}`));
3523
+ console.error(chalk5.red(`Error: ${err.message}`));
3461
3524
  } else {
3462
- console.error(chalk4.red(`Unexpected error: ${String(err)}`));
3525
+ console.error(chalk5.red(`Unexpected error: ${String(err)}`));
3463
3526
  }
3464
3527
  process.exit(1);
3465
3528
  }
@@ -3475,22 +3538,22 @@ function registerLogsCommand(program2) {
3475
3538
  async function runLogs(deploymentId, follow) {
3476
3539
  const token = getToken();
3477
3540
  if (!token) {
3478
- console.error(chalk4.red("Not authenticated. Run `doable login` first."));
3541
+ console.error(chalk5.red("Not authenticated. Run `doable login` first."));
3479
3542
  process.exit(1);
3480
3543
  }
3481
- const verifySpinner = ora9("Connecting to deployment...").start();
3544
+ const verifySpinner = ora10("Connecting to deployment...").start();
3482
3545
  let deployment;
3483
3546
  try {
3484
3547
  deployment = await getDeployment(deploymentId);
3485
3548
  } catch (err) {
3486
3549
  verifySpinner.fail("Failed to find deployment.");
3487
3550
  if (err instanceof ApiError) {
3488
- console.error(chalk4.red(err.message));
3551
+ console.error(chalk5.red(err.message));
3489
3552
  }
3490
3553
  process.exit(1);
3491
3554
  }
3492
3555
  verifySpinner.succeed(
3493
- `Streaming logs for deployment ${chalk4.dim(deployment.id)} (${deployment.status})`
3556
+ `Streaming logs for deployment ${chalk5.dim(deployment.id)} (${deployment.status})`
3494
3557
  );
3495
3558
  console.log();
3496
3559
  const url = getLogsStreamUrl(deploymentId);
@@ -3505,20 +3568,20 @@ async function runLogs(deploymentId, follow) {
3505
3568
  });
3506
3569
  } catch (fetchErr) {
3507
3570
  const msg = fetchErr instanceof Error ? fetchErr.message : String(fetchErr);
3508
- console.error(chalk4.red(`Failed to connect to log stream: ${msg}`));
3571
+ console.error(chalk5.red(`Failed to connect to log stream: ${msg}`));
3509
3572
  process.exit(1);
3510
3573
  return;
3511
3574
  }
3512
3575
  if (!res.ok) {
3513
3576
  if (res.status === 401) {
3514
- console.error(chalk4.red("Not authenticated. Run `doable login` first."));
3577
+ console.error(chalk5.red("Not authenticated. Run `doable login` first."));
3515
3578
  } else {
3516
- console.error(chalk4.red(`Failed to connect to log stream: ${res.statusText || `HTTP ${res.status}`}`));
3579
+ console.error(chalk5.red(`Failed to connect to log stream: ${res.statusText || `HTTP ${res.status}`}`));
3517
3580
  }
3518
3581
  process.exit(1);
3519
3582
  }
3520
3583
  if (!res.body) {
3521
- console.error(chalk4.red("No response body from log stream."));
3584
+ console.error(chalk5.red("No response body from log stream."));
3522
3585
  process.exit(1);
3523
3586
  }
3524
3587
  const reader = res.body.getReader();
@@ -3542,9 +3605,9 @@ async function runLogs(deploymentId, follow) {
3542
3605
  if (eventType === "done" || eventType === "complete" || eventType === "error") {
3543
3606
  console.log();
3544
3607
  if (eventType === "error") {
3545
- console.log(chalk4.red.bold(" Deployment failed."));
3608
+ console.log(chalk5.red.bold(" Deployment failed."));
3546
3609
  } else {
3547
- console.log(chalk4.green.bold(" Deployment complete."));
3610
+ console.log(chalk5.green.bold(" Deployment complete."));
3548
3611
  }
3549
3612
  console.log();
3550
3613
  return;
@@ -3555,14 +3618,14 @@ async function runLogs(deploymentId, follow) {
3555
3618
  }
3556
3619
  }
3557
3620
  console.log();
3558
- console.log(chalk4.dim(" Log stream ended."));
3621
+ console.log(chalk5.dim(" Log stream ended."));
3559
3622
  console.log();
3560
3623
  } catch (err) {
3561
3624
  if (err instanceof Error && err.name === "AbortError") {
3562
3625
  return;
3563
3626
  }
3564
3627
  console.error(
3565
- chalk4.red(`Log stream error: ${err instanceof Error ? err.message : String(err)}`)
3628
+ chalk5.red(`Log stream error: ${err instanceof Error ? err.message : String(err)}`)
3566
3629
  );
3567
3630
  process.exit(1);
3568
3631
  }
@@ -3579,7 +3642,7 @@ function printLogEvent(type, data) {
3579
3642
  if (ts) {
3580
3643
  try {
3581
3644
  const d = new Date(ts);
3582
- timestamp = chalk4.dim(
3645
+ timestamp = chalk5.dim(
3583
3646
  d.toLocaleTimeString("en-US", { hour12: false }) + " "
3584
3647
  );
3585
3648
  } catch {
@@ -3593,40 +3656,40 @@ function printLogEvent(type, data) {
3593
3656
  function getLogPrefix(level) {
3594
3657
  switch (level.toLowerCase()) {
3595
3658
  case "error":
3596
- return chalk4.red("ERROR");
3659
+ return chalk5.red("ERROR");
3597
3660
  case "warning":
3598
3661
  case "warn":
3599
- return chalk4.yellow("WARN ");
3662
+ return chalk5.yellow("WARN ");
3600
3663
  case "info":
3601
3664
  case "message":
3602
- return chalk4.blue("INFO ");
3665
+ return chalk5.blue("INFO ");
3603
3666
  case "debug":
3604
- return chalk4.dim("DEBUG");
3667
+ return chalk5.dim("DEBUG");
3605
3668
  case "step":
3606
- return chalk4.magenta("STEP ");
3669
+ return chalk5.magenta("STEP ");
3607
3670
  case "success":
3608
3671
  case "done":
3609
3672
  case "complete":
3610
- return chalk4.green("OK ");
3673
+ return chalk5.green("OK ");
3611
3674
  default:
3612
- return chalk4.dim("LOG ");
3675
+ return chalk5.dim("LOG ");
3613
3676
  }
3614
3677
  }
3615
3678
  function handleError4(err) {
3616
3679
  if (err instanceof ApiError) {
3617
3680
  if (err.status === 401) {
3618
- console.error(chalk4.red("Not authenticated. Run `doable login` first."));
3681
+ console.error(chalk5.red("Not authenticated. Run `doable login` first."));
3619
3682
  } else if (err.status === 0) {
3620
- console.error(chalk4.red(`Connection error: ${err.message}`));
3683
+ console.error(chalk5.red(`Connection error: ${err.message}`));
3621
3684
  } else if (err.status === 404) {
3622
- console.error(chalk4.red(`Deployment not found. Check the deployment ID and try again.`));
3685
+ console.error(chalk5.red(`Deployment not found. Check the deployment ID and try again.`));
3623
3686
  } else {
3624
- console.error(chalk4.red(`Error: ${err.message}`));
3687
+ console.error(chalk5.red(`Error: ${err.message}`));
3625
3688
  }
3626
3689
  } else if (err instanceof Error) {
3627
- console.error(chalk4.red(`Error: ${err.message}`));
3690
+ console.error(chalk5.red(`Error: ${err.message}`));
3628
3691
  } else {
3629
- console.error(chalk4.red(`Unexpected error: ${String(err)}`));
3692
+ console.error(chalk5.red(`Unexpected error: ${String(err)}`));
3630
3693
  }
3631
3694
  process.exit(1);
3632
3695
  }
@@ -3651,14 +3714,14 @@ function writeMcpJson(filePath, label) {
3651
3714
  }
3652
3715
  const servers = existing.mcpServers ?? {};
3653
3716
  if (servers.doable) {
3654
- console.log(chalk4.dim(` ${label}: already configured`));
3717
+ console.log(chalk5.dim(` ${label}: already configured`));
3655
3718
  return "already_configured";
3656
3719
  }
3657
3720
  servers.doable = MCP_CONFIG.doable;
3658
3721
  existing.mcpServers = servers;
3659
3722
  ensureDir(path7.dirname(filePath));
3660
3723
  fs6.writeFileSync(filePath, JSON.stringify(existing, null, 2) + "\n");
3661
- console.log(chalk4.green(` ${label}: configured`));
3724
+ console.log(chalk5.green(` ${label}: configured`));
3662
3725
  return "created";
3663
3726
  }
3664
3727
  function detectEditor() {
@@ -3680,9 +3743,9 @@ async function runSetup(opts) {
3680
3743
  const doAll = !opts.claude && !opts.cursor && !opts.codex && !opts.project;
3681
3744
  const detected = detectEditor();
3682
3745
  console.log();
3683
- console.log(chalk4.bold(" Doable MCP Setup"));
3746
+ console.log(chalk5.bold(" Doable MCP Setup"));
3684
3747
  if (detected !== "unknown") {
3685
- console.log(chalk4.dim(` Detected editor: ${detected}`));
3748
+ console.log(chalk5.dim(` Detected editor: ${detected}`));
3686
3749
  }
3687
3750
  console.log();
3688
3751
  let changedSomething = false;
@@ -3700,50 +3763,50 @@ async function runSetup(opts) {
3700
3763
  const homeSettings = path7.join(os.homedir(), ".claude", "settings.json");
3701
3764
  if (!fs6.existsSync(homeSettings)) {
3702
3765
  console.log(
3703
- chalk4.dim(
3766
+ chalk5.dim(
3704
3767
  " Tip: Claude Code uses per-project .mcp.json \u2014 no global config needed."
3705
3768
  )
3706
3769
  );
3707
3770
  }
3708
3771
  }
3709
3772
  console.log();
3710
- console.log(chalk4.bold(" Next:"));
3773
+ console.log(chalk5.bold(" Next:"));
3711
3774
  const loggedIn = Boolean(getToken());
3712
3775
  if (!loggedIn) {
3713
3776
  console.log(
3714
- ` 1. ${chalk4.cyan("doable login")} \u2014 opens your browser for a one-time sign-in`
3777
+ ` 1. ${chalk5.cyan("doable login")} \u2014 opens your browser for a one-time sign-in`
3715
3778
  );
3716
3779
  console.log(` 2. Restart your editor so the MCP loads`);
3717
3780
  console.log(
3718
- ` 3. In your editor, say ${chalk4.cyan('"deploy this"')} or type ${chalk4.cyan("/deploy")}`
3781
+ ` 3. In your editor, say ${chalk5.cyan('"deploy this"')} or type ${chalk5.cyan("/deploy")}`
3719
3782
  );
3720
3783
  } else {
3721
3784
  console.log(` 1. Restart your editor so the MCP loads`);
3722
3785
  console.log(
3723
- ` 2. In your editor, say ${chalk4.cyan('"deploy this"')} or type ${chalk4.cyan("/deploy")}`
3786
+ ` 2. In your editor, say ${chalk5.cyan('"deploy this"')} or type ${chalk5.cyan("/deploy")}`
3724
3787
  );
3725
3788
  console.log(
3726
- chalk4.dim(` (you're already logged in \u2014 skip straight to deploying)`)
3789
+ chalk5.dim(` (you're already logged in \u2014 skip straight to deploying)`)
3727
3790
  );
3728
3791
  }
3729
3792
  console.log();
3730
3793
  if (detected === "claude-code") {
3731
3794
  console.log(
3732
- chalk4.dim(
3733
- ` Verify it loaded: run ${chalk4.cyan("/mcp")} in Claude Code after restart.`
3795
+ chalk5.dim(
3796
+ ` Verify it loaded: run ${chalk5.cyan("/mcp")} in Claude Code after restart.`
3734
3797
  )
3735
3798
  );
3736
3799
  console.log();
3737
3800
  } else if (detected === "cursor") {
3738
3801
  console.log(
3739
- chalk4.dim(
3802
+ chalk5.dim(
3740
3803
  ` Verify it loaded: Cursor \u2192 Settings \u2192 MCP \u2192 Doable should show up.`
3741
3804
  )
3742
3805
  );
3743
3806
  console.log();
3744
3807
  }
3745
3808
  if (!changedSomething) {
3746
- console.log(chalk4.dim(` Config already in place \u2014 no changes needed.`));
3809
+ console.log(chalk5.dim(` Config already in place \u2014 no changes needed.`));
3747
3810
  console.log();
3748
3811
  }
3749
3812
  }
@@ -3752,7 +3815,7 @@ function registerSetupCommand(program2) {
3752
3815
  try {
3753
3816
  await runSetup(opts);
3754
3817
  } catch (err) {
3755
- console.error(chalk4.red(err instanceof Error ? err.message : "Setup failed"));
3818
+ console.error(chalk5.red(err instanceof Error ? err.message : "Setup failed"));
3756
3819
  process.exit(1);
3757
3820
  }
3758
3821
  });
@@ -3998,13 +4061,13 @@ async function runAttach(opts) {
3998
4061
  const providers = PROVIDERS[type];
3999
4062
  if (providers.length > 0) {
4000
4063
  console.log();
4001
- console.log(chalk4.bold(` Recommended ${LABELS[type]} providers:`));
4064
+ console.log(chalk5.bold(` Recommended ${LABELS[type]} providers:`));
4002
4065
  console.log();
4003
4066
  for (const p of providers) {
4004
4067
  console.log(
4005
- ` ${chalk4.cyan(p.name.padEnd(10))} ${chalk4.dim(p.tagline)}`
4068
+ ` ${chalk5.cyan(p.name.padEnd(10))} ${chalk5.dim(p.tagline)}`
4006
4069
  );
4007
- console.log(` ${" ".repeat(10)} ${chalk4.underline.dim(p.signupUrl)}`);
4070
+ console.log(` ${" ".repeat(10)} ${chalk5.underline.dim(p.signupUrl)}`);
4008
4071
  console.log();
4009
4072
  }
4010
4073
  const { pick: pick2 } = await inquirer3.prompt([
@@ -4027,12 +4090,12 @@ async function runAttach(opts) {
4027
4090
  try {
4028
4091
  const { default: openBrowser2 } = await import('open');
4029
4092
  await openBrowser2(chosen.signupUrl);
4030
- console.log(chalk4.dim(` Opened ${chosen.signupUrl} in your browser.`));
4093
+ console.log(chalk5.dim(` Opened ${chosen.signupUrl} in your browser.`));
4031
4094
  } catch {
4032
- console.log(chalk4.dim(` Open manually: ${chosen.signupUrl}`));
4095
+ console.log(chalk5.dim(` Open manually: ${chosen.signupUrl}`));
4033
4096
  }
4034
4097
  console.log(
4035
- chalk4.dim(" Create your database there, copy the connection string, then paste it below.")
4098
+ chalk5.dim(" Create your database there, copy the connection string, then paste it below.")
4036
4099
  );
4037
4100
  console.log();
4038
4101
  }
@@ -4057,7 +4120,7 @@ async function runAttach(opts) {
4057
4120
  const expectedScheme = type === "postgres" ? "postgres" : type === "redis" ? "redis" : type === "mongodb" ? "mongodb" : null;
4058
4121
  if (expectedScheme && !url.toLowerCase().startsWith(expectedScheme)) {
4059
4122
  console.log(
4060
- chalk4.yellow(
4123
+ chalk5.yellow(
4061
4124
  ` Warning: URL doesn't start with '${expectedScheme}://'. Saving anyway \u2014 double-check it's correct.`
4062
4125
  )
4063
4126
  );
@@ -4099,12 +4162,12 @@ async function runAttach(opts) {
4099
4162
  }
4100
4163
  ]);
4101
4164
  if (!confirm) {
4102
- console.log(chalk4.dim(" Cancelled."));
4165
+ console.log(chalk5.dim(" Cancelled."));
4103
4166
  return;
4104
4167
  }
4105
4168
  }
4106
4169
  }
4107
- const spinner = ora9(`Attaching ${envKey}...`).start();
4170
+ const spinner = ora10(`Attaching ${envKey}...`).start();
4108
4171
  try {
4109
4172
  await putEnvVars(projectId, { [envKey]: url });
4110
4173
  } catch (err) {
@@ -4113,8 +4176,8 @@ async function runAttach(opts) {
4113
4176
  }
4114
4177
  spinner.succeed(`${envKey} attached to project.`);
4115
4178
  console.log();
4116
- console.log(chalk4.dim(" Redeploy for the change to take effect:"));
4117
- console.log(` ${chalk4.cyan("doable deploy")}`);
4179
+ console.log(chalk5.dim(" Redeploy for the change to take effect:"));
4180
+ console.log(` ${chalk5.cyan("doable deploy")}`);
4118
4181
  console.log();
4119
4182
  }
4120
4183
  async function runAttachNative(opts) {
@@ -4137,16 +4200,16 @@ async function runAttachNative(opts) {
4137
4200
  const engineLabel = type === "postgres" ? "Postgres database" : type === "redis" ? "Redis cache" : "MongoDB database";
4138
4201
  console.log();
4139
4202
  console.log(
4140
- chalk4.bold(` Provisioning a Doable-managed ${engineLabel}`)
4203
+ chalk5.bold(` Provisioning a Doable-managed ${engineLabel}`)
4141
4204
  );
4142
4205
  console.log(
4143
- chalk4.dim(
4206
+ chalk5.dim(
4144
4207
  ` Version ${version}. Doable will create a container on your project's network
4145
4208
  and inject ${envKey} automatically \u2014 no connection string to paste.`
4146
4209
  )
4147
4210
  );
4148
4211
  console.log();
4149
- const creating = ora9("Requesting addon...").start();
4212
+ const creating = ora10("Requesting addon...").start();
4150
4213
  let addon;
4151
4214
  try {
4152
4215
  addon = await createAddon(projectId, {
@@ -4163,7 +4226,7 @@ async function runAttachNative(opts) {
4163
4226
  throw err;
4164
4227
  }
4165
4228
  creating.succeed(`Addon ${addon.id.slice(0, 8)} created.`);
4166
- const spinner = ora9("Waiting for database to start...").start();
4229
+ const spinner = ora10("Waiting for database to start...").start();
4167
4230
  const deadline = Date.now() + 5 * 60 * 1e3;
4168
4231
  const pollIntervalMs = 2e3;
4169
4232
  let current = addon;
@@ -4185,11 +4248,11 @@ async function runAttachNative(opts) {
4185
4248
  spinner.fail("Database provisioning failed.");
4186
4249
  if (current.statusMessage) {
4187
4250
  console.log();
4188
- console.log(chalk4.red(` ${current.statusMessage}`));
4251
+ console.log(chalk5.red(` ${current.statusMessage}`));
4189
4252
  }
4190
4253
  console.log();
4191
4254
  console.log(
4192
- chalk4.dim(
4255
+ chalk5.dim(
4193
4256
  " You can retry with `doable addon attach --native`. If this keeps happening\n contact support with the addon ID above."
4194
4257
  )
4195
4258
  );
@@ -4202,7 +4265,7 @@ async function runAttachNative(opts) {
4202
4265
  spinner.fail("Timed out waiting for database to start.");
4203
4266
  console.log();
4204
4267
  console.log(
4205
- chalk4.dim(
4268
+ chalk5.dim(
4206
4269
  ` The addon is still in status '${current.status}'. It may finish shortly \u2014
4207
4270
  run \`doable addon list\` to check again.`
4208
4271
  )
@@ -4212,23 +4275,23 @@ async function runAttachNative(opts) {
4212
4275
  }
4213
4276
  const readyLabel = type === "postgres" ? "Database ready." : type === "redis" ? "Cache ready." : "MongoDB ready.";
4214
4277
  console.log();
4215
- console.log(chalk4.green(` \u2714 ${readyLabel}`));
4278
+ console.log(chalk5.green(` \u2714 ${readyLabel}`));
4216
4279
  console.log();
4217
- console.log(` ${chalk4.dim("Engine:")} ${type} ${current.version}`);
4280
+ console.log(` ${chalk5.dim("Engine:")} ${type} ${current.version}`);
4218
4281
  console.log(
4219
- ` ${chalk4.dim("Host:")} ${current.containerName}:${current.port}`
4282
+ ` ${chalk5.dim("Host:")} ${current.containerName}:${current.port}`
4220
4283
  );
4221
4284
  if (type === "postgres") {
4222
- console.log(` ${chalk4.dim("Database:")} ${current.database}`);
4285
+ console.log(` ${chalk5.dim("Database:")} ${current.database}`);
4223
4286
  }
4224
- console.log(` ${chalk4.dim("Env var:")} ${envKey}`);
4287
+ console.log(` ${chalk5.dim("Env var:")} ${envKey}`);
4225
4288
  console.log();
4226
4289
  console.log(
4227
- chalk4.dim(
4290
+ chalk5.dim(
4228
4291
  ` ${envKey} has been set on your project. Redeploy to inject it:`
4229
4292
  )
4230
4293
  );
4231
- console.log(` ${chalk4.cyan("doable deploy")}`);
4294
+ console.log(` ${chalk5.cyan("doable deploy")}`);
4232
4295
  console.log();
4233
4296
  }
4234
4297
  function formatBytes(bytes) {
@@ -4252,14 +4315,14 @@ function timeAgo(iso) {
4252
4315
  }
4253
4316
  async function runBackupCreate(addonId) {
4254
4317
  console.log();
4255
- console.log(chalk4.bold(` Backing up addon ${addonId.slice(0, 8)}`));
4318
+ console.log(chalk5.bold(` Backing up addon ${addonId.slice(0, 8)}`));
4256
4319
  console.log(
4257
- chalk4.dim(
4320
+ chalk5.dim(
4258
4321
  " pg_dump runs in a transient container on the addon's network,\n streams gzipped output to object storage, and reports size here."
4259
4322
  )
4260
4323
  );
4261
4324
  console.log();
4262
- const creating = ora9("Queuing backup...").start();
4325
+ const creating = ora10("Queuing backup...").start();
4263
4326
  let backup;
4264
4327
  try {
4265
4328
  backup = await createAddonBackup(addonId);
@@ -4268,7 +4331,7 @@ async function runBackupCreate(addonId) {
4268
4331
  throw err;
4269
4332
  }
4270
4333
  creating.succeed(`Backup ${backup.id.slice(0, 8)} queued.`);
4271
- const spinner = ora9("Waiting for agent to finish pg_dump...").start();
4334
+ const spinner = ora10("Waiting for agent to finish pg_dump...").start();
4272
4335
  const deadline = Date.now() + 15 * 60 * 1e3;
4273
4336
  const pollIntervalMs = 3e3;
4274
4337
  let current = backup;
@@ -4289,11 +4352,11 @@ async function runBackupCreate(addonId) {
4289
4352
  spinner.fail("Backup failed.");
4290
4353
  if (current.statusMessage) {
4291
4354
  console.log();
4292
- console.log(chalk4.red(` ${current.statusMessage}`));
4355
+ console.log(chalk5.red(` ${current.statusMessage}`));
4293
4356
  }
4294
4357
  console.log();
4295
4358
  console.log(
4296
- chalk4.dim(
4359
+ chalk5.dim(
4297
4360
  " You can retry with `doable addon backup <addonId>`. If this keeps\n happening, check the agent logs on the addon's server."
4298
4361
  )
4299
4362
  );
@@ -4306,7 +4369,7 @@ async function runBackupCreate(addonId) {
4306
4369
  spinner.fail("Timed out waiting for backup to complete.");
4307
4370
  console.log();
4308
4371
  console.log(
4309
- chalk4.dim(
4372
+ chalk5.dim(
4310
4373
  ` Backup is still in status '${current.status}'. It may still finish \u2014
4311
4374
  run \`doable addon backups ${addonId}\` to check again.`
4312
4375
  )
@@ -4315,20 +4378,20 @@ async function runBackupCreate(addonId) {
4315
4378
  process.exit(1);
4316
4379
  }
4317
4380
  console.log();
4318
- console.log(chalk4.green(" \u2714 Backup saved."));
4381
+ console.log(chalk5.green(" \u2714 Backup saved."));
4319
4382
  console.log();
4320
- console.log(` ${chalk4.dim("ID:")} ${current.id}`);
4321
- console.log(` ${chalk4.dim("Size:")} ${formatBytes(current.sizeBytes)}`);
4322
- console.log(` ${chalk4.dim("Key:")} ${current.s3Key}`);
4383
+ console.log(` ${chalk5.dim("ID:")} ${current.id}`);
4384
+ console.log(` ${chalk5.dim("Size:")} ${formatBytes(current.sizeBytes)}`);
4385
+ console.log(` ${chalk5.dim("Key:")} ${current.s3Key}`);
4323
4386
  console.log();
4324
- console.log(chalk4.dim(" Restore anytime with:"));
4387
+ console.log(chalk5.dim(" Restore anytime with:"));
4325
4388
  console.log(
4326
- ` ${chalk4.cyan(`doable addon restore ${addonId} ${current.id}`)}`
4389
+ ` ${chalk5.cyan(`doable addon restore ${addonId} ${current.id}`)}`
4327
4390
  );
4328
4391
  console.log();
4329
4392
  }
4330
4393
  async function runBackupList(addonId) {
4331
- const spinner = ora9("Loading backups...").start();
4394
+ const spinner = ora10("Loading backups...").start();
4332
4395
  let backups;
4333
4396
  try {
4334
4397
  backups = await listAddonBackups(addonId);
@@ -4338,12 +4401,12 @@ async function runBackupList(addonId) {
4338
4401
  }
4339
4402
  spinner.stop();
4340
4403
  console.log();
4341
- console.log(chalk4.bold(` Backups for addon ${addonId.slice(0, 8)}`));
4404
+ console.log(chalk5.bold(` Backups for addon ${addonId.slice(0, 8)}`));
4342
4405
  console.log();
4343
4406
  if (backups.length === 0) {
4344
- console.log(chalk4.dim(" No backups yet."));
4407
+ console.log(chalk5.dim(" No backups yet."));
4345
4408
  console.log(
4346
- chalk4.dim(" Take one with: ") + chalk4.cyan(`doable addon backup ${addonId}`)
4409
+ chalk5.dim(" Take one with: ") + chalk5.cyan(`doable addon backup ${addonId}`)
4347
4410
  );
4348
4411
  console.log();
4349
4412
  return;
@@ -4354,11 +4417,11 @@ async function runBackupList(addonId) {
4354
4417
  const colAge = "AGE".padEnd(12);
4355
4418
  const colType = "TYPE";
4356
4419
  console.log(
4357
- " " + chalk4.dim(`${colId} ${colStatus} ${colSize} ${colAge} ${colType}`)
4420
+ " " + chalk5.dim(`${colId} ${colStatus} ${colSize} ${colAge} ${colType}`)
4358
4421
  );
4359
4422
  for (const b of backups) {
4360
4423
  const idCell = b.id.slice(0, 8).padEnd(12);
4361
- const statusColor = b.status === "completed" ? chalk4.green : b.status === "failed" ? chalk4.red : chalk4.yellow;
4424
+ const statusColor = b.status === "completed" ? chalk5.green : b.status === "failed" ? chalk5.red : chalk5.yellow;
4362
4425
  const statusCell = statusColor(b.status.padEnd(12));
4363
4426
  const sizeCell = formatBytes(b.sizeBytes).padEnd(10);
4364
4427
  const ageCell = timeAgo(b.createdAt).padEnd(12);
@@ -4383,11 +4446,11 @@ async function runRestore(addonId, backupId, opts) {
4383
4446
  }
4384
4447
  ]);
4385
4448
  if (!confirm) {
4386
- console.log(chalk4.dim(" Cancelled."));
4449
+ console.log(chalk5.dim(" Cancelled."));
4387
4450
  return;
4388
4451
  }
4389
4452
  }
4390
- const kicking = ora9("Starting restore...").start();
4453
+ const kicking = ora10("Starting restore...").start();
4391
4454
  try {
4392
4455
  await restoreAddonFromBackup(addonId, backupId);
4393
4456
  } catch (err) {
@@ -4395,7 +4458,7 @@ async function runRestore(addonId, backupId, opts) {
4395
4458
  throw err;
4396
4459
  }
4397
4460
  kicking.succeed("Restore started.");
4398
- const spinner = ora9("Waiting for psql restore to finish...").start();
4461
+ const spinner = ora10("Waiting for psql restore to finish...").start();
4399
4462
  const deadline = Date.now() + 15 * 60 * 1e3;
4400
4463
  const pollIntervalMs = 3e3;
4401
4464
  let current;
@@ -4412,7 +4475,7 @@ async function runRestore(addonId, backupId, opts) {
4412
4475
  spinner.succeed("Restore completed \u2014 addon is running.");
4413
4476
  console.log();
4414
4477
  console.log(
4415
- chalk4.dim(
4478
+ chalk5.dim(
4416
4479
  " The addon's DATABASE_URL is unchanged \u2014 only the data inside has\n been replaced. No redeploy needed."
4417
4480
  )
4418
4481
  );
@@ -4423,7 +4486,7 @@ async function runRestore(addonId, backupId, opts) {
4423
4486
  spinner.fail("Restore failed.");
4424
4487
  if (current.statusMessage) {
4425
4488
  console.log();
4426
- console.log(chalk4.red(` ${current.statusMessage}`));
4489
+ console.log(chalk5.red(` ${current.statusMessage}`));
4427
4490
  }
4428
4491
  console.log();
4429
4492
  process.exit(1);
@@ -4445,11 +4508,11 @@ async function runBackupDelete(backupId, opts) {
4445
4508
  }
4446
4509
  ]);
4447
4510
  if (!confirm) {
4448
- console.log(chalk4.dim(" Cancelled."));
4511
+ console.log(chalk5.dim(" Cancelled."));
4449
4512
  return;
4450
4513
  }
4451
4514
  }
4452
- const spinner = ora9("Deleting backup...").start();
4515
+ const spinner = ora10("Deleting backup...").start();
4453
4516
  try {
4454
4517
  await deleteAddonBackup(backupId);
4455
4518
  } catch (err) {
@@ -4472,7 +4535,7 @@ async function runMigrate(opts) {
4472
4535
  const loadTool = engine === "mongodb" ? "mongorestore" : "psql";
4473
4536
  let envKey = opts.fromEnv?.trim().toUpperCase();
4474
4537
  if (!envKey) {
4475
- const spinner2 = ora9("Loading project env vars...").start();
4538
+ const spinner2 = ora10("Loading project env vars...").start();
4476
4539
  let existing = [];
4477
4540
  try {
4478
4541
  existing = await listEnvVars(projectId);
@@ -4496,7 +4559,7 @@ async function runMigrate(opts) {
4496
4559
  if (candidates.length === 1) {
4497
4560
  envKey = candidates[0].key.toUpperCase();
4498
4561
  console.log(
4499
- chalk4.dim(` Using ${chalk4.cyan(envKey)} (the only ${engine}-ish env var on the project).`)
4562
+ chalk5.dim(` Using ${chalk5.cyan(envKey)} (the only ${engine}-ish env var on the project).`)
4500
4563
  );
4501
4564
  } else if (nonInteractive) {
4502
4565
  throw new Error(
@@ -4523,10 +4586,10 @@ async function runMigrate(opts) {
4523
4586
  const version = opts.version?.trim() || defaultVersion;
4524
4587
  console.log();
4525
4588
  console.log(
4526
- chalk4.bold(` Migrate external ${engineLabel} \u2192 Doable-managed ${engineLabel}`)
4589
+ chalk5.bold(` Migrate external ${engineLabel} \u2192 Doable-managed ${engineLabel}`)
4527
4590
  );
4528
4591
  console.log(
4529
- chalk4.dim(
4592
+ chalk5.dim(
4530
4593
  ` 1. Provision a new ${engineLabel} container on your project's network
4531
4594
  2. ${dumpTool} from the source URL in ${envKey}
4532
4595
  3. Load the dump into the new container via ${loadTool}
@@ -4549,11 +4612,11 @@ async function runMigrate(opts) {
4549
4612
  }
4550
4613
  ]);
4551
4614
  if (!confirm) {
4552
- console.log(chalk4.dim(" Cancelled."));
4615
+ console.log(chalk5.dim(" Cancelled."));
4553
4616
  return;
4554
4617
  }
4555
4618
  }
4556
- const kicking = ora9("Requesting migrate...").start();
4619
+ const kicking = ora10("Requesting migrate...").start();
4557
4620
  let addon;
4558
4621
  try {
4559
4622
  addon = await migrateAddonFromEnv(projectId, { envKey, engine, version });
@@ -4562,7 +4625,7 @@ async function runMigrate(opts) {
4562
4625
  throw err;
4563
4626
  }
4564
4627
  kicking.succeed(`Migrate queued (addon ${addon.id.slice(0, 8)}).`);
4565
- const spinner = ora9(
4628
+ const spinner = ora10(
4566
4629
  `Provisioning + copying data... (pull image \u2192 run container \u2192 ${dumpTool} \u2192 ${loadTool})`
4567
4630
  ).start();
4568
4631
  const deadline = Date.now() + 15 * 60 * 1e3;
@@ -4580,15 +4643,15 @@ async function runMigrate(opts) {
4580
4643
  if (current.status === "running") {
4581
4644
  spinner.succeed("Migrate complete.");
4582
4645
  console.log();
4583
- console.log(chalk4.green(` \u2714 ${envKey} now points at the Doable-managed ${engineLabel}.`));
4646
+ console.log(chalk5.green(` \u2714 ${envKey} now points at the Doable-managed ${engineLabel}.`));
4584
4647
  console.log();
4585
- console.log(` ${chalk4.dim("Engine:")} ${current.engine} ${current.version}`);
4586
- console.log(` ${chalk4.dim("Host:")} ${current.containerName}:${current.port}`);
4587
- console.log(` ${chalk4.dim("Database:")} ${current.database}`);
4588
- console.log(` ${chalk4.dim("Env var:")} ${envKey}`);
4648
+ console.log(` ${chalk5.dim("Engine:")} ${current.engine} ${current.version}`);
4649
+ console.log(` ${chalk5.dim("Host:")} ${current.containerName}:${current.port}`);
4650
+ console.log(` ${chalk5.dim("Database:")} ${current.database}`);
4651
+ console.log(` ${chalk5.dim("Env var:")} ${envKey}`);
4589
4652
  console.log();
4590
- console.log(chalk4.dim(" Redeploy to pick up the new env var:"));
4591
- console.log(` ${chalk4.cyan("doable deploy")}`);
4653
+ console.log(chalk5.dim(" Redeploy to pick up the new env var:"));
4654
+ console.log(` ${chalk5.cyan("doable deploy")}`);
4592
4655
  console.log();
4593
4656
  return;
4594
4657
  }
@@ -4596,11 +4659,11 @@ async function runMigrate(opts) {
4596
4659
  spinner.fail("Migrate failed.");
4597
4660
  if (current.statusMessage) {
4598
4661
  console.log();
4599
- console.log(chalk4.red(` ${current.statusMessage}`));
4662
+ console.log(chalk5.red(` ${current.statusMessage}`));
4600
4663
  }
4601
4664
  console.log();
4602
4665
  console.log(
4603
- chalk4.dim(
4666
+ chalk5.dim(
4604
4667
  " The source database is unchanged. If the native addon was partially\n created, delete it from the dashboard + retry. If the source URL is\n wrong, fix it and run `doable addon migrate` again."
4605
4668
  )
4606
4669
  );
@@ -4612,7 +4675,7 @@ async function runMigrate(opts) {
4612
4675
  spinner.fail("Timed out waiting for migrate to complete.");
4613
4676
  console.log();
4614
4677
  console.log(
4615
- chalk4.dim(
4678
+ chalk5.dim(
4616
4679
  ` Addon is still in status '${current.status}'. It may still finish \u2014
4617
4680
  run \`doable addon list\` to check again.`
4618
4681
  )
@@ -4629,7 +4692,7 @@ function classifyKey(key) {
4629
4692
  }
4630
4693
  async function runList(opts) {
4631
4694
  const projectId = await resolveProject(opts.project, !process.stdin.isTTY);
4632
- const spinner = ora9("Fetching addons...").start();
4695
+ const spinner = ora10("Fetching addons...").start();
4633
4696
  const vars = await listEnvVars(projectId);
4634
4697
  spinner.stop();
4635
4698
  const addons = [];
@@ -4638,22 +4701,22 @@ async function runList(opts) {
4638
4701
  if (t) addons.push({ key: v.key, type: t, updatedAt: v.updatedAt });
4639
4702
  }
4640
4703
  console.log();
4641
- console.log(chalk4.bold(" Attached addons"));
4704
+ console.log(chalk5.bold(" Attached addons"));
4642
4705
  console.log();
4643
4706
  if (addons.length === 0) {
4644
- console.log(chalk4.dim(" No addons attached yet."));
4645
- console.log(chalk4.dim(" Attach one with: ") + chalk4.cyan("doable addon attach"));
4707
+ console.log(chalk5.dim(" No addons attached yet."));
4708
+ console.log(chalk5.dim(" Attach one with: ") + chalk5.cyan("doable addon attach"));
4646
4709
  console.log();
4647
4710
  return;
4648
4711
  }
4649
4712
  for (const a of addons) {
4650
4713
  console.log(
4651
- ` ${chalk4.cyan(a.key.padEnd(20))} ${chalk4.dim(LABELS[a.type])}`
4714
+ ` ${chalk5.cyan(a.key.padEnd(20))} ${chalk5.dim(LABELS[a.type])}`
4652
4715
  );
4653
4716
  }
4654
4717
  console.log();
4655
4718
  console.log(
4656
- chalk4.dim(
4719
+ chalk5.dim(
4657
4720
  " Values are encrypted and not shown. Update by running `doable addon attach` again."
4658
4721
  )
4659
4722
  );
@@ -4675,11 +4738,11 @@ async function runDetach(key, opts) {
4675
4738
  }
4676
4739
  ]);
4677
4740
  if (!confirm) {
4678
- console.log(chalk4.dim(" Cancelled."));
4741
+ console.log(chalk5.dim(" Cancelled."));
4679
4742
  return;
4680
4743
  }
4681
4744
  }
4682
- const spinner = ora9(`Removing ${normalized}...`).start();
4745
+ const spinner = ora10(`Removing ${normalized}...`).start();
4683
4746
  try {
4684
4747
  await deleteEnvVar(projectId, normalized);
4685
4748
  } catch (err) {
@@ -4688,7 +4751,7 @@ async function runDetach(key, opts) {
4688
4751
  }
4689
4752
  spinner.succeed(`${normalized} removed.`);
4690
4753
  console.log();
4691
- console.log(chalk4.dim(" Redeploy for the change to take effect."));
4754
+ console.log(chalk5.dim(" Redeploy for the change to take effect."));
4692
4755
  console.log();
4693
4756
  }
4694
4757
  async function resolveProject(explicit, nonInteractive) {
@@ -4757,18 +4820,18 @@ async function runConnect2(addonId, opts) {
4757
4820
  }
4758
4821
  })();
4759
4822
  console.log();
4760
- console.log(chalk4.bold(` ${conn.engine} connection string`));
4823
+ console.log(chalk5.bold(` ${conn.engine} connection string`));
4761
4824
  console.log();
4762
- console.log(` ${chalk4.dim("URL:")} ${conn.url}`);
4763
- console.log(` ${chalk4.dim("Host:")} ${conn.host}:${conn.port}`);
4764
- console.log(` ${chalk4.dim("Database:")} ${conn.database}`);
4765
- console.log(` ${chalk4.dim("Username:")} ${conn.username}`);
4825
+ console.log(` ${chalk5.dim("URL:")} ${conn.url}`);
4826
+ console.log(` ${chalk5.dim("Host:")} ${conn.host}:${conn.port}`);
4827
+ console.log(` ${chalk5.dim("Database:")} ${conn.database}`);
4828
+ console.log(` ${chalk5.dim("Username:")} ${conn.username}`);
4766
4829
  console.log();
4767
- console.log(chalk4.dim(" This URL is only reachable from sibling containers on the same"));
4768
- console.log(chalk4.dim(" Doable account network. External clients can't connect by name."));
4830
+ console.log(chalk5.dim(" This URL is only reachable from sibling containers on the same"));
4831
+ console.log(chalk5.dim(" Doable account network. External clients can't connect by name."));
4769
4832
  console.log();
4770
- console.log(chalk4.dim(" To set it as an env var on your project:"));
4771
- console.log(` ${chalk4.cyan(`doable env set ${envVarHint}="<paste URL>"`)}`);
4833
+ console.log(chalk5.dim(" To set it as an env var on your project:"));
4834
+ console.log(` ${chalk5.cyan(`doable env set ${envVarHint}="<paste URL>"`)}`);
4772
4835
  console.log();
4773
4836
  }
4774
4837
  async function runTables(addonId) {
@@ -4785,8 +4848,8 @@ async function runTables(addonId) {
4785
4848
  }
4786
4849
  if (resp.tables.length === 0) {
4787
4850
  console.log();
4788
- console.log(chalk4.dim(" No user tables yet."));
4789
- console.log(chalk4.dim(" Create some by running your app's migrations against this addon."));
4851
+ console.log(chalk5.dim(" No user tables yet."));
4852
+ console.log(chalk5.dim(" Create some by running your app's migrations against this addon."));
4790
4853
  console.log();
4791
4854
  return;
4792
4855
  }
@@ -4800,10 +4863,10 @@ async function runTables(addonId) {
4800
4863
  bySchema.set(schema, list);
4801
4864
  }
4802
4865
  console.log();
4803
- console.log(chalk4.bold(` Tables in addon (${resp.tables.length} total)`));
4866
+ console.log(chalk5.bold(` Tables in addon (${resp.tables.length} total)`));
4804
4867
  for (const [schema, tables] of Array.from(bySchema.entries()).sort()) {
4805
4868
  console.log();
4806
- console.log(chalk4.dim(` ${schema}`));
4869
+ console.log(chalk5.dim(` ${schema}`));
4807
4870
  for (const t of tables) {
4808
4871
  console.log(` ${t}`);
4809
4872
  }
@@ -4818,7 +4881,7 @@ async function runLogs2(addonId, opts) {
4818
4881
  process.stdout.write(resp.logs);
4819
4882
  if (!resp.logs.endsWith("\n")) process.stdout.write("\n");
4820
4883
  if (resp.truncated) {
4821
- console.log(chalk4.dim(` (truncated to ${tail} lines)`));
4884
+ console.log(chalk5.dim(` (truncated to ${tail} lines)`));
4822
4885
  }
4823
4886
  previous = resp.logs;
4824
4887
  } catch (err) {
@@ -4830,7 +4893,7 @@ async function runLogs2(addonId, opts) {
4830
4893
  throw err;
4831
4894
  }
4832
4895
  if (!opts.follow) return;
4833
- console.log(chalk4.dim(" (following. Ctrl-C to stop.)"));
4896
+ console.log(chalk5.dim(" (following. Ctrl-C to stop.)"));
4834
4897
  const interval = 3e3;
4835
4898
  while (true) {
4836
4899
  await new Promise((r) => setTimeout(r, interval));
@@ -4851,9 +4914,9 @@ async function runLogs2(addonId, opts) {
4851
4914
  previous = resp.logs;
4852
4915
  } catch (err) {
4853
4916
  if (err instanceof ApiError) {
4854
- console.error(chalk4.dim(` (fetch error: ${err.message}, retrying...)`));
4917
+ console.error(chalk5.dim(` (fetch error: ${err.message}, retrying...)`));
4855
4918
  } else if (err instanceof Error) {
4856
- console.error(chalk4.dim(` (${err.message}, retrying...)`));
4919
+ console.error(chalk5.dim(` (${err.message}, retrying...)`));
4857
4920
  }
4858
4921
  }
4859
4922
  }
@@ -4880,12 +4943,12 @@ async function runClone(srcAddonId, destAddonId, opts) {
4880
4943
  throw new Error("Source and destination must be different addons.");
4881
4944
  }
4882
4945
  console.log();
4883
- console.log(chalk4.bold(` Clone ${src.engine} addon`));
4946
+ console.log(chalk5.bold(` Clone ${src.engine} addon`));
4884
4947
  console.log(
4885
- chalk4.dim(` from: ${src.containerName} (${srcAddonId.slice(0, 8)})`)
4948
+ chalk5.dim(` from: ${src.containerName} (${srcAddonId.slice(0, 8)})`)
4886
4949
  );
4887
4950
  console.log(
4888
- chalk4.dim(` to: ${dest.containerName} (${destAddonId.slice(0, 8)})`)
4951
+ chalk5.dim(` to: ${dest.containerName} (${destAddonId.slice(0, 8)})`)
4889
4952
  );
4890
4953
  console.log();
4891
4954
  if (!opts.yes) {
@@ -4903,11 +4966,11 @@ async function runClone(srcAddonId, destAddonId, opts) {
4903
4966
  }
4904
4967
  ]);
4905
4968
  if (!confirm) {
4906
- console.log(chalk4.dim(" Cancelled."));
4969
+ console.log(chalk5.dim(" Cancelled."));
4907
4970
  return;
4908
4971
  }
4909
4972
  }
4910
- const kicking = ora9("Taking source snapshot...").start();
4973
+ const kicking = ora10("Taking source snapshot...").start();
4911
4974
  let backup;
4912
4975
  try {
4913
4976
  backup = await createAddonBackup(srcAddonId);
@@ -4917,7 +4980,7 @@ async function runClone(srcAddonId, destAddonId, opts) {
4917
4980
  }
4918
4981
  kicking.succeed(`Backup ${backup.id.slice(0, 8)} queued.`);
4919
4982
  const backupDeadline = Date.now() + 15 * 60 * 1e3;
4920
- const backupSpinner = ora9("Waiting for source backup to finish...").start();
4983
+ const backupSpinner = ora10("Waiting for source backup to finish...").start();
4921
4984
  let currentBackup = backup;
4922
4985
  while (Date.now() < backupDeadline) {
4923
4986
  await new Promise((r) => setTimeout(r, 3e3));
@@ -4944,7 +5007,7 @@ async function runClone(srcAddonId, destAddonId, opts) {
4944
5007
  backupSpinner.fail("Timed out waiting for backup.");
4945
5008
  throw new Error("Backup did not complete within 15 minutes.");
4946
5009
  }
4947
- const restoreKicking = ora9("Starting restore into destination...").start();
5010
+ const restoreKicking = ora10("Starting restore into destination...").start();
4948
5011
  try {
4949
5012
  await restoreAddonFromBackup(destAddonId, currentBackup.id);
4950
5013
  } catch (err) {
@@ -4953,7 +5016,7 @@ async function runClone(srcAddonId, destAddonId, opts) {
4953
5016
  }
4954
5017
  restoreKicking.succeed("Restore queued.");
4955
5018
  const restoreDeadline = Date.now() + 15 * 60 * 1e3;
4956
- const restoreSpinner = ora9("Waiting for restore to finish...").start();
5019
+ const restoreSpinner = ora10("Waiting for restore to finish...").start();
4957
5020
  let currentDest = dest;
4958
5021
  while (Date.now() < restoreDeadline) {
4959
5022
  await new Promise((r) => setTimeout(r, 3e3));
@@ -4965,7 +5028,7 @@ async function runClone(srcAddonId, destAddonId, opts) {
4965
5028
  if (currentDest.status === "running") {
4966
5029
  restoreSpinner.succeed("Clone completed.");
4967
5030
  console.log();
4968
- console.log(chalk4.green(` \u2714 Destination addon now mirrors the source.`));
5031
+ console.log(chalk5.green(` \u2714 Destination addon now mirrors the source.`));
4969
5032
  console.log();
4970
5033
  return;
4971
5034
  }
@@ -4983,16 +5046,16 @@ async function runClone(srcAddonId, destAddonId, opts) {
4983
5046
  function handleError5(err) {
4984
5047
  if (err instanceof ApiError) {
4985
5048
  if (err.status === 401) {
4986
- console.error(chalk4.red("Not authenticated. Run `doable login` first."));
5049
+ console.error(chalk5.red("Not authenticated. Run `doable login` first."));
4987
5050
  } else if (err.status === 0) {
4988
- console.error(chalk4.red(`Connection error: ${err.message}`));
5051
+ console.error(chalk5.red(`Connection error: ${err.message}`));
4989
5052
  } else {
4990
- console.error(chalk4.red(`Error: ${err.message}`));
5053
+ console.error(chalk5.red(`Error: ${err.message}`));
4991
5054
  }
4992
5055
  } else if (err instanceof Error) {
4993
- console.error(chalk4.red(`Error: ${err.message}`));
5056
+ console.error(chalk5.red(`Error: ${err.message}`));
4994
5057
  } else {
4995
- console.error(chalk4.red(`Unexpected error: ${String(err)}`));
5058
+ console.error(chalk5.red(`Unexpected error: ${String(err)}`));
4996
5059
  }
4997
5060
  process.exit(1);
4998
5061
  }
@@ -5035,30 +5098,30 @@ async function runList2(opts) {
5035
5098
  if (isNaN(limit) || limit < 1) {
5036
5099
  throw new Error("--limit must be a positive integer");
5037
5100
  }
5038
- const spinner = ora9("Fetching deployments...").start();
5101
+ const spinner = ora10("Fetching deployments...").start();
5039
5102
  const deployments = await listDeployments(projectId, { limit });
5040
5103
  spinner.stop();
5041
5104
  if (deployments.length === 0) {
5042
- console.log(chalk4.dim("\n No deployments yet.\n"));
5105
+ console.log(chalk5.dim("\n No deployments yet.\n"));
5043
5106
  return;
5044
5107
  }
5045
5108
  console.log();
5046
- console.log(chalk4.bold(" Deployment History"));
5109
+ console.log(chalk5.bold(" Deployment History"));
5047
5110
  console.log();
5048
5111
  const header = ` ${pad4("#", 5)} ${pad4("STATUS", 10)} ${pad4("GIT SHA", 9)} ${pad4("ARTIFACT", 10)} CREATED`;
5049
- console.log(chalk4.dim(header));
5050
- console.log(chalk4.dim(" " + "-".repeat(header.length - 2)));
5112
+ console.log(chalk5.dim(header));
5113
+ console.log(chalk5.dim(" " + "-".repeat(header.length - 2)));
5051
5114
  for (let i = 0; i < deployments.length; i++) {
5052
5115
  const d = deployments[i];
5053
5116
  const isCurrent = i === 0;
5054
5117
  const num = `#${d.number}`;
5055
5118
  const statusLabel2 = colorStatus(d.status);
5056
5119
  const gitShort = d.gitSha ? d.gitSha.slice(0, 7) : "-";
5057
- const hasArtifact = d.artifactImageRef ? "yes" : chalk4.dim("no");
5120
+ const hasArtifact = d.artifactImageRef ? "yes" : chalk5.dim("no");
5058
5121
  const created = new Date(d.createdAt).toLocaleString();
5059
- const currentMarker = isCurrent ? chalk4.green(" \u2190 live") : "";
5122
+ const currentMarker = isCurrent ? chalk5.green(" \u2190 live") : "";
5060
5123
  console.log(
5061
- ` ${pad4(num, 5)} ${padRaw(statusLabel2, 10)} ${pad4(gitShort, 9)} ${padRaw(hasArtifact, 10)} ${chalk4.dim(created)}${currentMarker}`
5124
+ ` ${pad4(num, 5)} ${padRaw(statusLabel2, 10)} ${pad4(gitShort, 9)} ${padRaw(hasArtifact, 10)} ${chalk5.dim(created)}${currentMarker}`
5062
5125
  );
5063
5126
  }
5064
5127
  const cancelable = deployments.filter((d) => CANCELABLE_STATUSES.includes(d.status));
@@ -5067,12 +5130,12 @@ async function runList2(opts) {
5067
5130
  console.log();
5068
5131
  if (cancelable.length > 0) {
5069
5132
  console.log(
5070
- chalk4.dim(` Cancel with: ${chalk4.cyan(`doable deployments cancel`)}`)
5133
+ chalk5.dim(` Cancel with: ${chalk5.cyan(`doable deployments cancel`)}`)
5071
5134
  );
5072
5135
  }
5073
5136
  if (rollbackable.length > 0) {
5074
5137
  console.log(
5075
- chalk4.dim(` Roll back with: ${chalk4.cyan(`doable deployments rollback <number>`)}`)
5138
+ chalk5.dim(` Roll back with: ${chalk5.cyan(`doable deployments rollback <number>`)}`)
5076
5139
  );
5077
5140
  }
5078
5141
  }
@@ -5084,7 +5147,7 @@ async function runRollback(numberArg, opts) {
5084
5147
  throw new Error(`Invalid deployment number: ${numberArg}`);
5085
5148
  }
5086
5149
  const projectId = await resolveProject2(opts.project, !process.stdin.isTTY);
5087
- const spinner = ora9("Looking up deployment...").start();
5150
+ const spinner = ora10("Looking up deployment...").start();
5088
5151
  const deployments = await listDeployments(projectId, { limit: 100 });
5089
5152
  spinner.stop();
5090
5153
  const target = deployments.find((d) => d.number === number);
@@ -5111,16 +5174,16 @@ async function runRollback(numberArg, opts) {
5111
5174
  if (!opts.yes && process.stdin.isTTY) {
5112
5175
  console.log();
5113
5176
  console.log(
5114
- ` Target: ${chalk4.cyan(`#${target.number}`)} ${chalk4.dim(`(${new Date(target.createdAt).toLocaleString()})`)}`
5177
+ ` Target: ${chalk5.cyan(`#${target.number}`)} ${chalk5.dim(`(${new Date(target.createdAt).toLocaleString()})`)}`
5115
5178
  );
5116
5179
  if (target.gitSha) {
5117
- console.log(` Git SHA: ${chalk4.dim(target.gitSha.slice(0, 7))}`);
5180
+ console.log(` Git SHA: ${chalk5.dim(target.gitSha.slice(0, 7))}`);
5118
5181
  }
5119
5182
  console.log();
5120
5183
  console.log(
5121
- chalk4.dim(" This will create a new deployment that reuses the artifact from")
5184
+ chalk5.dim(" This will create a new deployment that reuses the artifact from")
5122
5185
  );
5123
- console.log(chalk4.dim(` #${target.number}. Your current environment variables will be used.`));
5186
+ console.log(chalk5.dim(` #${target.number}. Your current environment variables will be used.`));
5124
5187
  console.log();
5125
5188
  const { confirm } = await inquirer3.prompt([
5126
5189
  {
@@ -5131,15 +5194,15 @@ async function runRollback(numberArg, opts) {
5131
5194
  }
5132
5195
  ]);
5133
5196
  if (!confirm) {
5134
- console.log(chalk4.dim(" Cancelled."));
5197
+ console.log(chalk5.dim(" Cancelled."));
5135
5198
  return;
5136
5199
  }
5137
5200
  }
5138
- const rollbackSpinner = ora9("Starting rollback...").start();
5201
+ const rollbackSpinner = ora10("Starting rollback...").start();
5139
5202
  try {
5140
5203
  const newDeployment = await rollbackDeployment(target.id);
5141
5204
  rollbackSpinner.succeed(
5142
- `Rollback started: new deployment ${chalk4.bold(`#${newDeployment.number}`)} (${chalk4.dim(newDeployment.id)})`
5205
+ `Rollback started: new deployment ${chalk5.bold(`#${newDeployment.number}`)} (${chalk5.dim(newDeployment.id)})`
5143
5206
  );
5144
5207
  } catch (err) {
5145
5208
  rollbackSpinner.fail("Rollback failed to start.");
@@ -5147,15 +5210,15 @@ async function runRollback(numberArg, opts) {
5147
5210
  }
5148
5211
  console.log();
5149
5212
  console.log(
5150
- chalk4.dim(
5151
- ` Track progress with: ${chalk4.cyan(`doable logs --deployment <id>`)}`
5213
+ chalk5.dim(
5214
+ ` Track progress with: ${chalk5.cyan(`doable logs --deployment <id>`)}`
5152
5215
  )
5153
5216
  );
5154
5217
  console.log();
5155
5218
  }
5156
5219
  async function runCancel(numberArg, opts) {
5157
5220
  const projectId = await resolveProject2(opts.project, !process.stdin.isTTY);
5158
- const spinner = ora9("Fetching deployments...").start();
5221
+ const spinner = ora10("Fetching deployments...").start();
5159
5222
  const deployments = await listDeployments(projectId, { limit: 20 });
5160
5223
  spinner.stop();
5161
5224
  let target;
@@ -5173,7 +5236,7 @@ async function runCancel(numberArg, opts) {
5173
5236
  } else {
5174
5237
  target = deployments.find((d) => CANCELABLE_STATUSES.includes(d.status));
5175
5238
  if (!target) {
5176
- console.log(chalk4.dim("\n No in-progress deployment to cancel.\n"));
5239
+ console.log(chalk5.dim("\n No in-progress deployment to cancel.\n"));
5177
5240
  return;
5178
5241
  }
5179
5242
  }
@@ -5185,7 +5248,7 @@ async function runCancel(numberArg, opts) {
5185
5248
  if (!opts.yes && process.stdin.isTTY) {
5186
5249
  console.log();
5187
5250
  console.log(
5188
- ` Target: ${chalk4.cyan(`#${target.number}`)} ${chalk4.yellow(target.status)} ${chalk4.dim(`(${new Date(target.createdAt).toLocaleString()})`)}`
5251
+ ` Target: ${chalk5.cyan(`#${target.number}`)} ${chalk5.yellow(target.status)} ${chalk5.dim(`(${new Date(target.createdAt).toLocaleString()})`)}`
5189
5252
  );
5190
5253
  console.log();
5191
5254
  const { confirm } = await inquirer3.prompt([
@@ -5197,22 +5260,22 @@ async function runCancel(numberArg, opts) {
5197
5260
  }
5198
5261
  ]);
5199
5262
  if (!confirm) {
5200
- console.log(chalk4.dim(" Aborted."));
5263
+ console.log(chalk5.dim(" Aborted."));
5201
5264
  return;
5202
5265
  }
5203
5266
  }
5204
- const cancelSpinner = ora9("Canceling deployment...").start();
5267
+ const cancelSpinner = ora10("Canceling deployment...").start();
5205
5268
  try {
5206
5269
  const updated = await cancelDeployment(target.id);
5207
5270
  cancelSpinner.succeed(
5208
- `Deployment ${chalk4.bold(`#${updated.number}`)} canceled.`
5271
+ `Deployment ${chalk5.bold(`#${updated.number}`)} canceled.`
5209
5272
  );
5210
5273
  } catch (err) {
5211
5274
  cancelSpinner.fail("Cancel failed.");
5212
5275
  throw err;
5213
5276
  }
5214
5277
  console.log(
5215
- chalk4.dim(
5278
+ chalk5.dim(
5216
5279
  "\n Your app continues running on the previous healthy version.\n"
5217
5280
  )
5218
5281
  );
@@ -5255,19 +5318,19 @@ async function resolveProject2(explicit, nonInteractive) {
5255
5318
  function colorStatus(status) {
5256
5319
  switch (status) {
5257
5320
  case "healthy":
5258
- return chalk4.green("healthy");
5321
+ return chalk5.green("healthy");
5259
5322
  case "failed":
5260
- return chalk4.red("failed");
5323
+ return chalk5.red("failed");
5261
5324
  case "canceled":
5262
- return chalk4.gray("canceled");
5325
+ return chalk5.gray("canceled");
5263
5326
  case "building":
5264
5327
  case "deploying":
5265
5328
  case "uploading":
5266
- return chalk4.yellow(status);
5329
+ return chalk5.yellow(status);
5267
5330
  case "queued":
5268
- return chalk4.blue("queued");
5331
+ return chalk5.blue("queued");
5269
5332
  default:
5270
- return chalk4.dim(status);
5333
+ return chalk5.dim(status);
5271
5334
  }
5272
5335
  }
5273
5336
  function pad4(str, width) {
@@ -5282,16 +5345,16 @@ function padRaw(str, width) {
5282
5345
  function handleError6(err) {
5283
5346
  if (err instanceof ApiError) {
5284
5347
  if (err.status === 401) {
5285
- console.error(chalk4.red("Not authenticated. Run `doable login` first."));
5348
+ console.error(chalk5.red("Not authenticated. Run `doable login` first."));
5286
5349
  } else if (err.status === 0) {
5287
- console.error(chalk4.red(`Connection error: ${err.message}`));
5350
+ console.error(chalk5.red(`Connection error: ${err.message}`));
5288
5351
  } else {
5289
- console.error(chalk4.red(`Error: ${err.message}`));
5352
+ console.error(chalk5.red(`Error: ${err.message}`));
5290
5353
  }
5291
5354
  } else if (err instanceof Error) {
5292
- console.error(chalk4.red(`Error: ${err.message}`));
5355
+ console.error(chalk5.red(`Error: ${err.message}`));
5293
5356
  } else {
5294
- console.error(chalk4.red(`Unexpected error: ${String(err)}`));
5357
+ console.error(chalk5.red(`Unexpected error: ${String(err)}`));
5295
5358
  }
5296
5359
  process.exit(1);
5297
5360
  }
@@ -5375,9 +5438,9 @@ async function runLocalPreflight(projectDir) {
5375
5438
  findings.push({ level: "fail", message: `.doable.json port is invalid: ${config2.port}` });
5376
5439
  }
5377
5440
  console.log();
5378
- console.log(chalk4.bold(" Doable local preflight"));
5441
+ console.log(chalk5.bold(" Doable local preflight"));
5379
5442
  for (const finding of findings) {
5380
- const icon = finding.level === "ok" ? chalk4.green("OK ") : finding.level === "warn" ? chalk4.yellow("WARN") : chalk4.red("FAIL");
5443
+ const icon = finding.level === "ok" ? chalk5.green("OK ") : finding.level === "warn" ? chalk5.yellow("WARN") : chalk5.red("FAIL");
5381
5444
  console.log(` ${icon} ${finding.message}`);
5382
5445
  }
5383
5446
  console.log();
@@ -5387,7 +5450,7 @@ async function runLocalPreflight(projectDir) {
5387
5450
  }
5388
5451
  async function runDoctorCommand(deploymentId, opts) {
5389
5452
  if (opts.run) {
5390
- const spin = ora9("Requesting diagnosis...").start();
5453
+ const spin = ora10("Requesting diagnosis...").start();
5391
5454
  try {
5392
5455
  await runDoctor(deploymentId);
5393
5456
  spin.text = "Diagnosing (this can take 10\u201320s)...";
@@ -5406,15 +5469,15 @@ async function runDoctorCommand(deploymentId, opts) {
5406
5469
  const report = await getDoctorReport(deploymentId);
5407
5470
  if (!report) {
5408
5471
  console.log();
5409
- console.log(chalk4.dim(" No Doctor report for this deployment."));
5472
+ console.log(chalk5.dim(" No Doctor report for this deployment."));
5410
5473
  console.log(
5411
- chalk4.dim(" Run with --run to generate one, or wait for an auto-diagnosis after a failure.")
5474
+ chalk5.dim(" Run with --run to generate one, or wait for an auto-diagnosis after a failure.")
5412
5475
  );
5413
5476
  console.log();
5414
5477
  return;
5415
5478
  }
5416
5479
  if (opts.dismiss) {
5417
- const spin = ora9("Dismissing...").start();
5480
+ const spin = ora10("Dismissing...").start();
5418
5481
  try {
5419
5482
  await dismissDoctorReport(report.id);
5420
5483
  spin.succeed("Report dismissed.");
@@ -5428,7 +5491,7 @@ async function runDoctorCommand(deploymentId, opts) {
5428
5491
  const idx = parseInt(opts.apply, 10);
5429
5492
  if (!Number.isFinite(idx) || idx < 0 || idx >= report.actions.length) {
5430
5493
  console.error(
5431
- chalk4.red(`Invalid action index ${opts.apply}. Report has ${report.actions.length} action(s).`)
5494
+ chalk5.red(`Invalid action index ${opts.apply}. Report has ${report.actions.length} action(s).`)
5432
5495
  );
5433
5496
  process.exit(1);
5434
5497
  }
@@ -5455,11 +5518,11 @@ async function runDoctorCommand(deploymentId, opts) {
5455
5518
  }
5456
5519
  ]);
5457
5520
  if (!answer.ok) {
5458
- console.log(chalk4.dim(" Aborted."));
5521
+ console.log(chalk5.dim(" Aborted."));
5459
5522
  return;
5460
5523
  }
5461
5524
  }
5462
- const spin = ora9(`Applying ${actionLabel(action)}...`).start();
5525
+ const spin = ora10(`Applying ${actionLabel(action)}...`).start();
5463
5526
  try {
5464
5527
  const result = await applyDoctorAction(report.id, idx, value);
5465
5528
  spin.succeed(result.message);
@@ -5473,34 +5536,34 @@ async function runDoctorCommand(deploymentId, opts) {
5473
5536
  }
5474
5537
  function renderReport(report) {
5475
5538
  console.log();
5476
- console.log(chalk4.bold(" \u{1F9E0} Doctor report"));
5539
+ console.log(chalk5.bold(" \u{1F9E0} Doctor report"));
5477
5540
  console.log(
5478
- chalk4.dim(
5541
+ chalk5.dim(
5479
5542
  ` ${report.aiModel} \xB7 confidence ${report.confidence} \xB7 status ${report.status}`
5480
5543
  )
5481
5544
  );
5482
5545
  console.log();
5483
- console.log(chalk4.bold(` ${report.summary}`));
5546
+ console.log(chalk5.bold(` ${report.summary}`));
5484
5547
  console.log();
5485
5548
  console.log(wrapLines(report.diagnosis, 76, " "));
5486
5549
  console.log();
5487
5550
  if (report.applyError) {
5488
- console.log(chalk4.red(` Last apply error: ${report.applyError}`));
5551
+ console.log(chalk5.red(` Last apply error: ${report.applyError}`));
5489
5552
  console.log();
5490
5553
  }
5491
5554
  if (report.actions.length === 0) {
5492
- console.log(chalk4.dim(" No suggested actions."));
5555
+ console.log(chalk5.dim(" No suggested actions."));
5493
5556
  console.log();
5494
5557
  return;
5495
5558
  }
5496
- console.log(chalk4.bold(" Suggested actions:"));
5559
+ console.log(chalk5.bold(" Suggested actions:"));
5497
5560
  report.actions.forEach((action, idx) => {
5498
- console.log(` ${chalk4.cyan(`[${idx}]`)} ${actionLabel(action)}`);
5499
- console.log(` ${chalk4.dim(action.rationale)}`);
5561
+ console.log(` ${chalk5.cyan(`[${idx}]`)} ${actionLabel(action)}`);
5562
+ console.log(` ${chalk5.dim(action.rationale)}`);
5500
5563
  });
5501
5564
  console.log();
5502
5565
  console.log(
5503
- chalk4.dim(
5566
+ chalk5.dim(
5504
5567
  ` Apply with: doable doctor <deploymentId> --apply <idx>`
5505
5568
  )
5506
5569
  );
@@ -5544,11 +5607,11 @@ function sleep2(ms) {
5544
5607
  }
5545
5608
  function handleError7(err) {
5546
5609
  if (err instanceof ApiError) {
5547
- console.error(chalk4.red(` API error: ${err.message}`));
5610
+ console.error(chalk5.red(` API error: ${err.message}`));
5548
5611
  } else if (err instanceof Error) {
5549
- console.error(chalk4.red(` ${err.message}`));
5612
+ console.error(chalk5.red(` ${err.message}`));
5550
5613
  } else {
5551
- console.error(chalk4.red(` Unknown error: ${String(err)}`));
5614
+ console.error(chalk5.red(` Unknown error: ${String(err)}`));
5552
5615
  }
5553
5616
  process.exit(1);
5554
5617
  }
@@ -5558,7 +5621,7 @@ function registerOpenCommand(program2) {
5558
5621
  const projectId = resolveProjectId(opts.project);
5559
5622
  if (!projectId) {
5560
5623
  console.error(
5561
- chalk4.red(
5624
+ chalk5.red(
5562
5625
  "No project found. Pass --project <id> or run from a directory with .doable.json."
5563
5626
  )
5564
5627
  );
@@ -5566,13 +5629,13 @@ function registerOpenCommand(program2) {
5566
5629
  }
5567
5630
  const project = await getProject(projectId);
5568
5631
  const url = pickUrl(project);
5569
- console.log(chalk4.dim(` Opening ${chalk4.cyan(url)}...`));
5632
+ console.log(chalk5.dim(` Opening ${chalk5.cyan(url)}...`));
5570
5633
  openBrowser(url);
5571
5634
  } catch (err) {
5572
5635
  if (err instanceof ApiError) {
5573
- console.error(chalk4.red(`Error: ${err.message}`));
5636
+ console.error(chalk5.red(`Error: ${err.message}`));
5574
5637
  } else if (err instanceof Error) {
5575
- console.error(chalk4.red(`Error: ${err.message}`));
5638
+ console.error(chalk5.red(`Error: ${err.message}`));
5576
5639
  }
5577
5640
  process.exit(1);
5578
5641
  }
@@ -5614,14 +5677,14 @@ function openBrowser(url) {
5614
5677
  function registerStatusCommand(program2) {
5615
5678
  program2.command("status").description("Show all your projects at a glance.").action(async () => {
5616
5679
  try {
5617
- const spinner = ora9("Loading\u2026").start();
5680
+ const spinner = ora10("Loading\u2026").start();
5618
5681
  const projects = await listProjects();
5619
5682
  spinner.stop();
5620
5683
  if (projects.length === 0) {
5621
5684
  console.log();
5622
- console.log(chalk4.bold(" Nothing shipped yet."));
5685
+ console.log(chalk5.bold(" Nothing shipped yet."));
5623
5686
  console.log(
5624
- chalk4.dim(" Run ") + chalk4.cyan("doable preview") + chalk4.dim(" to put something live with no signup, or ") + chalk4.cyan("doable new next") + chalk4.dim(" to scaffold a starter.")
5687
+ chalk5.dim(" Run ") + chalk5.cyan("doable preview") + chalk5.dim(" to put something live with no signup, or ") + chalk5.cyan("doable new next") + chalk5.dim(" to scaffold a starter.")
5625
5688
  );
5626
5689
  console.log();
5627
5690
  return;
@@ -5634,7 +5697,7 @@ function registerStatusCommand(program2) {
5634
5697
  const counts = countStatuses(projects);
5635
5698
  console.log();
5636
5699
  console.log(
5637
- chalk4.bold(` Your projects (${projects.length})`) + " " + chalk4.dim(formatCounts(counts))
5700
+ chalk5.bold(` Your projects (${projects.length})`) + " " + chalk5.dim(formatCounts(counts))
5638
5701
  );
5639
5702
  console.log();
5640
5703
  for (const p of projects) {
@@ -5644,13 +5707,13 @@ function registerStatusCommand(program2) {
5644
5707
  if (err instanceof ApiError) {
5645
5708
  if (err.status === 401) {
5646
5709
  console.error(
5647
- chalk4.red("Not authenticated. Run `doable login` first.")
5710
+ chalk5.red("Not authenticated. Run `doable login` first.")
5648
5711
  );
5649
5712
  } else {
5650
- console.error(chalk4.red(`Error: ${err.message}`));
5713
+ console.error(chalk5.red(`Error: ${err.message}`));
5651
5714
  }
5652
5715
  } else if (err instanceof Error) {
5653
- console.error(chalk4.red(`Error: ${err.message}`));
5716
+ console.error(chalk5.red(`Error: ${err.message}`));
5654
5717
  }
5655
5718
  process.exit(1);
5656
5719
  }
@@ -5660,31 +5723,31 @@ function renderTile(p) {
5660
5723
  const dot = statusDot(p.latestDeployment?.status);
5661
5724
  const status = formatStatus(p.latestDeployment);
5662
5725
  const liveUrl = `https://${p.slug}.doable.do`;
5663
- console.log(` ${dot} ${chalk4.bold(p.name)} ${chalk4.dim(status)}`);
5664
- console.log(` ${chalk4.cyan(liveUrl)}`);
5726
+ console.log(` ${dot} ${chalk5.bold(p.name)} ${chalk5.dim(status)}`);
5727
+ console.log(` ${chalk5.cyan(liveUrl)}`);
5665
5728
  if (p.latestDeployment) {
5666
5729
  const shareUrl = `https://doable.do/s/${p.latestDeployment.id}`;
5667
- console.log(` ${chalk4.dim("Share: ")}${chalk4.cyan(shareUrl)}`);
5730
+ console.log(` ${chalk5.dim("Share: ")}${chalk5.cyan(shareUrl)}`);
5668
5731
  }
5669
5732
  console.log();
5670
5733
  }
5671
5734
  function statusDot(status) {
5672
5735
  switch (status) {
5673
5736
  case "healthy":
5674
- return chalk4.green("\u25CF");
5737
+ return chalk5.green("\u25CF");
5675
5738
  case "failed":
5676
- return chalk4.red("\u25CF");
5739
+ return chalk5.red("\u25CF");
5677
5740
  case "building":
5678
5741
  case "deploying":
5679
5742
  case "uploading":
5680
5743
  case "queued":
5681
- return chalk4.yellow("\u25CF");
5744
+ return chalk5.yellow("\u25CF");
5682
5745
  case "canceled":
5683
5746
  case "rolled_back":
5684
5747
  case "suspended":
5685
- return chalk4.gray("\u25CF");
5748
+ return chalk5.gray("\u25CF");
5686
5749
  default:
5687
- return chalk4.gray("\u25CB");
5750
+ return chalk5.gray("\u25CB");
5688
5751
  }
5689
5752
  }
5690
5753
  function formatStatus(d) {
@@ -5749,7 +5812,7 @@ function registerInitCommand(program2) {
5749
5812
  await runInit(opts.path);
5750
5813
  } catch (err) {
5751
5814
  if (err instanceof Error) {
5752
- console.error(chalk4.red(`Error: ${err.message}`));
5815
+ console.error(chalk5.red(`Error: ${err.message}`));
5753
5816
  }
5754
5817
  process.exit(1);
5755
5818
  }
@@ -5765,7 +5828,7 @@ async function runInit(dirPath) {
5765
5828
  throw new Error(`Directory not found: ${projectDir}`);
5766
5829
  }
5767
5830
  console.log();
5768
- console.log(chalk4.bold(" Scanning your project..."));
5831
+ console.log(chalk5.bold(" Scanning your project..."));
5769
5832
  console.log();
5770
5833
  const detected = await detectRuntime(projectDir, nodeDetectFS4);
5771
5834
  const runtimeLabel = detected.framework ? `${detected.runtime} (${detected.framework})` : detected.runtime;
@@ -5850,12 +5913,12 @@ async function runInit(dirPath) {
5850
5913
  });
5851
5914
  }
5852
5915
  for (const check of checks) {
5853
- const icon = check.status === "ok" ? chalk4.green(" +") : check.status === "fix" ? chalk4.yellow(" ~") : chalk4.dim(" !");
5854
- const detail = check.status === "warn" ? chalk4.yellow(check.detail) : chalk4.dim(check.detail);
5855
- console.log(`${icon} ${chalk4.white(check.label)} ${detail}`);
5916
+ const icon = check.status === "ok" ? chalk5.green(" +") : check.status === "fix" ? chalk5.yellow(" ~") : chalk5.dim(" !");
5917
+ const detail = check.status === "warn" ? chalk5.yellow(check.detail) : chalk5.dim(check.detail);
5918
+ console.log(`${icon} ${chalk5.white(check.label)} ${detail}`);
5856
5919
  }
5857
5920
  console.log();
5858
- console.log(chalk4.dim(` Build estimate: ${estimate}`));
5921
+ console.log(chalk5.dim(` Build estimate: ${estimate}`));
5859
5922
  console.log();
5860
5923
  const fixes = checks.filter((c) => c.status === "fix");
5861
5924
  if (fixes.length > 0 || !hasDoableJson) {
@@ -5868,13 +5931,13 @@ async function runInit(dirPath) {
5868
5931
  }
5869
5932
  ]);
5870
5933
  if (!proceed) {
5871
- console.log(chalk4.dim(" Skipped."));
5934
+ console.log(chalk5.dim(" Skipped."));
5872
5935
  return;
5873
5936
  }
5874
5937
  if (!hasGitignore) {
5875
5938
  const gitignoreContent = detected.runtime === "node" ? "node_modules/\n.env\n.env.local\ndist/\n.next/\n" : detected.runtime === "python" ? "__pycache__/\n*.pyc\n.env\n.venv/\nvenv/\n" : ".env\n";
5876
5939
  fs6.writeFileSync(path7.join(projectDir, ".gitignore"), gitignoreContent);
5877
- console.log(chalk4.green(" Created .gitignore"));
5940
+ console.log(chalk5.green(" Created .gitignore"));
5878
5941
  }
5879
5942
  if (detected.runtime === "node") {
5880
5943
  const pkgPath = path7.join(projectDir, "package.json");
@@ -5886,7 +5949,7 @@ async function runInit(dirPath) {
5886
5949
  pkg.scripts = pkg.scripts || {};
5887
5950
  pkg.scripts.start = `node ${mainFile}`;
5888
5951
  fs6.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
5889
- console.log(chalk4.green(` Added start script: node ${mainFile}`));
5952
+ console.log(chalk5.green(` Added start script: node ${mainFile}`));
5890
5953
  }
5891
5954
  } catch {
5892
5955
  }
@@ -5900,17 +5963,17 @@ async function runInit(dirPath) {
5900
5963
  target: "cloud"
5901
5964
  };
5902
5965
  fs6.writeFileSync(doableJsonPath, JSON.stringify(doableJson, null, 2) + "\n");
5903
- console.log(chalk4.green(" Created .doable.json"));
5966
+ console.log(chalk5.green(" Created .doable.json"));
5904
5967
  }
5905
5968
  const claudeMdPath = path7.join(projectDir, "CLAUDE.md");
5906
5969
  if (!fs6.existsSync(claudeMdPath)) {
5907
5970
  fs6.writeFileSync(claudeMdPath, generateClaudeMd(detected.runtime, detected.framework ?? null));
5908
- console.log(chalk4.green(" Created CLAUDE.md (teaches AI assistants how to deploy)"));
5971
+ console.log(chalk5.green(" Created CLAUDE.md (teaches AI assistants how to deploy)"));
5909
5972
  }
5910
5973
  console.log();
5911
5974
  }
5912
- console.log(chalk4.bold(" Ready to deploy."));
5913
- console.log(chalk4.dim(` Run ${chalk4.cyan("doable deploy")} to go live.`));
5975
+ console.log(chalk5.bold(" Ready to deploy."));
5976
+ console.log(chalk5.dim(` Run ${chalk5.cyan("doable deploy")} to go live.`));
5914
5977
  console.log();
5915
5978
  }
5916
5979
  function generateClaudeMd(runtime, framework) {
@@ -5970,14 +6033,14 @@ function registerClaimCommand(program2) {
5970
6033
  async (tokenArg, opts) => {
5971
6034
  const token = tokenArg || readTokenFromPreviewJson();
5972
6035
  if (!token) {
5973
- console.error(chalk4.red("No preview token provided."));
6036
+ console.error(chalk5.red("No preview token provided."));
5974
6037
  console.error(
5975
- chalk4.dim(
5976
- " Pass it as an argument: " + chalk4.cyan("doable claim prv_abc123")
6038
+ chalk5.dim(
6039
+ " Pass it as an argument: " + chalk5.cyan("doable claim prv_abc123")
5977
6040
  )
5978
6041
  );
5979
6042
  console.error(
5980
- chalk4.dim(
6043
+ chalk5.dim(
5981
6044
  " Or run this from the same directory where you ran `doable preview` so the CLI can pick it up automatically."
5982
6045
  )
5983
6046
  );
@@ -5985,13 +6048,13 @@ function registerClaimCommand(program2) {
5985
6048
  }
5986
6049
  const apiToken = getToken();
5987
6050
  if (!apiToken) {
5988
- console.error(chalk4.red("You're not logged in."));
6051
+ console.error(chalk5.red("You're not logged in."));
5989
6052
  console.error(
5990
- chalk4.dim(" Run " + chalk4.cyan("doable login") + " first, then retry.")
6053
+ chalk5.dim(" Run " + chalk5.cyan("doable login") + " first, then retry.")
5991
6054
  );
5992
6055
  process.exit(1);
5993
6056
  }
5994
- const spinner = ora9("Claiming preview\u2026").start();
6057
+ const spinner = ora10("Claiming preview\u2026").start();
5995
6058
  let result;
5996
6059
  try {
5997
6060
  const res = await fetch(`${getApiUrl()}/v1/preview/claim`, {
@@ -6013,31 +6076,31 @@ function registerClaimCommand(program2) {
6013
6076
  spinner.fail(
6014
6077
  "Couldn't reach the Doable API. Check your connection and try again."
6015
6078
  );
6016
- console.error(chalk4.dim(err instanceof Error ? err.message : String(err)));
6079
+ console.error(chalk5.dim(err instanceof Error ? err.message : String(err)));
6017
6080
  process.exit(1);
6018
6081
  }
6019
6082
  spinner.succeed("Preview claimed.");
6020
6083
  const liveUrl = `https://${result.projectSlug}.doable.do`;
6021
6084
  console.log();
6022
- console.log(chalk4.green.bold(" It's yours."));
6023
- console.log(` Live at ${chalk4.underline.cyan(liveUrl)}`);
6085
+ console.log(chalk5.green.bold(" It's yours."));
6086
+ console.log(` Live at ${chalk5.underline.cyan(liveUrl)}`);
6024
6087
  console.log(
6025
- ` Share ${chalk4.cyan(`https://doable.do/s/${result.projectId}`)}`
6088
+ ` Share ${chalk5.cyan(`https://doable.do/s/${result.projectId}`)}`
6026
6089
  );
6027
- console.log(chalk4.dim(` Project ${result.projectSlug}`));
6090
+ console.log(chalk5.dim(` Project ${result.projectSlug}`));
6028
6091
  console.log();
6029
- console.log(chalk4.dim(" Next:"));
6092
+ console.log(chalk5.dim(" Next:"));
6030
6093
  console.log(
6031
- chalk4.dim(
6032
- " Add a custom domain: " + chalk4.cyan(`doable domain search yourname.com`)
6094
+ chalk5.dim(
6095
+ " Add a custom domain: " + chalk5.cyan(`doable domain search yourname.com`)
6033
6096
  )
6034
6097
  );
6035
6098
  console.log(
6036
- chalk4.dim(" Manage env vars: " + chalk4.cyan("doable env list"))
6099
+ chalk5.dim(" Manage env vars: " + chalk5.cyan("doable env list"))
6037
6100
  );
6038
6101
  console.log(
6039
- chalk4.dim(
6040
- " Watch logs: " + chalk4.cyan(`doable logs --deployment <id>`)
6102
+ chalk5.dim(
6103
+ " Watch logs: " + chalk5.cyan(`doable logs --deployment <id>`)
6041
6104
  )
6042
6105
  );
6043
6106
  console.log();
@@ -6305,13 +6368,13 @@ function registerNewCommand(program2) {
6305
6368
  return;
6306
6369
  }
6307
6370
  if (!templateArg) {
6308
- console.error(chalk4.red("Pick a template."));
6371
+ console.error(chalk5.red("Pick a template."));
6309
6372
  printTemplateList();
6310
6373
  process.exit(1);
6311
6374
  }
6312
6375
  const template = TEMPLATES[templateArg];
6313
6376
  if (!template) {
6314
- console.error(chalk4.red(`Unknown template: ${templateArg}`));
6377
+ console.error(chalk5.red(`Unknown template: ${templateArg}`));
6315
6378
  printTemplateList();
6316
6379
  process.exit(1);
6317
6380
  }
@@ -6319,7 +6382,7 @@ function registerNewCommand(program2) {
6319
6382
  const safeName = sanitizeName(projectName);
6320
6383
  if (!safeName) {
6321
6384
  console.error(
6322
- chalk4.red(
6385
+ chalk5.red(
6323
6386
  `"${projectName}" isn't a valid project name. Use letters, numbers, dashes.`
6324
6387
  )
6325
6388
  );
@@ -6330,12 +6393,12 @@ function registerNewCommand(program2) {
6330
6393
  const entries = fs6.readdirSync(targetDir);
6331
6394
  if (entries.length > 0) {
6332
6395
  console.error(
6333
- chalk4.red(
6396
+ chalk5.red(
6334
6397
  `Directory ${path7.relative(process.cwd(), targetDir) || "."} already exists and isn't empty.`
6335
6398
  )
6336
6399
  );
6337
6400
  console.error(
6338
- chalk4.dim(
6401
+ chalk5.dim(
6339
6402
  " Pick a different name, or pass --into <dir> to be explicit."
6340
6403
  )
6341
6404
  );
@@ -6347,19 +6410,19 @@ function registerNewCommand(program2) {
6347
6410
  scaffold(template, safeName, targetDir);
6348
6411
  const relDir = path7.relative(process.cwd(), targetDir) || ".";
6349
6412
  console.log();
6350
- console.log(chalk4.green.bold(` Done. Created ${safeName} from the ${template.name} template.`));
6413
+ console.log(chalk5.green.bold(` Done. Created ${safeName} from the ${template.name} template.`));
6351
6414
  console.log();
6352
- console.log(chalk4.dim(" Next:"));
6415
+ console.log(chalk5.dim(" Next:"));
6353
6416
  if (relDir !== ".") {
6354
6417
  console.log(
6355
- " " + chalk4.cyan(`cd ${relDir}`) + chalk4.dim(" switch to the new directory")
6418
+ " " + chalk5.cyan(`cd ${relDir}`) + chalk5.dim(" switch to the new directory")
6356
6419
  );
6357
6420
  }
6358
6421
  console.log(
6359
- " " + chalk4.cyan("doable preview") + chalk4.dim(" deploy with no signup, live in seconds")
6422
+ " " + chalk5.cyan("doable preview") + chalk5.dim(" deploy with no signup, live in seconds")
6360
6423
  );
6361
6424
  console.log(
6362
- " " + chalk4.cyan("doable deploy") + chalk4.dim(" deploy to your account")
6425
+ " " + chalk5.cyan("doable deploy") + chalk5.dim(" deploy to your account")
6363
6426
  );
6364
6427
  console.log();
6365
6428
  }
@@ -6382,19 +6445,19 @@ function scaffold(template, name, targetDir) {
6382
6445
  written.push(".doable.json");
6383
6446
  }
6384
6447
  for (const file of written) {
6385
- console.log(chalk4.dim(` + ${file}`));
6448
+ console.log(chalk5.dim(` + ${file}`));
6386
6449
  }
6387
6450
  }
6388
6451
  function printTemplateList() {
6389
6452
  console.log();
6390
- console.log(chalk4.bold(" Templates"));
6453
+ console.log(chalk5.bold(" Templates"));
6391
6454
  console.log();
6392
6455
  for (const t of listTemplates()) {
6393
- console.log(` ${chalk4.cyan(t.name.padEnd(12))}${chalk4.dim(t.description)}`);
6456
+ console.log(` ${chalk5.cyan(t.name.padEnd(12))}${chalk5.dim(t.description)}`);
6394
6457
  }
6395
6458
  console.log();
6396
- console.log(chalk4.dim(" Usage:"));
6397
- console.log(" " + chalk4.cyan("doable new next my-app"));
6459
+ console.log(chalk5.dim(" Usage:"));
6460
+ console.log(" " + chalk5.cyan("doable new next my-app"));
6398
6461
  console.log();
6399
6462
  }
6400
6463
  function sanitizeName(input) {
@@ -6410,7 +6473,7 @@ function registerUpgradeCommand(program2) {
6410
6473
  "Exit code only: 0 if up to date, 1 if a newer version is available."
6411
6474
  ).action(async (opts) => {
6412
6475
  const current = readCliVersion();
6413
- const spinner = ora9("Checking the npm registry\u2026").start();
6476
+ const spinner = ora10("Checking the npm registry\u2026").start();
6414
6477
  let latest;
6415
6478
  try {
6416
6479
  latest = await fetchLatestVersion();
@@ -6419,42 +6482,42 @@ function registerUpgradeCommand(program2) {
6419
6482
  "Couldn't reach the npm registry. Try again, or upgrade manually."
6420
6483
  );
6421
6484
  console.error(
6422
- chalk4.dim(err instanceof Error ? err.message : String(err))
6485
+ chalk5.dim(err instanceof Error ? err.message : String(err))
6423
6486
  );
6424
6487
  process.exit(1);
6425
6488
  }
6426
6489
  spinner.stop();
6427
6490
  const cmp = compareSemver(current, latest);
6428
6491
  if (cmp >= 0) {
6429
- console.log(chalk4.green(` You're on ${current}. That's the latest.`));
6492
+ console.log(chalk5.green(` You're on ${current}. That's the latest.`));
6430
6493
  process.exit(0);
6431
6494
  }
6432
6495
  if (opts.check) {
6433
6496
  console.log(
6434
- chalk4.yellow(` ${current} \u2192 ${latest}. A newer version is available.`)
6497
+ chalk5.yellow(` ${current} \u2192 ${latest}. A newer version is available.`)
6435
6498
  );
6436
6499
  process.exit(1);
6437
6500
  }
6438
6501
  console.log();
6439
- console.log(chalk4.bold(` Update available: ${current} \u2192 ${latest}`));
6502
+ console.log(chalk5.bold(` Update available: ${current} \u2192 ${latest}`));
6440
6503
  console.log();
6441
6504
  if (!opts.install) {
6442
- console.log(chalk4.dim(" Install it with one of:"));
6443
- console.log(" " + chalk4.cyan("npm install -g doable-cli@latest"));
6444
- console.log(" " + chalk4.cyan("brew upgrade doublewltd/doable/doable") + chalk4.dim(" (Homebrew)"));
6445
- console.log(" " + chalk4.cyan("curl -fsSL https://doable.do/install.sh | bash"));
6505
+ console.log(chalk5.dim(" Install it with one of:"));
6506
+ console.log(" " + chalk5.cyan("npm install -g doable-cli@latest"));
6507
+ console.log(" " + chalk5.cyan("brew upgrade doublewltd/doable/doable") + chalk5.dim(" (Homebrew)"));
6508
+ console.log(" " + chalk5.cyan("curl -fsSL https://doable.do/install.sh | bash"));
6446
6509
  console.log();
6447
- console.log(chalk4.dim(" Or rerun this with --install to do it now."));
6510
+ console.log(chalk5.dim(" Or rerun this with --install to do it now."));
6448
6511
  return;
6449
6512
  }
6450
- const installSpinner = ora9("Running npm install -g doable-cli@latest\u2026").start();
6513
+ const installSpinner = ora10("Running npm install -g doable-cli@latest\u2026").start();
6451
6514
  const code = await runNpmGlobal();
6452
6515
  if (code === 0) {
6453
6516
  installSpinner.succeed(`Upgraded to ${latest}.`);
6454
6517
  } else {
6455
6518
  installSpinner.fail(`npm install exited with code ${code}.`);
6456
6519
  console.error(
6457
- chalk4.dim(
6520
+ chalk5.dim(
6458
6521
  " If your CLI was installed via Homebrew or the install script, use that instead."
6459
6522
  )
6460
6523
  );
@@ -6511,10 +6574,11 @@ function runNpmGlobal() {
6511
6574
  }
6512
6575
 
6513
6576
  // src/index.ts
6514
- var CLI_VERSION = "0.1.3" ;
6577
+ var CLI_VERSION = "0.1.4" ;
6515
6578
  var program = new Command();
6516
6579
  program.name("doable").description("Doable -- AI-native deployment platform").version(CLI_VERSION, "-v, --version");
6517
6580
  registerLoginCommand(program);
6581
+ registerLogoutCommand(program);
6518
6582
  registerDeployCommand(program);
6519
6583
  registerProjectsCommand(program);
6520
6584
  registerDomainCommand(program);