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