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