vibeview 0.24.0 → 0.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -12,6 +12,7 @@ import {
12
12
  formatDevicesHuman,
13
13
  formatDuration,
14
14
  killOwnedMetro,
15
+ listBuilds,
15
16
  loadDevConfig,
16
17
  newTransitionState,
17
18
  printOutcomeLine,
@@ -19,7 +20,7 @@ import {
19
20
  readDevState,
20
21
  readMetroState,
21
22
  runAgentVerb
22
- } from "./chunk-MVNMGB6I.js";
23
+ } from "./chunk-GEKYHEWU.js";
23
24
  import {
24
25
  ApiClient,
25
26
  EXIT_INFRA_ERROR,
@@ -32,7 +33,7 @@ import {
32
33
  resolveAuth,
33
34
  saveConfig,
34
35
  uploadApp
35
- } from "./chunk-2WNJYNDW.js";
36
+ } from "./chunk-RFFCCPGZ.js";
36
37
 
37
38
  // src/cli.ts
38
39
  import { createRequire } from "module";
@@ -881,8 +882,52 @@ async function listApps(options) {
881
882
  console.log();
882
883
  }
883
884
 
884
- // src/commands/create-test.ts
885
+ // src/commands/stop.ts
885
886
  import chalk8 from "chalk";
887
+ async function stopCommand(sessionId, opts, deps) {
888
+ const id = sessionId?.trim();
889
+ if (!id) throw new UsageError("a session id is required: vibeview stop <session-id>");
890
+ const { token, baseUrl } = resolveAuth(opts);
891
+ const d = deps ?? { stopSession: (s) => new ApiClient(baseUrl, token).stopSession(s) };
892
+ try {
893
+ await d.stopSession(id);
894
+ } catch (err) {
895
+ const message = err instanceof Error ? err.message : String(err);
896
+ if (opts.json) console.log(JSON.stringify({ stopped: false, session_id: id, message }));
897
+ console.error(chalk8.red(`error: ${message}`));
898
+ process.exitCode = 1;
899
+ return;
900
+ }
901
+ const state = readDevState(process.cwd());
902
+ let devPid = null;
903
+ if (state && state.session_id === id) {
904
+ devPid = state.pid;
905
+ try {
906
+ process.kill(state.pid, "SIGTERM");
907
+ } catch (err) {
908
+ if (err.code !== "ESRCH") throw err;
909
+ }
910
+ clearDevState(process.cwd());
911
+ killOwnedMetro(process.cwd());
912
+ }
913
+ if (opts.json) {
914
+ console.log(
915
+ JSON.stringify({
916
+ stopped: true,
917
+ session_id: id,
918
+ ...devPid !== null ? { pid: devPid } : {}
919
+ })
920
+ );
921
+ } else {
922
+ console.error(
923
+ `Stopped session ${id}${devPid !== null ? ` (and this project's dev process, pid ${devPid})` : ""}.`
924
+ );
925
+ }
926
+ process.exitCode = 0;
927
+ }
928
+
929
+ // src/commands/create-test.ts
930
+ import chalk9 from "chalk";
886
931
  import { existsSync, readFileSync } from "fs";
887
932
  async function readStdin() {
888
933
  return new Promise((resolve, reject) => {
@@ -973,19 +1018,19 @@ async function createTest(options) {
973
1018
  );
974
1019
  } else {
975
1020
  console.log(`
976
- ${chalk8.cyan(result.public_id)} ${result.name}`);
1021
+ ${chalk9.cyan(result.public_id)} ${result.name}`);
977
1022
  if (result.app_name) {
978
- console.log(` ${chalk8.dim(`App: ${result.app_name}`)}`);
1023
+ console.log(` ${chalk9.dim(`App: ${result.app_name}`)}`);
979
1024
  }
980
1025
  if (result.platform) {
981
- console.log(` ${chalk8.dim(`Platform: ${result.platform}`)}`);
1026
+ console.log(` ${chalk9.dim(`Platform: ${result.platform}`)}`);
982
1027
  }
983
- console.log(` ${chalk8.dim(`Steps: ${steps.length}`)}`);
984
- console.log(chalk8.dim(` ${baseUrl}/tests?tab=standalone
1028
+ console.log(` ${chalk9.dim(`Steps: ${steps.length}`)}`);
1029
+ console.log(chalk9.dim(` ${baseUrl}/tests?tab=standalone
985
1030
  `));
986
1031
  }
987
1032
  } catch (err) {
988
- console.error(chalk8.red(err instanceof Error ? err.message : "Failed to create test"));
1033
+ console.error(chalk9.red(err instanceof Error ? err.message : "Failed to create test"));
989
1034
  process.exit(EXIT_INFRA_ERROR);
990
1035
  }
991
1036
  }
@@ -1067,7 +1112,7 @@ async function logout() {
1067
1112
  }
1068
1113
 
1069
1114
  // src/commands/dev-status.ts
1070
- import chalk9 from "chalk";
1115
+ import chalk10 from "chalk";
1071
1116
  async function devStatus(opts) {
1072
1117
  const state = readDevState(process.cwd());
1073
1118
  if (!state) {
@@ -1112,7 +1157,7 @@ async function devStatus(opts) {
1112
1157
  console.error(` url: ${state.page_url}`);
1113
1158
  console.error(` started: ${state.started_at}`);
1114
1159
  }
1115
- if (metro) console.error(chalk9.dim(`Metro: pid ${metro.pid} on :${metro.port} (CLI-owned)`));
1160
+ if (metro) console.error(chalk10.dim(`Metro: pid ${metro.pid} on :${metro.port} (CLI-owned)`));
1116
1161
  process.exit(0);
1117
1162
  }
1118
1163
 
@@ -1148,12 +1193,12 @@ async function devStop(opts) {
1148
1193
  }
1149
1194
 
1150
1195
  // src/commands/dev-reload.ts
1151
- import chalk10 from "chalk";
1196
+ import chalk11 from "chalk";
1152
1197
  function fail(opts, code, message, exit) {
1153
1198
  if (opts.json) {
1154
1199
  console.log(JSON.stringify({ reloaded: false, code, message }));
1155
1200
  } else {
1156
- console.error(chalk10.red(`error: ${message}`));
1201
+ console.error(chalk11.red(`error: ${message}`));
1157
1202
  }
1158
1203
  process.exit(exit);
1159
1204
  }
@@ -1206,7 +1251,7 @@ async function devReload(opts) {
1206
1251
  api,
1207
1252
  // Build output and the "reloaded in" line are human output: stderr, so
1208
1253
  // stdout stays reserved for the --json result.
1209
- log: (line) => console.error(chalk10.dim(line))
1254
+ log: (line) => console.error(chalk11.dim(line))
1210
1255
  });
1211
1256
  const started = Date.now();
1212
1257
  try {
@@ -1227,22 +1272,22 @@ async function devReload(opts) {
1227
1272
  }
1228
1273
 
1229
1274
  // src/commands/builds.ts
1230
- import chalk12 from "chalk";
1275
+ import chalk13 from "chalk";
1231
1276
 
1232
1277
  // src/lib/formatters/builds.ts
1233
- import chalk11 from "chalk";
1278
+ import chalk12 from "chalk";
1234
1279
  var HEADERS = ["ID", "PLATFORM", "STATUS", "CREATED", "COST", "APP"];
1235
1280
  function formatCostCell(costMicro) {
1236
1281
  return costMicro > 0 ? `$${(costMicro / 1e6).toFixed(2)}` : "";
1237
1282
  }
1238
1283
  var STATUS_COLOR = {
1239
- succeeded: chalk11.green,
1240
- failed: chalk11.red,
1241
- timed_out: chalk11.red,
1242
- infra_failed: chalk11.red,
1243
- canceled: chalk11.dim,
1244
- running: chalk11.yellow,
1245
- queued: chalk11.dim
1284
+ succeeded: chalk12.green,
1285
+ failed: chalk12.red,
1286
+ timed_out: chalk12.red,
1287
+ infra_failed: chalk12.red,
1288
+ canceled: chalk12.dim,
1289
+ running: chalk12.yellow,
1290
+ queued: chalk12.dim
1246
1291
  };
1247
1292
  function formatBuildsHuman(builds2) {
1248
1293
  if (builds2.length === 0) {
@@ -1259,7 +1304,7 @@ function formatBuildsHuman(builds2) {
1259
1304
  const widths = HEADERS.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
1260
1305
  const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
1261
1306
  const join3 = (cells) => cells.join(" ").trimEnd();
1262
- const lines = [chalk11.bold(join3(padCells(HEADERS)))];
1307
+ const lines = [chalk12.bold(join3(padCells(HEADERS)))];
1263
1308
  for (const row of rows) {
1264
1309
  const padded = padCells(row);
1265
1310
  const colorFn = STATUS_COLOR[row[2]] ?? ((s) => s);
@@ -1318,7 +1363,7 @@ async function buildsLogs(id, opts, deps) {
1318
1363
  console.log(JSON.stringify(logs));
1319
1364
  } else {
1320
1365
  if (logs.content) process.stdout.write(logs.content);
1321
- console.error(chalk12.dim(`status: ${logs.status}`));
1366
+ console.error(chalk13.dim(`status: ${logs.status}`));
1322
1367
  }
1323
1368
  process.exitCode = 0;
1324
1369
  return;
@@ -1415,7 +1460,7 @@ async function buildsDownload(id, opts, deps) {
1415
1460
  if (opts.json) {
1416
1461
  console.log(JSON.stringify(result));
1417
1462
  } else {
1418
- console.log(chalk12.green(`\u2713 saved ${result.path}`));
1463
+ console.log(chalk13.green(`\u2713 saved ${result.path}`));
1419
1464
  }
1420
1465
  process.exitCode = 0;
1421
1466
  } catch (err) {
@@ -1432,7 +1477,7 @@ async function buildsCancel(id, opts, deps) {
1432
1477
  if (opts.json) {
1433
1478
  console.log(JSON.stringify(build));
1434
1479
  } else {
1435
- console.log(`${chalk12.cyan(build.id)} ${build.status}`);
1480
+ console.log(`${chalk13.cyan(build.id)} ${build.status}`);
1436
1481
  }
1437
1482
  process.exitCode = 0;
1438
1483
  } catch (err) {
@@ -1449,7 +1494,7 @@ async function buildsDeleteArtifact(id, opts, deps) {
1449
1494
  if (opts.json) {
1450
1495
  console.log(JSON.stringify(result));
1451
1496
  } else {
1452
- console.log(chalk12.green(`\u2713 deleted artifact for ${id}`));
1497
+ console.log(chalk13.green(`\u2713 deleted artifact for ${id}`));
1453
1498
  }
1454
1499
  process.exitCode = 0;
1455
1500
  } catch (err) {
@@ -1460,11 +1505,11 @@ async function buildsDeleteArtifact(id, opts, deps) {
1460
1505
  function printError(opts, err) {
1461
1506
  const message = err instanceof Error ? err.message : String(err);
1462
1507
  if (opts.json) console.log(JSON.stringify({ event: "error", message }));
1463
- console.error(chalk12.red(`error: ${message}`));
1508
+ console.error(chalk13.red(`error: ${message}`));
1464
1509
  }
1465
1510
 
1466
1511
  // src/commands/submit.ts
1467
- import chalk13 from "chalk";
1512
+ import chalk14 from "chalk";
1468
1513
  function defaultDeps2(client) {
1469
1514
  return {
1470
1515
  createSubmission: client.createSubmission.bind(client),
@@ -1487,7 +1532,7 @@ function submitConfigFor(cwd, platform) {
1487
1532
  cfg = loadDevConfig(cwd);
1488
1533
  } catch (err) {
1489
1534
  console.error(
1490
- chalk13.yellow(
1535
+ chalk14.yellow(
1491
1536
  `warning: vibeview.json could not be read \u2014 ignoring submit defaults (${err instanceof Error ? err.message : String(err)})`
1492
1537
  )
1493
1538
  );
@@ -1635,12 +1680,12 @@ async function submitCommand(opts, deps) {
1635
1680
  return;
1636
1681
  }
1637
1682
  if (!opts.json) {
1638
- console.error(` ${chalk13.cyan(sub.id)} ${chalk13.dim(`[${sub.platform}]`)}`);
1683
+ console.error(` ${chalk14.cyan(sub.id)} ${chalk14.dim(`[${sub.platform}]`)}`);
1639
1684
  }
1640
1685
  const final = await followSubmission(sub, opts, d);
1641
1686
  if (!opts.json && final.status === "succeeded" && (final.release_status ?? releaseStatus) === "draft") {
1642
1687
  console.error(
1643
- chalk13.dim("release status: draft \u2014 activate it from the Play Console when ready")
1688
+ chalk14.dim("release status: draft \u2014 activate it from the Play Console when ready")
1644
1689
  );
1645
1690
  }
1646
1691
  process.exitCode = final.status === "succeeded" ? 0 : 1;
@@ -1664,11 +1709,11 @@ async function checkStore(platform, androidFamily, ascKey2, opts, deps) {
1664
1709
  });
1665
1710
  emit(opts.json, { event: "preflight", ...result });
1666
1711
  if (!opts.json) {
1667
- const mark = result.ready ? chalk13.green("\u2713") : chalk13.red("\u2717");
1712
+ const mark = result.ready ? chalk14.green("\u2713") : chalk14.red("\u2717");
1668
1713
  console.error(`${mark} ${result.message}`);
1669
1714
  if (result.ready) {
1670
1715
  console.error(
1671
- chalk13.dim(
1716
+ chalk14.dim(
1672
1717
  ` next: vibeview build --cloud --production --platform ${platform}, then vibeview submit --platform ${platform} --latest`
1673
1718
  )
1674
1719
  );
@@ -1717,14 +1762,14 @@ async function followSubmission(created, opts, deps) {
1717
1762
  if (opts.json) {
1718
1763
  emit(opts.json, { event: "status", id: created.id, status: "queued" });
1719
1764
  } else {
1720
- console.error(chalk13.dim("\u23F3 queued \u2014 waiting for a submission slot"));
1765
+ console.error(chalk14.dim("\u23F3 queued \u2014 waiting for a submission slot"));
1721
1766
  }
1722
1767
  } else if (log.status === "running" && !printedRunning) {
1723
1768
  printedRunning = true;
1724
1769
  if (opts.json) {
1725
1770
  emit(opts.json, { event: "status", id: created.id, status: "running" });
1726
1771
  } else {
1727
- console.error(`${chalk13.cyan("\u25B6")} submitting`);
1772
+ console.error(`${chalk14.cyan("\u25B6")} submitting`);
1728
1773
  }
1729
1774
  }
1730
1775
  if (log.content) {
@@ -1766,7 +1811,7 @@ The submission continues server-side \u2014 see its outcome on the Builds page i
1766
1811
  });
1767
1812
  if (!opts.json) {
1768
1813
  const ok = final.status === "succeeded";
1769
- const mark = ok ? chalk13.green("\u2713") : chalk13.red("\u2717");
1814
+ const mark = ok ? chalk14.green("\u2713") : chalk14.red("\u2717");
1770
1815
  const duration = formatDuration(final.started_at, final.finished_at);
1771
1816
  let line = `${mark} ${final.status}`;
1772
1817
  if (duration) line += ` (${duration})`;
@@ -1778,11 +1823,11 @@ The submission continues server-side \u2014 see its outcome on the Builds page i
1778
1823
  function printError2(opts, err) {
1779
1824
  const message = err instanceof Error ? err.message : String(err);
1780
1825
  if (opts.json) console.log(JSON.stringify({ event: "error", message }));
1781
- console.error(chalk13.red(`error: ${message}`));
1826
+ console.error(chalk14.red(`error: ${message}`));
1782
1827
  }
1783
1828
 
1784
1829
  // src/commands/versions.ts
1785
- import chalk14 from "chalk";
1830
+ import chalk15 from "chalk";
1786
1831
  function defaultDeps3(client) {
1787
1832
  return {
1788
1833
  listAppVersions: client.listAppVersions.bind(client),
@@ -1832,7 +1877,7 @@ async function versionsSet(buildNumber, opts, deps) {
1832
1877
  console.log(JSON.stringify(version));
1833
1878
  } else {
1834
1879
  console.log(
1835
- chalk14.green(`\u2713 ${version.platform} build number set to ${version.build_number}`) + chalk14.dim(
1880
+ chalk15.green(`\u2713 ${version.platform} build number set to ${version.build_number}`) + chalk15.dim(
1836
1881
  " \u2014 the next autoIncrement production build gets " + (version.build_number + 1)
1837
1882
  )
1838
1883
  );
@@ -1852,18 +1897,18 @@ function formatVersionsHuman(versions2) {
1852
1897
  const widths = HEADERS2.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
1853
1898
  const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
1854
1899
  const join3 = (cells) => cells.join(" ").trimEnd();
1855
- const lines = [chalk14.bold(join3(padCells(HEADERS2)))];
1900
+ const lines = [chalk15.bold(join3(padCells(HEADERS2)))];
1856
1901
  for (const row of rows) lines.push(join3(padCells(row)));
1857
1902
  return lines.join("\n") + "\n";
1858
1903
  }
1859
1904
  function printError3(opts, err) {
1860
1905
  const message = err instanceof Error ? err.message : String(err);
1861
1906
  if (opts.json) console.log(JSON.stringify({ event: "error", message }));
1862
- console.error(chalk14.red(`error: ${message}`));
1907
+ console.error(chalk15.red(`error: ${message}`));
1863
1908
  }
1864
1909
 
1865
1910
  // src/commands/whoami.ts
1866
- import chalk15 from "chalk";
1911
+ import chalk16 from "chalk";
1867
1912
  function defaultDeps4(opts) {
1868
1913
  const { token, baseUrl } = resolveAuth(opts);
1869
1914
  const client = new ApiClient(baseUrl, token);
@@ -1885,27 +1930,27 @@ async function whoAmI(opts, deps) {
1885
1930
  process.exitCode = 0;
1886
1931
  return;
1887
1932
  }
1888
- console.log(`${chalk15.bold("Logged in as")} ${me.email}`);
1933
+ console.log(`${chalk16.bold("Logged in as")} ${me.email}`);
1889
1934
  if (me.memberships.length === 1) {
1890
- console.log(`${chalk15.bold("Organization")} ${me.memberships[0].organization_name}`);
1935
+ console.log(`${chalk16.bold("Organization")} ${me.memberships[0].organization_name}`);
1891
1936
  } else if (me.memberships.length > 1) {
1892
- console.log(chalk15.bold("Organizations"));
1937
+ console.log(chalk16.bold("Organizations"));
1893
1938
  for (const m of me.memberships) {
1894
1939
  console.log(` ${m.organization_name} (${m.role})`);
1895
1940
  }
1896
1941
  }
1897
- console.log(`${chalk15.bold("Member role")} ${me.role ?? "\u2014"}`);
1898
- console.log(`${chalk15.bold("Token role")} ${me.token_role ?? "\u2014 (session auth)"}`);
1899
- console.log(`${chalk15.bold("Effective role")} ${effective ?? "\u2014"}`);
1942
+ console.log(`${chalk16.bold("Member role")} ${me.role ?? "\u2014"}`);
1943
+ console.log(`${chalk16.bold("Token role")} ${me.token_role ?? "\u2014 (session auth)"}`);
1944
+ console.log(`${chalk16.bold("Effective role")} ${effective ?? "\u2014"}`);
1900
1945
  if (me.is_superadmin) {
1901
- console.log(chalk15.dim("Superadmin (platform-wide; does not raise the org role above)"));
1946
+ console.log(chalk16.dim("Superadmin (platform-wide; does not raise the org role above)"));
1902
1947
  }
1903
1948
  if (me.role === "admin" && me.token_role && me.token_role !== "admin") {
1904
1949
  console.log(
1905
- chalk15.yellow(
1950
+ chalk16.yellow(
1906
1951
  `
1907
- Your account is admin, but this CLI token is ${me.token_role}. The effective role is the lower of the two, so admin-only commands (such as ${chalk15.bold("vibeview env set")}) will fail with a permissions error.
1908
- Create an admin API token in the dashboard and use ${chalk15.bold("--token")} or ${chalk15.bold("VIBEVIEW_API_TOKEN")}.`
1952
+ Your account is admin, but this CLI token is ${me.token_role}. The effective role is the lower of the two, so admin-only commands (such as ${chalk16.bold("vibeview env set")}) will fail with a permissions error.
1953
+ Create an admin API token in the dashboard and use ${chalk16.bold("--token")} or ${chalk16.bold("VIBEVIEW_API_TOKEN")}.`
1909
1954
  )
1910
1955
  );
1911
1956
  }
@@ -1915,7 +1960,7 @@ Create an admin API token in the dashboard and use ${chalk15.bold("--token")} or
1915
1960
  if (opts.json) {
1916
1961
  console.log(JSON.stringify({ event: "error", message }));
1917
1962
  }
1918
- console.error(chalk15.red(`error: ${message}`));
1963
+ console.error(chalk16.red(`error: ${message}`));
1919
1964
  process.exitCode = 1;
1920
1965
  }
1921
1966
  }
@@ -1923,7 +1968,7 @@ Create an admin API token in the dashboard and use ${chalk15.bold("--token")} or
1923
1968
  // src/commands/env.ts
1924
1969
  import { chmod, open as open2, readFile as readFileAsync } from "fs/promises";
1925
1970
  import { basename } from "path";
1926
- import chalk16 from "chalk";
1971
+ import chalk17 from "chalk";
1927
1972
  import { password } from "@inquirer/prompts";
1928
1973
 
1929
1974
  // src/lib/gitignore.ts
@@ -1979,7 +2024,7 @@ function defaultDeps5(client) {
1979
2024
  isIgnored: (path) => isGitIgnored(path),
1980
2025
  writeFile: writeSecureFile,
1981
2026
  readFile: (path) => readFileAsync(path),
1982
- warn: (message) => console.error(chalk16.yellow(`warning: ${message}`))
2027
+ warn: (message) => console.error(chalk17.yellow(`warning: ${message}`))
1983
2028
  };
1984
2029
  }
1985
2030
  async function envList(opts, deps) {
@@ -2011,7 +2056,7 @@ function formatEnvVarsHuman(variables) {
2011
2056
  const widths = HEADERS3.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
2012
2057
  const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
2013
2058
  const join3 = (cells) => cells.join(" ").trimEnd();
2014
- const lines = [chalk16.bold(join3(padCells(HEADERS3)))];
2059
+ const lines = [chalk17.bold(join3(padCells(HEADERS3)))];
2015
2060
  for (const row of rows) lines.push(join3(padCells(row)));
2016
2061
  return lines.join("\n") + "\n";
2017
2062
  }
@@ -2061,7 +2106,7 @@ function printSetResult(opts, result) {
2061
2106
  if (opts.json) {
2062
2107
  console.log(JSON.stringify(result));
2063
2108
  } else {
2064
- console.log(chalk16.green(`\u2713 set ${result.name} (${result.visibility})`));
2109
+ console.log(chalk17.green(`\u2713 set ${result.name} (${result.visibility})`));
2065
2110
  }
2066
2111
  }
2067
2112
  async function envRm(name, opts, deps) {
@@ -2071,7 +2116,7 @@ async function envRm(name, opts, deps) {
2071
2116
  if (opts.json) {
2072
2117
  console.log(JSON.stringify({ event: "deleted", name }));
2073
2118
  } else {
2074
- console.log(chalk16.green(`\u2713 removed ${name}`));
2119
+ console.log(chalk17.green(`\u2713 removed ${name}`));
2075
2120
  }
2076
2121
  process.exitCode = 0;
2077
2122
  } catch (err) {
@@ -2103,7 +2148,7 @@ async function envPull(opts, deps) {
2103
2148
  if (opts.json) {
2104
2149
  console.log(JSON.stringify({ event: "pulled", path: outPath, count: variables.length }));
2105
2150
  } else {
2106
- console.log(chalk16.green(`\u2713 wrote ${variables.length} variable(s) to ${outPath}`));
2151
+ console.log(chalk17.green(`\u2713 wrote ${variables.length} variable(s) to ${outPath}`));
2107
2152
  }
2108
2153
  process.exitCode = 0;
2109
2154
  } catch (err) {
@@ -2142,7 +2187,7 @@ function escapeEnvValue(value) {
2142
2187
  function printError4(opts, err) {
2143
2188
  const message = err instanceof Error ? err.message : String(err);
2144
2189
  if (opts.json) console.log(JSON.stringify({ event: "error", message }));
2145
- console.error(chalk16.red(`error: ${message}`));
2190
+ console.error(chalk17.red(`error: ${message}`));
2146
2191
  }
2147
2192
 
2148
2193
  // src/commands/credentials.ts
@@ -2154,7 +2199,7 @@ import {
2154
2199
  } from "fs/promises";
2155
2200
  import { mkdirSync } from "fs";
2156
2201
  import { basename as basename2, extname, join } from "path";
2157
- import chalk17 from "chalk";
2202
+ import chalk18 from "chalk";
2158
2203
  import { input, password as password2 } from "@inquirer/prompts";
2159
2204
  function clientFor2(opts) {
2160
2205
  const { token, baseUrl } = resolveAuth(opts);
@@ -2227,7 +2272,7 @@ function defaultDeps6(client) {
2227
2272
  mkdir: (dir) => mkdirSync(dir, { recursive: true }),
2228
2273
  writeFile: (path, data, mode) => writeFileAsync(path, data, mode !== void 0 ? { mode } : void 0),
2229
2274
  chmod: (path, mode) => chmodAsync(path, mode),
2230
- warn: (message) => console.error(chalk17.yellow(`warning: ${message}`))
2275
+ warn: (message) => console.error(chalk18.yellow(`warning: ${message}`))
2231
2276
  };
2232
2277
  }
2233
2278
  var ANDROID_KIND = "android_keystore";
@@ -2296,7 +2341,7 @@ async function credentialsAdd(opts, deps) {
2296
2341
  if (opts.json) {
2297
2342
  console.log(JSON.stringify(result));
2298
2343
  } else {
2299
- console.log(chalk17.green(`\u2713 added ${result.name} (${result.platform})`));
2344
+ console.log(chalk18.green(`\u2713 added ${result.name} (${result.platform})`));
2300
2345
  }
2301
2346
  process.exitCode = 0;
2302
2347
  } catch (err) {
@@ -2354,20 +2399,20 @@ async function credentialsGenerate(opts, deps) {
2354
2399
  }
2355
2400
  if (opts.json) {
2356
2401
  } else {
2357
- console.log(chalk17.green(`\u2713 generated ${result.name} (android)`));
2402
+ console.log(chalk18.green(`\u2713 generated ${result.name} (android)`));
2358
2403
  console.log("");
2359
- console.log(` Keystore saved to: ${chalk17.bold(savedTo)}`);
2360
- console.log(` Key alias: ${chalk17.bold(result.key_alias)}`);
2361
- console.log(` Store password: ${chalk17.bold(result.store_password)}`);
2362
- console.log(` Key password: ${chalk17.bold(result.key_password)}`);
2404
+ console.log(` Keystore saved to: ${chalk18.bold(savedTo)}`);
2405
+ console.log(` Key alias: ${chalk18.bold(result.key_alias)}`);
2406
+ console.log(` Store password: ${chalk18.bold(result.store_password)}`);
2407
+ console.log(` Key password: ${chalk18.bold(result.key_password)}`);
2363
2408
  console.log("");
2364
2409
  console.log(
2365
- chalk17.yellow(
2410
+ chalk18.yellow(
2366
2411
  " These are shown ONCE \u2014 VibeView cannot recover the keystore file or its passwords. Back them up somewhere safe."
2367
2412
  )
2368
2413
  );
2369
2414
  console.log(
2370
- chalk17.yellow(
2415
+ chalk18.yellow(
2371
2416
  " The credential is already uploaded and active for android and androidtv production builds."
2372
2417
  )
2373
2418
  );
@@ -2393,19 +2438,19 @@ async function generateAppleCredential(platform, opts, d) {
2393
2438
  if (opts.json) {
2394
2439
  console.log(JSON.stringify(result));
2395
2440
  } else {
2396
- console.log(chalk17.green(`\u2713 generated ${result.name} (${result.platform})`));
2441
+ console.log(chalk18.green(`\u2713 generated ${result.name} (${result.platform})`));
2397
2442
  console.log("");
2398
- console.log(` Scope: ${chalk17.bold(result.app_public_id ?? "org-wide")}`);
2443
+ console.log(` Scope: ${chalk18.bold(result.app_public_id ?? "org-wide")}`);
2399
2444
  const expires = formatExpires(result);
2400
2445
  if (expires) {
2401
- console.log(` Expires: ${chalk17.bold(expires)}`);
2446
+ console.log(` Expires: ${chalk18.bold(expires)}`);
2402
2447
  }
2403
2448
  console.log("");
2404
2449
  console.log(
2405
2450
  ` The certificate and provisioning profile are stored with VibeView and already active for ${result.platform} production builds.`
2406
2451
  );
2407
2452
  console.log(
2408
- ` Run ${chalk17.bold(`vibeview credentials download ${result.name}`)} if you need the files locally.`
2453
+ ` Run ${chalk18.bold(`vibeview credentials download ${result.name}`)} if you need the files locally.`
2409
2454
  );
2410
2455
  }
2411
2456
  process.exitCode = 0;
@@ -2471,17 +2516,17 @@ async function credentialsDownload(name, opts, deps) {
2471
2516
  await d.chmod(path, 384);
2472
2517
  }
2473
2518
  if (!opts.json) {
2474
- console.log(chalk17.green(`\u2713 downloaded ${material.name} (${material.platform})`));
2519
+ console.log(chalk18.green(`\u2713 downloaded ${material.name} (${material.platform})`));
2475
2520
  console.log("");
2476
- console.log(` Saved to: ${chalk17.bold(savedTo)}`);
2521
+ console.log(` Saved to: ${chalk18.bold(savedTo)}`);
2477
2522
  if (profileSavedTo !== null) {
2478
- console.log(` Profile: ${chalk17.bold(profileSavedTo)}`);
2523
+ console.log(` Profile: ${chalk18.bold(profileSavedTo)}`);
2479
2524
  }
2480
2525
  for (const [key, value] of Object.entries(material.passwords)) {
2481
- console.log(` ${key}: ${chalk17.bold(value)}`);
2526
+ console.log(` ${key}: ${chalk18.bold(value)}`);
2482
2527
  }
2483
2528
  console.log("");
2484
- console.log(chalk17.yellow(" This download is recorded in your organization's audit log."));
2529
+ console.log(chalk18.yellow(" This download is recorded in your organization's audit log."));
2485
2530
  }
2486
2531
  process.exitCode = 0;
2487
2532
  } catch (err) {
@@ -2512,13 +2557,13 @@ async function ascKeyAdd(opts, deps) {
2512
2557
  if (opts.json) {
2513
2558
  console.log(JSON.stringify(result));
2514
2559
  } else {
2515
- console.log(chalk17.green(`\u2713 added App Store Connect key ${result.name}`));
2560
+ console.log(chalk18.green(`\u2713 added App Store Connect key ${result.name}`));
2516
2561
  console.log("");
2517
- console.log(` Key ID: ${chalk17.bold(result.key_id)}`);
2518
- console.log(` Issuer ID: ${chalk17.bold(result.issuer_id)}`);
2562
+ console.log(` Key ID: ${chalk18.bold(result.key_id)}`);
2563
+ console.log(` Issuer ID: ${chalk18.bold(result.issuer_id)}`);
2519
2564
  console.log("");
2520
2565
  console.log(
2521
- chalk17.yellow(
2566
+ chalk18.yellow(
2522
2567
  " The .p8 is stored encrypted and will never be shown again \u2014 keep your own copy somewhere safe."
2523
2568
  )
2524
2569
  );
@@ -2553,7 +2598,7 @@ function formatAscKeysHuman(keys) {
2553
2598
  const widths = HEADERS3.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
2554
2599
  const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
2555
2600
  const join3 = (cells) => cells.join(" ").trimEnd();
2556
- const lines = [chalk17.bold(join3(padCells(HEADERS3)))];
2601
+ const lines = [chalk18.bold(join3(padCells(HEADERS3)))];
2557
2602
  for (const row of rows) lines.push(join3(padCells(row)));
2558
2603
  return lines.join("\n") + "\n";
2559
2604
  }
@@ -2564,7 +2609,7 @@ async function ascKeyRm(name, opts, deps) {
2564
2609
  if (opts.json) {
2565
2610
  console.log(JSON.stringify({ event: "deleted", name }));
2566
2611
  } else {
2567
- console.log(chalk17.green(`\u2713 removed App Store Connect key ${name}`));
2612
+ console.log(chalk18.green(`\u2713 removed App Store Connect key ${name}`));
2568
2613
  }
2569
2614
  process.exitCode = 0;
2570
2615
  } catch (err) {
@@ -2589,12 +2634,12 @@ async function playKeyAdd(opts, deps) {
2589
2634
  if (opts.json) {
2590
2635
  console.log(JSON.stringify(result));
2591
2636
  } else {
2592
- console.log(chalk17.green(`\u2713 added Google Play key ${result.name}`));
2637
+ console.log(chalk18.green(`\u2713 added Google Play key ${result.name}`));
2593
2638
  console.log("");
2594
- console.log(` Service account: ${chalk17.bold(result.client_email)}`);
2639
+ console.log(` Service account: ${chalk18.bold(result.client_email)}`);
2595
2640
  console.log("");
2596
2641
  console.log(
2597
- chalk17.yellow(
2642
+ chalk18.yellow(
2598
2643
  " The JSON key is stored encrypted and will never be shown again \u2014 keep your own copy somewhere safe."
2599
2644
  )
2600
2645
  );
@@ -2629,7 +2674,7 @@ function formatPlayKeysHuman(keys) {
2629
2674
  const widths = HEADERS3.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
2630
2675
  const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
2631
2676
  const join3 = (cells) => cells.join(" ").trimEnd();
2632
- const lines = [chalk17.bold(join3(padCells(HEADERS3)))];
2677
+ const lines = [chalk18.bold(join3(padCells(HEADERS3)))];
2633
2678
  for (const row of rows) lines.push(join3(padCells(row)));
2634
2679
  return lines.join("\n") + "\n";
2635
2680
  }
@@ -2640,7 +2685,7 @@ async function playKeyRm(name, opts, deps) {
2640
2685
  if (opts.json) {
2641
2686
  console.log(JSON.stringify({ event: "deleted", name }));
2642
2687
  } else {
2643
- console.log(chalk17.green(`\u2713 removed Google Play key ${name}`));
2688
+ console.log(chalk18.green(`\u2713 removed Google Play key ${name}`));
2644
2689
  }
2645
2690
  process.exitCode = 0;
2646
2691
  } catch (err) {
@@ -2709,7 +2754,7 @@ function formatCredentialsHuman(credentials2) {
2709
2754
  const widths = HEADERS3.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
2710
2755
  const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
2711
2756
  const join3 = (cells) => cells.join(" ").trimEnd();
2712
- const lines = [chalk17.bold(join3(padCells(HEADERS3)))];
2757
+ const lines = [chalk18.bold(join3(padCells(HEADERS3)))];
2713
2758
  for (const row of rows) lines.push(join3(padCells(row)));
2714
2759
  return lines.join("\n") + "\n";
2715
2760
  }
@@ -2720,7 +2765,7 @@ async function credentialsRm(name, opts, deps) {
2720
2765
  if (opts.json) {
2721
2766
  console.log(JSON.stringify({ event: "deleted", name }));
2722
2767
  } else {
2723
- console.log(chalk17.green(`\u2713 removed ${name}`));
2768
+ console.log(chalk18.green(`\u2713 removed ${name}`));
2724
2769
  }
2725
2770
  process.exitCode = 0;
2726
2771
  } catch (err) {
@@ -2732,16 +2777,16 @@ function printKeystoreWriteFailure(opts, err, result) {
2732
2777
  const message = err instanceof Error ? err.message : String(err);
2733
2778
  if (opts.json) {
2734
2779
  console.log(JSON.stringify({ event: "error", message }));
2735
- console.error(chalk17.red(`error: ${message}`));
2780
+ console.error(chalk18.red(`error: ${message}`));
2736
2781
  return;
2737
2782
  }
2738
- console.error(chalk17.red(`error: ${message}`));
2783
+ console.error(chalk18.red(`error: ${message}`));
2739
2784
  console.error(
2740
- chalk17.red(
2785
+ chalk18.red(
2741
2786
  `The keystore could NOT be saved locally, but the credential "${result.name}" WAS created server-side and its key material can never be re-fetched.`
2742
2787
  )
2743
2788
  );
2744
- console.error(chalk17.red("Recover it NOW from the values below \u2014 they will not be shown again:"));
2789
+ console.error(chalk18.red("Recover it NOW from the values below \u2014 they will not be shown again:"));
2745
2790
  console.error("");
2746
2791
  console.error(` Key alias: ${result.key_alias}`);
2747
2792
  console.error(` Store password: ${result.store_password}`);
@@ -2755,11 +2800,11 @@ function printKeystoreWriteFailure(opts, err, result) {
2755
2800
  function printError5(opts, err) {
2756
2801
  const message = err instanceof Error ? err.message : String(err);
2757
2802
  if (opts.json) console.log(JSON.stringify({ event: "error", message }));
2758
- console.error(chalk17.red(`error: ${message}`));
2803
+ console.error(chalk18.red(`error: ${message}`));
2759
2804
  }
2760
2805
 
2761
2806
  // src/commands/webhooks.ts
2762
- import chalk18 from "chalk";
2807
+ import chalk19 from "chalk";
2763
2808
  function clientFor3(opts) {
2764
2809
  const { token, baseUrl } = resolveAuth(opts);
2765
2810
  return new ApiClient(baseUrl, token);
@@ -2790,14 +2835,14 @@ async function webhooksAdd(opts, deps) {
2790
2835
  if (opts.json) {
2791
2836
  console.log(JSON.stringify(result));
2792
2837
  } else {
2793
- console.log(chalk18.green(`\u2713 added ${result.id} ${result.url}`));
2838
+ console.log(chalk19.green(`\u2713 added ${result.id} ${result.url}`));
2794
2839
  console.log("");
2795
- console.log(` Signing secret: ${chalk18.bold(result.secret)}`);
2840
+ console.log(` Signing secret: ${chalk19.bold(result.secret)}`);
2796
2841
  if (opts.secret !== void 0) {
2797
- console.log(chalk18.yellow(" (This is the secret you provided.)"));
2842
+ console.log(chalk19.yellow(" (This is the secret you provided.)"));
2798
2843
  }
2799
2844
  console.log(
2800
- chalk18.yellow(" This secret is shown ONCE and cannot be retrieved again \u2014 store it now.")
2845
+ chalk19.yellow(" This secret is shown ONCE and cannot be retrieved again \u2014 store it now.")
2801
2846
  );
2802
2847
  }
2803
2848
  process.exitCode = 0;
@@ -2828,7 +2873,7 @@ function formatWebhooksHuman(webhooks2) {
2828
2873
  const widths = HEADERS3.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
2829
2874
  const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
2830
2875
  const join3 = (cells) => cells.join(" ").trimEnd();
2831
- const lines = [chalk18.bold(join3(padCells(HEADERS3)))];
2876
+ const lines = [chalk19.bold(join3(padCells(HEADERS3)))];
2832
2877
  for (const row of rows) lines.push(join3(padCells(row)));
2833
2878
  return lines.join("\n") + "\n";
2834
2879
  }
@@ -2854,7 +2899,7 @@ async function webhooksRm(id, opts, deps) {
2854
2899
  if (opts.json) {
2855
2900
  console.log(JSON.stringify({ event: "deleted", id }));
2856
2901
  } else {
2857
- console.log(chalk18.green(`\u2713 removed ${id}`));
2902
+ console.log(chalk19.green(`\u2713 removed ${id}`));
2858
2903
  }
2859
2904
  process.exitCode = 0;
2860
2905
  } catch (err) {
@@ -2887,13 +2932,13 @@ async function webhooksTest(id, opts, deps) {
2887
2932
  if (opts.json) console.log(JSON.stringify(ld));
2888
2933
  if (ld.status === "succeeded") {
2889
2934
  if (!opts.json) {
2890
- console.log(chalk18.green(`\u2713 Delivered (HTTP ${ld.response_code})`));
2935
+ console.log(chalk19.green(`\u2713 Delivered (HTTP ${ld.response_code})`));
2891
2936
  }
2892
2937
  process.exitCode = 0;
2893
2938
  } else {
2894
2939
  if (!opts.json) {
2895
2940
  const code = ld.response_code != null ? `HTTP ${ld.response_code}` : "no response";
2896
- console.error(chalk18.red(`\u2717 Failed (${code}) at ${ld.at}`));
2941
+ console.error(chalk19.red(`\u2717 Failed (${code}) at ${ld.at}`));
2897
2942
  }
2898
2943
  process.exitCode = 1;
2899
2944
  }
@@ -2902,7 +2947,7 @@ async function webhooksTest(id, opts, deps) {
2902
2947
  }
2903
2948
  if (opts.json) console.log(JSON.stringify({ status: "pending" }));
2904
2949
  console.error(
2905
- chalk18.yellow(
2950
+ chalk19.yellow(
2906
2951
  `Delivery still pending after ${TEST_POLL_BUDGET_MS / 1e3}s \u2014 check \`vibeview webhooks list\` later for the result.`
2907
2952
  )
2908
2953
  );
@@ -2915,7 +2960,7 @@ async function webhooksTest(id, opts, deps) {
2915
2960
  function printError6(opts, err) {
2916
2961
  const message = err instanceof Error ? err.message : String(err);
2917
2962
  if (opts.json) console.log(JSON.stringify({ event: "error", message }));
2918
- console.error(chalk18.red(`error: ${message}`));
2963
+ console.error(chalk19.red(`error: ${message}`));
2919
2964
  }
2920
2965
 
2921
2966
  // src/commands/list-devices.ts
@@ -3015,7 +3060,7 @@ import { dirname, join as join2 } from "path";
3015
3060
  import { fileURLToPath } from "url";
3016
3061
  import { spawnSync } from "child_process";
3017
3062
  import { confirm } from "@inquirer/prompts";
3018
- import chalk19 from "chalk";
3063
+ import chalk20 from "chalk";
3019
3064
  function resolveShippedSkillPath(fromDir) {
3020
3065
  for (const rel of ["..", join2("..", "..")]) {
3021
3066
  const candidate = join2(fromDir, rel, "skills", "vibeview-agent", "SKILL.md");
@@ -3043,23 +3088,23 @@ function registerMcp() {
3043
3088
  encoding: "utf8"
3044
3089
  });
3045
3090
  if (res.status === 0) {
3046
- console.log(chalk19.green("\u2713 MCP server registered with Claude Code (claude mcp add vibeview)"));
3091
+ console.log(chalk20.green("\u2713 MCP server registered with Claude Code (claude mcp add vibeview)"));
3047
3092
  return;
3048
3093
  }
3049
3094
  const out = `${res.stderr ?? ""}${res.stdout ?? ""}`;
3050
3095
  if (out.includes("already exists")) {
3051
- console.log(chalk19.dim("MCP server already registered with Claude Code \u2014 nothing to do"));
3096
+ console.log(chalk20.dim("MCP server already registered with Claude Code \u2014 nothing to do"));
3052
3097
  return;
3053
3098
  }
3054
- console.error(chalk19.yellow(`\u26A0 could not register the MCP server: ${out.trim() || res.error}`));
3055
- console.error(chalk19.yellow(" register it manually: claude mcp add vibeview -- vibeview mcp"));
3099
+ console.error(chalk20.yellow(`\u26A0 could not register the MCP server: ${out.trim() || res.error}`));
3100
+ console.error(chalk20.yellow(" register it manually: claude mcp add vibeview -- vibeview mcp"));
3056
3101
  }
3057
3102
  async function agentSetup(opts) {
3058
3103
  const moduleDir = dirname(fileURLToPath(import.meta.url));
3059
3104
  const shipped = resolveShippedSkillPath(moduleDir);
3060
3105
  if (!shipped) {
3061
3106
  console.error(
3062
- chalk19.red(
3107
+ chalk20.red(
3063
3108
  "error: could not find the packaged skill (skills/vibeview-agent/SKILL.md) \u2014 is this a complete install of the vibeview package?"
3064
3109
  )
3065
3110
  );
@@ -3068,9 +3113,9 @@ async function agentSetup(opts) {
3068
3113
  }
3069
3114
  const targetDir = skillInstallDir(opts.project ?? false, process.cwd(), homedir());
3070
3115
  const result = installSkill(shipped, targetDir);
3071
- console.log(chalk19.green(`\u2713 vibeview-agent skill ${result}: ${join2(targetDir, "SKILL.md")}`));
3116
+ console.log(chalk20.green(`\u2713 vibeview-agent skill ${result}: ${join2(targetDir, "SKILL.md")}`));
3072
3117
  console.log(
3073
- chalk19.dim(
3118
+ chalk20.dim(
3074
3119
  opts.project ? " Claude Code discovers it automatically for anyone working in this project." : " Claude Code discovers it automatically in every project on this machine."
3075
3120
  )
3076
3121
  );
@@ -3078,7 +3123,7 @@ async function agentSetup(opts) {
3078
3123
  if (!claudeCliAvailable()) {
3079
3124
  if (opts.mcp === true) {
3080
3125
  console.error(
3081
- chalk19.yellow(
3126
+ chalk20.yellow(
3082
3127
  "\u26A0 claude CLI not found \u2014 install Claude Code, then run: claude mcp add vibeview -- vibeview mcp"
3083
3128
  )
3084
3129
  );
@@ -3092,7 +3137,7 @@ async function agentSetup(opts) {
3092
3137
  }
3093
3138
  console.log(
3094
3139
  `
3095
- Suggested line for your project's ${chalk19.bold("CLAUDE.md")} (or ${chalk19.bold(
3140
+ Suggested line for your project's ${chalk20.bold("CLAUDE.md")} (or ${chalk20.bold(
3096
3141
  "AGENTS.md"
3097
3142
  )} for other agents):
3098
3143
  `
@@ -3100,7 +3145,7 @@ Suggested line for your project's ${chalk19.bold("CLAUDE.md")} (or ${chalk19.bol
3100
3145
  console.log(` ${CLAUDE_MD_SUGGESTION}
3101
3146
  `);
3102
3147
  console.log(
3103
- chalk19.dim(
3148
+ chalk20.dim(
3104
3149
  "Other agents (Cursor, Copilot, Codex, ...): install the same skill from GitHub with\n npx skills add vibeview/skills\nor point them at the skill file above, or add the MCP server per client \u2014\nsee https://vibeview.io/docs/agent-control"
3105
3150
  )
3106
3151
  );
@@ -3222,6 +3267,9 @@ program.command("list-suites").description("List available test suites").option(
3222
3267
  []
3223
3268
  ).option("--match <mode>", "When multiple --tag flags: any (default) or all", "any").action((opts) => listSuites(opts));
3224
3269
  program.command("list-apps").description("List apps in your organization").option("--token <token>", "API token").option("--output <format>", "Output format: human, json", "human").action((opts) => listApps(opts));
3270
+ program.command("list-builds <app>").description(
3271
+ "List an app's uploaded builds, newest first: id, version, platform, debug/release, upload time and note (the ids `dev --build <id>` and `run-test --build` take)"
3272
+ ).option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--json", "Print the builds as a JSON array", false).action((app, opts) => listBuilds(app, opts));
3225
3273
  program.command("list-devices").description("List devices registered with the platform (filterable by type and category)").option("--token <token>", "API token").option("--output <format>", "Output format: human, json", "human").option("--type <type>", "Filter by device type: ios, android, roku").option("--category <category>", "Filter by category: phone, tablet, tv").option("--status <status>", "Filter by status: available, busy, offline").option(
3226
3274
  "--models",
3227
3275
  "List the device models you can start instead (the names `dev --model` takes)",
@@ -3250,8 +3298,8 @@ program.command("dev").description(
3250
3298
  "Run the dev session in the background; prints session info and exits (see `dev-status`, `dev-stop`, and `dev-reload` for Roku)",
3251
3299
  false
3252
3300
  ).option(
3253
- "--build",
3254
- "Build and upload first (uses platforms.<platform>.build from vibeview.json)",
3301
+ "--build [id]",
3302
+ "Bare: build and upload first (uses platforms.<platform>.build from vibeview.json). With an id: run that uploaded debug build instead of the newest one (see `vibeview list-builds <app>`)",
3255
3303
  false
3256
3304
  ).option(
3257
3305
  "--watch [seconds]",
@@ -3387,13 +3435,16 @@ webhooks.command("rm <id>").description("Remove a webhook endpoint (rotate the s
3387
3435
  webhooks.command("test <id>").description("Send a ping delivery and wait up to 60s for the result").option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--json", "Print the final delivery result as one JSON line", false).action((id, opts) => webhooksTest(id, opts));
3388
3436
  program.command("dev-status").description("Show the status of a `vibeview dev --detach` session in this project").option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--json", "Print as one JSON line", false).action((opts) => devStatus(opts));
3389
3437
  program.command("dev-stop").description("Stop a `vibeview dev --detach` session in this project").option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--json", "Print as one JSON line", false).action((opts) => devStop(opts));
3438
+ program.command("stop <session-id>").description(
3439
+ "Stop any session by id \u2014 e.g. one started from the API, the sandbox, or a collaboration link (`dev-stop` stops this project's detached dev session without an id)"
3440
+ ).option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--json", "Print the result as one JSON line", false).action((sessionId, opts) => stopCommand(sessionId, opts));
3390
3441
  program.command("dev-reload").description(
3391
3442
  "Roku only: re-package, upload and reload the channel into this project's `vibeview dev --detach` session (the `r` key of the foreground loop)"
3392
3443
  ).option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--json", "Print as one JSON line", false).action((opts) => devReload(opts));
3393
3444
  program.command("mcp").description(
3394
3445
  "Run an MCP stdio server exposing the agent verbs (tap, swipe, type, ...) plus dev_start/dev_stop/dev_reload as tools"
3395
3446
  ).option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").action(async (opts) => {
3396
- const { runMcpServer } = await import("./server-KIJCEN6T.js");
3447
+ const { runMcpServer } = await import("./server-6EUBLXKE.js");
3397
3448
  await runMcpServer(opts);
3398
3449
  });
3399
3450
  program.command("agent-setup").description(