vibeview 0.21.0 → 0.23.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
@@ -4,8 +4,12 @@ import {
4
4
  BUILD_TERMINAL_STATUSES,
5
5
  buildCommand,
6
6
  clearDevState,
7
+ createRokuPush,
7
8
  dev,
8
9
  fetchFinalSummary,
10
+ filterDeviceModels,
11
+ formatDeviceModelsHuman,
12
+ formatDevicesHuman,
9
13
  formatDuration,
10
14
  killOwnedMetro,
11
15
  loadDevConfig,
@@ -15,7 +19,7 @@ import {
15
19
  readDevState,
16
20
  readMetroState,
17
21
  runAgentVerb
18
- } from "./chunk-VVKF7GGL.js";
22
+ } from "./chunk-E2LZSLRF.js";
19
23
  import {
20
24
  ApiClient,
21
25
  EXIT_INFRA_ERROR,
@@ -28,7 +32,7 @@ import {
28
32
  resolveAuth,
29
33
  saveConfig,
30
34
  uploadApp
31
- } from "./chunk-KON2FOEC.js";
35
+ } from "./chunk-PSFXVUHG.js";
32
36
 
33
37
  // src/cli.ts
34
38
  import { createRequire } from "module";
@@ -1143,23 +1147,102 @@ async function devStop(opts) {
1143
1147
  process.exit(0);
1144
1148
  }
1145
1149
 
1150
+ // src/commands/dev-reload.ts
1151
+ import chalk10 from "chalk";
1152
+ function fail(opts, code, message, exit) {
1153
+ if (opts.json) {
1154
+ console.log(JSON.stringify({ reloaded: false, code, message }));
1155
+ } else {
1156
+ console.error(chalk10.red(`error: ${message}`));
1157
+ }
1158
+ process.exit(exit);
1159
+ }
1160
+ async function devReload(opts) {
1161
+ const cwd = process.cwd();
1162
+ const state = readDevState(cwd);
1163
+ if (!state) {
1164
+ fail(
1165
+ opts,
1166
+ "no_dev_session",
1167
+ "No dev session running (or it was not started with --detach).",
1168
+ 1
1169
+ );
1170
+ }
1171
+ if (state.platform !== "roku") {
1172
+ fail(
1173
+ opts,
1174
+ "not_roku",
1175
+ `dev-reload is for Roku channels; a ${state.platform} session hot-reloads through Metro on its own.`,
1176
+ 2
1177
+ );
1178
+ }
1179
+ let build;
1180
+ try {
1181
+ build = loadDevConfig(cwd)?.platforms?.roku?.build;
1182
+ } catch (err) {
1183
+ fail(
1184
+ opts,
1185
+ "config_required",
1186
+ `vibeview.json could not be read: ${err instanceof Error ? err.message : String(err)}`,
1187
+ 1
1188
+ );
1189
+ }
1190
+ if (!build) {
1191
+ fail(
1192
+ opts,
1193
+ "config_required",
1194
+ "no build config for roku \u2014 run `vibeview build --platform roku` once to set it up",
1195
+ 1
1196
+ );
1197
+ }
1198
+ const { token, baseUrl } = resolveAuth(opts);
1199
+ const api = new ApiClient(baseUrl, token);
1200
+ const push = createRokuPush({
1201
+ cwd,
1202
+ sessionId: state.session_id,
1203
+ build,
1204
+ token,
1205
+ baseUrl,
1206
+ api,
1207
+ // Build output and the "reloaded in" line are human output: stderr, so
1208
+ // stdout stays reserved for the --json result.
1209
+ log: (line) => console.error(chalk10.dim(line))
1210
+ });
1211
+ const started = Date.now();
1212
+ try {
1213
+ await push();
1214
+ } catch (err) {
1215
+ fail(opts, "reload_failed", err instanceof Error ? err.message : String(err), 1);
1216
+ }
1217
+ if (opts.json) {
1218
+ console.log(
1219
+ JSON.stringify({
1220
+ reloaded: true,
1221
+ session_id: state.session_id,
1222
+ seconds: (Date.now() - started) / 1e3
1223
+ })
1224
+ );
1225
+ }
1226
+ process.exit(0);
1227
+ }
1228
+
1146
1229
  // src/commands/builds.ts
1147
- import chalk11 from "chalk";
1230
+ import chalk12 from "chalk";
1148
1231
 
1149
1232
  // src/lib/formatters/builds.ts
1150
- import chalk10 from "chalk";
1233
+ import chalk11 from "chalk";
1151
1234
  var HEADERS = ["ID", "PLATFORM", "STATUS", "CREATED", "COST", "APP"];
1152
1235
  function formatCostCell(costMicro) {
1153
1236
  return costMicro > 0 ? `$${(costMicro / 1e6).toFixed(2)}` : "";
1154
1237
  }
1155
1238
  var STATUS_COLOR = {
1156
- succeeded: chalk10.green,
1157
- failed: chalk10.red,
1158
- timed_out: chalk10.red,
1159
- infra_failed: chalk10.red,
1160
- canceled: chalk10.dim,
1161
- running: chalk10.yellow,
1162
- queued: chalk10.dim
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
1163
1246
  };
1164
1247
  function formatBuildsHuman(builds2) {
1165
1248
  if (builds2.length === 0) {
@@ -1176,7 +1259,7 @@ function formatBuildsHuman(builds2) {
1176
1259
  const widths = HEADERS.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
1177
1260
  const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
1178
1261
  const join3 = (cells) => cells.join(" ").trimEnd();
1179
- const lines = [chalk10.bold(join3(padCells(HEADERS)))];
1262
+ const lines = [chalk11.bold(join3(padCells(HEADERS)))];
1180
1263
  for (const row of rows) {
1181
1264
  const padded = padCells(row);
1182
1265
  const colorFn = STATUS_COLOR[row[2]] ?? ((s) => s);
@@ -1235,7 +1318,7 @@ async function buildsLogs(id, opts, deps) {
1235
1318
  console.log(JSON.stringify(logs));
1236
1319
  } else {
1237
1320
  if (logs.content) process.stdout.write(logs.content);
1238
- console.error(chalk11.dim(`status: ${logs.status}`));
1321
+ console.error(chalk12.dim(`status: ${logs.status}`));
1239
1322
  }
1240
1323
  process.exitCode = 0;
1241
1324
  return;
@@ -1332,7 +1415,7 @@ async function buildsDownload(id, opts, deps) {
1332
1415
  if (opts.json) {
1333
1416
  console.log(JSON.stringify(result));
1334
1417
  } else {
1335
- console.log(chalk11.green(`\u2713 saved ${result.path}`));
1418
+ console.log(chalk12.green(`\u2713 saved ${result.path}`));
1336
1419
  }
1337
1420
  process.exitCode = 0;
1338
1421
  } catch (err) {
@@ -1349,7 +1432,7 @@ async function buildsCancel(id, opts, deps) {
1349
1432
  if (opts.json) {
1350
1433
  console.log(JSON.stringify(build));
1351
1434
  } else {
1352
- console.log(`${chalk11.cyan(build.id)} ${build.status}`);
1435
+ console.log(`${chalk12.cyan(build.id)} ${build.status}`);
1353
1436
  }
1354
1437
  process.exitCode = 0;
1355
1438
  } catch (err) {
@@ -1366,7 +1449,7 @@ async function buildsDeleteArtifact(id, opts, deps) {
1366
1449
  if (opts.json) {
1367
1450
  console.log(JSON.stringify(result));
1368
1451
  } else {
1369
- console.log(chalk11.green(`\u2713 deleted artifact for ${id}`));
1452
+ console.log(chalk12.green(`\u2713 deleted artifact for ${id}`));
1370
1453
  }
1371
1454
  process.exitCode = 0;
1372
1455
  } catch (err) {
@@ -1377,11 +1460,11 @@ async function buildsDeleteArtifact(id, opts, deps) {
1377
1460
  function printError(opts, err) {
1378
1461
  const message = err instanceof Error ? err.message : String(err);
1379
1462
  if (opts.json) console.log(JSON.stringify({ event: "error", message }));
1380
- console.error(chalk11.red(`error: ${message}`));
1463
+ console.error(chalk12.red(`error: ${message}`));
1381
1464
  }
1382
1465
 
1383
1466
  // src/commands/submit.ts
1384
- import chalk12 from "chalk";
1467
+ import chalk13 from "chalk";
1385
1468
  function defaultDeps2(client) {
1386
1469
  return {
1387
1470
  createSubmission: client.createSubmission.bind(client),
@@ -1404,7 +1487,7 @@ function submitConfigFor(cwd, platform) {
1404
1487
  cfg = loadDevConfig(cwd);
1405
1488
  } catch (err) {
1406
1489
  console.error(
1407
- chalk12.yellow(
1490
+ chalk13.yellow(
1408
1491
  `warning: vibeview.json could not be read \u2014 ignoring submit defaults (${err instanceof Error ? err.message : String(err)})`
1409
1492
  )
1410
1493
  );
@@ -1552,12 +1635,12 @@ async function submitCommand(opts, deps) {
1552
1635
  return;
1553
1636
  }
1554
1637
  if (!opts.json) {
1555
- console.error(` ${chalk12.cyan(sub.id)} ${chalk12.dim(`[${sub.platform}]`)}`);
1638
+ console.error(` ${chalk13.cyan(sub.id)} ${chalk13.dim(`[${sub.platform}]`)}`);
1556
1639
  }
1557
1640
  const final = await followSubmission(sub, opts, d);
1558
1641
  if (!opts.json && final.status === "succeeded" && (final.release_status ?? releaseStatus) === "draft") {
1559
1642
  console.error(
1560
- chalk12.dim("release status: draft \u2014 activate it from the Play Console when ready")
1643
+ chalk13.dim("release status: draft \u2014 activate it from the Play Console when ready")
1561
1644
  );
1562
1645
  }
1563
1646
  process.exitCode = final.status === "succeeded" ? 0 : 1;
@@ -1581,11 +1664,11 @@ async function checkStore(platform, androidFamily, ascKey2, opts, deps) {
1581
1664
  });
1582
1665
  emit(opts.json, { event: "preflight", ...result });
1583
1666
  if (!opts.json) {
1584
- const mark = result.ready ? chalk12.green("\u2713") : chalk12.red("\u2717");
1667
+ const mark = result.ready ? chalk13.green("\u2713") : chalk13.red("\u2717");
1585
1668
  console.error(`${mark} ${result.message}`);
1586
1669
  if (result.ready) {
1587
1670
  console.error(
1588
- chalk12.dim(
1671
+ chalk13.dim(
1589
1672
  ` next: vibeview build --cloud --production --platform ${platform}, then vibeview submit --platform ${platform} --latest`
1590
1673
  )
1591
1674
  );
@@ -1634,14 +1717,14 @@ async function followSubmission(created, opts, deps) {
1634
1717
  if (opts.json) {
1635
1718
  emit(opts.json, { event: "status", id: created.id, status: "queued" });
1636
1719
  } else {
1637
- console.error(chalk12.dim("\u23F3 queued \u2014 waiting for a submission slot"));
1720
+ console.error(chalk13.dim("\u23F3 queued \u2014 waiting for a submission slot"));
1638
1721
  }
1639
1722
  } else if (log.status === "running" && !printedRunning) {
1640
1723
  printedRunning = true;
1641
1724
  if (opts.json) {
1642
1725
  emit(opts.json, { event: "status", id: created.id, status: "running" });
1643
1726
  } else {
1644
- console.error(`${chalk12.cyan("\u25B6")} submitting`);
1727
+ console.error(`${chalk13.cyan("\u25B6")} submitting`);
1645
1728
  }
1646
1729
  }
1647
1730
  if (log.content) {
@@ -1683,7 +1766,7 @@ The submission continues server-side \u2014 see its outcome on the Builds page i
1683
1766
  });
1684
1767
  if (!opts.json) {
1685
1768
  const ok = final.status === "succeeded";
1686
- const mark = ok ? chalk12.green("\u2713") : chalk12.red("\u2717");
1769
+ const mark = ok ? chalk13.green("\u2713") : chalk13.red("\u2717");
1687
1770
  const duration = formatDuration(final.started_at, final.finished_at);
1688
1771
  let line = `${mark} ${final.status}`;
1689
1772
  if (duration) line += ` (${duration})`;
@@ -1695,11 +1778,11 @@ The submission continues server-side \u2014 see its outcome on the Builds page i
1695
1778
  function printError2(opts, err) {
1696
1779
  const message = err instanceof Error ? err.message : String(err);
1697
1780
  if (opts.json) console.log(JSON.stringify({ event: "error", message }));
1698
- console.error(chalk12.red(`error: ${message}`));
1781
+ console.error(chalk13.red(`error: ${message}`));
1699
1782
  }
1700
1783
 
1701
1784
  // src/commands/versions.ts
1702
- import chalk13 from "chalk";
1785
+ import chalk14 from "chalk";
1703
1786
  function defaultDeps3(client) {
1704
1787
  return {
1705
1788
  listAppVersions: client.listAppVersions.bind(client),
@@ -1749,7 +1832,7 @@ async function versionsSet(buildNumber, opts, deps) {
1749
1832
  console.log(JSON.stringify(version));
1750
1833
  } else {
1751
1834
  console.log(
1752
- chalk13.green(`\u2713 ${version.platform} build number set to ${version.build_number}`) + chalk13.dim(
1835
+ chalk14.green(`\u2713 ${version.platform} build number set to ${version.build_number}`) + chalk14.dim(
1753
1836
  " \u2014 the next autoIncrement production build gets " + (version.build_number + 1)
1754
1837
  )
1755
1838
  );
@@ -1769,18 +1852,18 @@ function formatVersionsHuman(versions2) {
1769
1852
  const widths = HEADERS2.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
1770
1853
  const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
1771
1854
  const join3 = (cells) => cells.join(" ").trimEnd();
1772
- const lines = [chalk13.bold(join3(padCells(HEADERS2)))];
1855
+ const lines = [chalk14.bold(join3(padCells(HEADERS2)))];
1773
1856
  for (const row of rows) lines.push(join3(padCells(row)));
1774
1857
  return lines.join("\n") + "\n";
1775
1858
  }
1776
1859
  function printError3(opts, err) {
1777
1860
  const message = err instanceof Error ? err.message : String(err);
1778
1861
  if (opts.json) console.log(JSON.stringify({ event: "error", message }));
1779
- console.error(chalk13.red(`error: ${message}`));
1862
+ console.error(chalk14.red(`error: ${message}`));
1780
1863
  }
1781
1864
 
1782
1865
  // src/commands/whoami.ts
1783
- import chalk14 from "chalk";
1866
+ import chalk15 from "chalk";
1784
1867
  function defaultDeps4(opts) {
1785
1868
  const { token, baseUrl } = resolveAuth(opts);
1786
1869
  const client = new ApiClient(baseUrl, token);
@@ -1802,27 +1885,27 @@ async function whoAmI(opts, deps) {
1802
1885
  process.exitCode = 0;
1803
1886
  return;
1804
1887
  }
1805
- console.log(`${chalk14.bold("Logged in as")} ${me.email}`);
1888
+ console.log(`${chalk15.bold("Logged in as")} ${me.email}`);
1806
1889
  if (me.memberships.length === 1) {
1807
- console.log(`${chalk14.bold("Organization")} ${me.memberships[0].organization_name}`);
1890
+ console.log(`${chalk15.bold("Organization")} ${me.memberships[0].organization_name}`);
1808
1891
  } else if (me.memberships.length > 1) {
1809
- console.log(chalk14.bold("Organizations"));
1892
+ console.log(chalk15.bold("Organizations"));
1810
1893
  for (const m of me.memberships) {
1811
1894
  console.log(` ${m.organization_name} (${m.role})`);
1812
1895
  }
1813
1896
  }
1814
- console.log(`${chalk14.bold("Member role")} ${me.role ?? "\u2014"}`);
1815
- console.log(`${chalk14.bold("Token role")} ${me.token_role ?? "\u2014 (session auth)"}`);
1816
- console.log(`${chalk14.bold("Effective role")} ${effective ?? "\u2014"}`);
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"}`);
1817
1900
  if (me.is_superadmin) {
1818
- console.log(chalk14.dim("Superadmin (platform-wide; does not raise the org role above)"));
1901
+ console.log(chalk15.dim("Superadmin (platform-wide; does not raise the org role above)"));
1819
1902
  }
1820
1903
  if (me.role === "admin" && me.token_role && me.token_role !== "admin") {
1821
1904
  console.log(
1822
- chalk14.yellow(
1905
+ chalk15.yellow(
1823
1906
  `
1824
- 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 ${chalk14.bold("vibeview env set")}) will fail with a permissions error.
1825
- Create an admin API token in the dashboard and use ${chalk14.bold("--token")} or ${chalk14.bold("VIBEVIEW_API_TOKEN")}.`
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")}.`
1826
1909
  )
1827
1910
  );
1828
1911
  }
@@ -1832,7 +1915,7 @@ Create an admin API token in the dashboard and use ${chalk14.bold("--token")} or
1832
1915
  if (opts.json) {
1833
1916
  console.log(JSON.stringify({ event: "error", message }));
1834
1917
  }
1835
- console.error(chalk14.red(`error: ${message}`));
1918
+ console.error(chalk15.red(`error: ${message}`));
1836
1919
  process.exitCode = 1;
1837
1920
  }
1838
1921
  }
@@ -1840,7 +1923,7 @@ Create an admin API token in the dashboard and use ${chalk14.bold("--token")} or
1840
1923
  // src/commands/env.ts
1841
1924
  import { chmod, open as open2, readFile as readFileAsync } from "fs/promises";
1842
1925
  import { basename } from "path";
1843
- import chalk15 from "chalk";
1926
+ import chalk16 from "chalk";
1844
1927
  import { password } from "@inquirer/prompts";
1845
1928
 
1846
1929
  // src/lib/gitignore.ts
@@ -1896,7 +1979,7 @@ function defaultDeps5(client) {
1896
1979
  isIgnored: (path) => isGitIgnored(path),
1897
1980
  writeFile: writeSecureFile,
1898
1981
  readFile: (path) => readFileAsync(path),
1899
- warn: (message) => console.error(chalk15.yellow(`warning: ${message}`))
1982
+ warn: (message) => console.error(chalk16.yellow(`warning: ${message}`))
1900
1983
  };
1901
1984
  }
1902
1985
  async function envList(opts, deps) {
@@ -1918,17 +2001,17 @@ function formatEnvVarsHuman(variables) {
1918
2001
  if (variables.length === 0) {
1919
2002
  return "No build variables found.\n";
1920
2003
  }
1921
- const HEADERS4 = ["NAME", "VISIBILITY", "SCOPE", "HINT"];
2004
+ const HEADERS3 = ["NAME", "VISIBILITY", "SCOPE", "HINT"];
1922
2005
  const rows = variables.map((v) => [
1923
2006
  v.name,
1924
2007
  v.is_file ? `${v.visibility} (file)` : v.visibility,
1925
2008
  v.app_public_id ?? "org-wide",
1926
2009
  v.value_hint ?? ""
1927
2010
  ]);
1928
- const widths = HEADERS4.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
2011
+ const widths = HEADERS3.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
1929
2012
  const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
1930
2013
  const join3 = (cells) => cells.join(" ").trimEnd();
1931
- const lines = [chalk15.bold(join3(padCells(HEADERS4)))];
2014
+ const lines = [chalk16.bold(join3(padCells(HEADERS3)))];
1932
2015
  for (const row of rows) lines.push(join3(padCells(row)));
1933
2016
  return lines.join("\n") + "\n";
1934
2017
  }
@@ -1978,7 +2061,7 @@ function printSetResult(opts, result) {
1978
2061
  if (opts.json) {
1979
2062
  console.log(JSON.stringify(result));
1980
2063
  } else {
1981
- console.log(chalk15.green(`\u2713 set ${result.name} (${result.visibility})`));
2064
+ console.log(chalk16.green(`\u2713 set ${result.name} (${result.visibility})`));
1982
2065
  }
1983
2066
  }
1984
2067
  async function envRm(name, opts, deps) {
@@ -1988,7 +2071,7 @@ async function envRm(name, opts, deps) {
1988
2071
  if (opts.json) {
1989
2072
  console.log(JSON.stringify({ event: "deleted", name }));
1990
2073
  } else {
1991
- console.log(chalk15.green(`\u2713 removed ${name}`));
2074
+ console.log(chalk16.green(`\u2713 removed ${name}`));
1992
2075
  }
1993
2076
  process.exitCode = 0;
1994
2077
  } catch (err) {
@@ -2020,7 +2103,7 @@ async function envPull(opts, deps) {
2020
2103
  if (opts.json) {
2021
2104
  console.log(JSON.stringify({ event: "pulled", path: outPath, count: variables.length }));
2022
2105
  } else {
2023
- console.log(chalk15.green(`\u2713 wrote ${variables.length} variable(s) to ${outPath}`));
2106
+ console.log(chalk16.green(`\u2713 wrote ${variables.length} variable(s) to ${outPath}`));
2024
2107
  }
2025
2108
  process.exitCode = 0;
2026
2109
  } catch (err) {
@@ -2059,7 +2142,7 @@ function escapeEnvValue(value) {
2059
2142
  function printError4(opts, err) {
2060
2143
  const message = err instanceof Error ? err.message : String(err);
2061
2144
  if (opts.json) console.log(JSON.stringify({ event: "error", message }));
2062
- console.error(chalk15.red(`error: ${message}`));
2145
+ console.error(chalk16.red(`error: ${message}`));
2063
2146
  }
2064
2147
 
2065
2148
  // src/commands/credentials.ts
@@ -2071,7 +2154,7 @@ import {
2071
2154
  } from "fs/promises";
2072
2155
  import { mkdirSync } from "fs";
2073
2156
  import { basename as basename2, extname, join } from "path";
2074
- import chalk16 from "chalk";
2157
+ import chalk17 from "chalk";
2075
2158
  import { input, password as password2 } from "@inquirer/prompts";
2076
2159
  function clientFor2(opts) {
2077
2160
  const { token, baseUrl } = resolveAuth(opts);
@@ -2144,7 +2227,7 @@ function defaultDeps6(client) {
2144
2227
  mkdir: (dir) => mkdirSync(dir, { recursive: true }),
2145
2228
  writeFile: (path, data, mode) => writeFileAsync(path, data, mode !== void 0 ? { mode } : void 0),
2146
2229
  chmod: (path, mode) => chmodAsync(path, mode),
2147
- warn: (message) => console.error(chalk16.yellow(`warning: ${message}`))
2230
+ warn: (message) => console.error(chalk17.yellow(`warning: ${message}`))
2148
2231
  };
2149
2232
  }
2150
2233
  var ANDROID_KIND = "android_keystore";
@@ -2213,7 +2296,7 @@ async function credentialsAdd(opts, deps) {
2213
2296
  if (opts.json) {
2214
2297
  console.log(JSON.stringify(result));
2215
2298
  } else {
2216
- console.log(chalk16.green(`\u2713 added ${result.name} (${result.platform})`));
2299
+ console.log(chalk17.green(`\u2713 added ${result.name} (${result.platform})`));
2217
2300
  }
2218
2301
  process.exitCode = 0;
2219
2302
  } catch (err) {
@@ -2271,20 +2354,20 @@ async function credentialsGenerate(opts, deps) {
2271
2354
  }
2272
2355
  if (opts.json) {
2273
2356
  } else {
2274
- console.log(chalk16.green(`\u2713 generated ${result.name} (android)`));
2357
+ console.log(chalk17.green(`\u2713 generated ${result.name} (android)`));
2275
2358
  console.log("");
2276
- console.log(` Keystore saved to: ${chalk16.bold(savedTo)}`);
2277
- console.log(` Key alias: ${chalk16.bold(result.key_alias)}`);
2278
- console.log(` Store password: ${chalk16.bold(result.store_password)}`);
2279
- console.log(` Key password: ${chalk16.bold(result.key_password)}`);
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)}`);
2280
2363
  console.log("");
2281
2364
  console.log(
2282
- chalk16.yellow(
2365
+ chalk17.yellow(
2283
2366
  " These are shown ONCE \u2014 VibeView cannot recover the keystore file or its passwords. Back them up somewhere safe."
2284
2367
  )
2285
2368
  );
2286
2369
  console.log(
2287
- chalk16.yellow(
2370
+ chalk17.yellow(
2288
2371
  " The credential is already uploaded and active for android and androidtv production builds."
2289
2372
  )
2290
2373
  );
@@ -2310,19 +2393,19 @@ async function generateAppleCredential(platform, opts, d) {
2310
2393
  if (opts.json) {
2311
2394
  console.log(JSON.stringify(result));
2312
2395
  } else {
2313
- console.log(chalk16.green(`\u2713 generated ${result.name} (${result.platform})`));
2396
+ console.log(chalk17.green(`\u2713 generated ${result.name} (${result.platform})`));
2314
2397
  console.log("");
2315
- console.log(` Scope: ${chalk16.bold(result.app_public_id ?? "org-wide")}`);
2398
+ console.log(` Scope: ${chalk17.bold(result.app_public_id ?? "org-wide")}`);
2316
2399
  const expires = formatExpires(result);
2317
2400
  if (expires) {
2318
- console.log(` Expires: ${chalk16.bold(expires)}`);
2401
+ console.log(` Expires: ${chalk17.bold(expires)}`);
2319
2402
  }
2320
2403
  console.log("");
2321
2404
  console.log(
2322
2405
  ` The certificate and provisioning profile are stored with VibeView and already active for ${result.platform} production builds.`
2323
2406
  );
2324
2407
  console.log(
2325
- ` Run ${chalk16.bold(`vibeview credentials download ${result.name}`)} if you need the files locally.`
2408
+ ` Run ${chalk17.bold(`vibeview credentials download ${result.name}`)} if you need the files locally.`
2326
2409
  );
2327
2410
  }
2328
2411
  process.exitCode = 0;
@@ -2388,17 +2471,17 @@ async function credentialsDownload(name, opts, deps) {
2388
2471
  await d.chmod(path, 384);
2389
2472
  }
2390
2473
  if (!opts.json) {
2391
- console.log(chalk16.green(`\u2713 downloaded ${material.name} (${material.platform})`));
2474
+ console.log(chalk17.green(`\u2713 downloaded ${material.name} (${material.platform})`));
2392
2475
  console.log("");
2393
- console.log(` Saved to: ${chalk16.bold(savedTo)}`);
2476
+ console.log(` Saved to: ${chalk17.bold(savedTo)}`);
2394
2477
  if (profileSavedTo !== null) {
2395
- console.log(` Profile: ${chalk16.bold(profileSavedTo)}`);
2478
+ console.log(` Profile: ${chalk17.bold(profileSavedTo)}`);
2396
2479
  }
2397
2480
  for (const [key, value] of Object.entries(material.passwords)) {
2398
- console.log(` ${key}: ${chalk16.bold(value)}`);
2481
+ console.log(` ${key}: ${chalk17.bold(value)}`);
2399
2482
  }
2400
2483
  console.log("");
2401
- console.log(chalk16.yellow(" This download is recorded in your organization's audit log."));
2484
+ console.log(chalk17.yellow(" This download is recorded in your organization's audit log."));
2402
2485
  }
2403
2486
  process.exitCode = 0;
2404
2487
  } catch (err) {
@@ -2429,13 +2512,13 @@ async function ascKeyAdd(opts, deps) {
2429
2512
  if (opts.json) {
2430
2513
  console.log(JSON.stringify(result));
2431
2514
  } else {
2432
- console.log(chalk16.green(`\u2713 added App Store Connect key ${result.name}`));
2515
+ console.log(chalk17.green(`\u2713 added App Store Connect key ${result.name}`));
2433
2516
  console.log("");
2434
- console.log(` Key ID: ${chalk16.bold(result.key_id)}`);
2435
- console.log(` Issuer ID: ${chalk16.bold(result.issuer_id)}`);
2517
+ console.log(` Key ID: ${chalk17.bold(result.key_id)}`);
2518
+ console.log(` Issuer ID: ${chalk17.bold(result.issuer_id)}`);
2436
2519
  console.log("");
2437
2520
  console.log(
2438
- chalk16.yellow(
2521
+ chalk17.yellow(
2439
2522
  " The .p8 is stored encrypted and will never be shown again \u2014 keep your own copy somewhere safe."
2440
2523
  )
2441
2524
  );
@@ -2465,12 +2548,12 @@ function formatAscKeysHuman(keys) {
2465
2548
  if (keys.length === 0) {
2466
2549
  return "No App Store Connect keys found.\n";
2467
2550
  }
2468
- const HEADERS4 = ["NAME", "KEY ID", "ISSUER ID", "CREATED"];
2551
+ const HEADERS3 = ["NAME", "KEY ID", "ISSUER ID", "CREATED"];
2469
2552
  const rows = keys.map((k) => [k.name, k.key_id, k.issuer_id, k.created_at ?? ""]);
2470
- const widths = HEADERS4.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
2553
+ const widths = HEADERS3.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
2471
2554
  const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
2472
2555
  const join3 = (cells) => cells.join(" ").trimEnd();
2473
- const lines = [chalk16.bold(join3(padCells(HEADERS4)))];
2556
+ const lines = [chalk17.bold(join3(padCells(HEADERS3)))];
2474
2557
  for (const row of rows) lines.push(join3(padCells(row)));
2475
2558
  return lines.join("\n") + "\n";
2476
2559
  }
@@ -2481,7 +2564,7 @@ async function ascKeyRm(name, opts, deps) {
2481
2564
  if (opts.json) {
2482
2565
  console.log(JSON.stringify({ event: "deleted", name }));
2483
2566
  } else {
2484
- console.log(chalk16.green(`\u2713 removed App Store Connect key ${name}`));
2567
+ console.log(chalk17.green(`\u2713 removed App Store Connect key ${name}`));
2485
2568
  }
2486
2569
  process.exitCode = 0;
2487
2570
  } catch (err) {
@@ -2506,12 +2589,12 @@ async function playKeyAdd(opts, deps) {
2506
2589
  if (opts.json) {
2507
2590
  console.log(JSON.stringify(result));
2508
2591
  } else {
2509
- console.log(chalk16.green(`\u2713 added Google Play key ${result.name}`));
2592
+ console.log(chalk17.green(`\u2713 added Google Play key ${result.name}`));
2510
2593
  console.log("");
2511
- console.log(` Service account: ${chalk16.bold(result.client_email)}`);
2594
+ console.log(` Service account: ${chalk17.bold(result.client_email)}`);
2512
2595
  console.log("");
2513
2596
  console.log(
2514
- chalk16.yellow(
2597
+ chalk17.yellow(
2515
2598
  " The JSON key is stored encrypted and will never be shown again \u2014 keep your own copy somewhere safe."
2516
2599
  )
2517
2600
  );
@@ -2541,12 +2624,12 @@ function formatPlayKeysHuman(keys) {
2541
2624
  if (keys.length === 0) {
2542
2625
  return "No Google Play keys found.\n";
2543
2626
  }
2544
- const HEADERS4 = ["NAME", "CLIENT EMAIL", "CREATED"];
2627
+ const HEADERS3 = ["NAME", "CLIENT EMAIL", "CREATED"];
2545
2628
  const rows = keys.map((k) => [k.name, k.client_email, k.created_at ?? ""]);
2546
- const widths = HEADERS4.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
2629
+ const widths = HEADERS3.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
2547
2630
  const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
2548
2631
  const join3 = (cells) => cells.join(" ").trimEnd();
2549
- const lines = [chalk16.bold(join3(padCells(HEADERS4)))];
2632
+ const lines = [chalk17.bold(join3(padCells(HEADERS3)))];
2550
2633
  for (const row of rows) lines.push(join3(padCells(row)));
2551
2634
  return lines.join("\n") + "\n";
2552
2635
  }
@@ -2557,7 +2640,7 @@ async function playKeyRm(name, opts, deps) {
2557
2640
  if (opts.json) {
2558
2641
  console.log(JSON.stringify({ event: "deleted", name }));
2559
2642
  } else {
2560
- console.log(chalk16.green(`\u2713 removed Google Play key ${name}`));
2643
+ console.log(chalk17.green(`\u2713 removed Google Play key ${name}`));
2561
2644
  }
2562
2645
  process.exitCode = 0;
2563
2646
  } catch (err) {
@@ -2615,7 +2698,7 @@ function formatCredentialsHuman(credentials2) {
2615
2698
  if (credentials2.length === 0) {
2616
2699
  return "No signing credentials found.\n";
2617
2700
  }
2618
- const HEADERS4 = ["NAME", "PLATFORM", "SCOPE", "IDENTITY", "EXPIRES"];
2701
+ const HEADERS3 = ["NAME", "PLATFORM", "SCOPE", "IDENTITY", "EXPIRES"];
2619
2702
  const rows = credentials2.map((c) => [
2620
2703
  c.name,
2621
2704
  c.platform,
@@ -2623,10 +2706,10 @@ function formatCredentialsHuman(credentials2) {
2623
2706
  formatIdentity(c),
2624
2707
  formatExpires(c)
2625
2708
  ]);
2626
- const widths = HEADERS4.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
2709
+ const widths = HEADERS3.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
2627
2710
  const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
2628
2711
  const join3 = (cells) => cells.join(" ").trimEnd();
2629
- const lines = [chalk16.bold(join3(padCells(HEADERS4)))];
2712
+ const lines = [chalk17.bold(join3(padCells(HEADERS3)))];
2630
2713
  for (const row of rows) lines.push(join3(padCells(row)));
2631
2714
  return lines.join("\n") + "\n";
2632
2715
  }
@@ -2637,7 +2720,7 @@ async function credentialsRm(name, opts, deps) {
2637
2720
  if (opts.json) {
2638
2721
  console.log(JSON.stringify({ event: "deleted", name }));
2639
2722
  } else {
2640
- console.log(chalk16.green(`\u2713 removed ${name}`));
2723
+ console.log(chalk17.green(`\u2713 removed ${name}`));
2641
2724
  }
2642
2725
  process.exitCode = 0;
2643
2726
  } catch (err) {
@@ -2649,16 +2732,16 @@ function printKeystoreWriteFailure(opts, err, result) {
2649
2732
  const message = err instanceof Error ? err.message : String(err);
2650
2733
  if (opts.json) {
2651
2734
  console.log(JSON.stringify({ event: "error", message }));
2652
- console.error(chalk16.red(`error: ${message}`));
2735
+ console.error(chalk17.red(`error: ${message}`));
2653
2736
  return;
2654
2737
  }
2655
- console.error(chalk16.red(`error: ${message}`));
2738
+ console.error(chalk17.red(`error: ${message}`));
2656
2739
  console.error(
2657
- chalk16.red(
2740
+ chalk17.red(
2658
2741
  `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.`
2659
2742
  )
2660
2743
  );
2661
- console.error(chalk16.red("Recover it NOW from the values below \u2014 they will not be shown again:"));
2744
+ console.error(chalk17.red("Recover it NOW from the values below \u2014 they will not be shown again:"));
2662
2745
  console.error("");
2663
2746
  console.error(` Key alias: ${result.key_alias}`);
2664
2747
  console.error(` Store password: ${result.store_password}`);
@@ -2672,11 +2755,11 @@ function printKeystoreWriteFailure(opts, err, result) {
2672
2755
  function printError5(opts, err) {
2673
2756
  const message = err instanceof Error ? err.message : String(err);
2674
2757
  if (opts.json) console.log(JSON.stringify({ event: "error", message }));
2675
- console.error(chalk16.red(`error: ${message}`));
2758
+ console.error(chalk17.red(`error: ${message}`));
2676
2759
  }
2677
2760
 
2678
2761
  // src/commands/webhooks.ts
2679
- import chalk17 from "chalk";
2762
+ import chalk18 from "chalk";
2680
2763
  function clientFor3(opts) {
2681
2764
  const { token, baseUrl } = resolveAuth(opts);
2682
2765
  return new ApiClient(baseUrl, token);
@@ -2707,14 +2790,14 @@ async function webhooksAdd(opts, deps) {
2707
2790
  if (opts.json) {
2708
2791
  console.log(JSON.stringify(result));
2709
2792
  } else {
2710
- console.log(chalk17.green(`\u2713 added ${result.id} ${result.url}`));
2793
+ console.log(chalk18.green(`\u2713 added ${result.id} ${result.url}`));
2711
2794
  console.log("");
2712
- console.log(` Signing secret: ${chalk17.bold(result.secret)}`);
2795
+ console.log(` Signing secret: ${chalk18.bold(result.secret)}`);
2713
2796
  if (opts.secret !== void 0) {
2714
- console.log(chalk17.yellow(" (This is the secret you provided.)"));
2797
+ console.log(chalk18.yellow(" (This is the secret you provided.)"));
2715
2798
  }
2716
2799
  console.log(
2717
- chalk17.yellow(" This secret is shown ONCE and cannot be retrieved again \u2014 store it now.")
2800
+ chalk18.yellow(" This secret is shown ONCE and cannot be retrieved again \u2014 store it now.")
2718
2801
  );
2719
2802
  }
2720
2803
  process.exitCode = 0;
@@ -2734,7 +2817,7 @@ function formatWebhooksHuman(webhooks2) {
2734
2817
  if (webhooks2.length === 0) {
2735
2818
  return "No webhooks found.\n";
2736
2819
  }
2737
- const HEADERS4 = ["ID", "URL", "ACTIVE", "LAST DELIVERY", "CREATED"];
2820
+ const HEADERS3 = ["ID", "URL", "ACTIVE", "LAST DELIVERY", "CREATED"];
2738
2821
  const rows = webhooks2.map((w) => [
2739
2822
  w.id,
2740
2823
  w.url,
@@ -2742,10 +2825,10 @@ function formatWebhooksHuman(webhooks2) {
2742
2825
  formatLastDelivery(w.last_delivery),
2743
2826
  w.created_at
2744
2827
  ]);
2745
- const widths = HEADERS4.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
2828
+ const widths = HEADERS3.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
2746
2829
  const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
2747
2830
  const join3 = (cells) => cells.join(" ").trimEnd();
2748
- const lines = [chalk17.bold(join3(padCells(HEADERS4)))];
2831
+ const lines = [chalk18.bold(join3(padCells(HEADERS3)))];
2749
2832
  for (const row of rows) lines.push(join3(padCells(row)));
2750
2833
  return lines.join("\n") + "\n";
2751
2834
  }
@@ -2771,7 +2854,7 @@ async function webhooksRm(id, opts, deps) {
2771
2854
  if (opts.json) {
2772
2855
  console.log(JSON.stringify({ event: "deleted", id }));
2773
2856
  } else {
2774
- console.log(chalk17.green(`\u2713 removed ${id}`));
2857
+ console.log(chalk18.green(`\u2713 removed ${id}`));
2775
2858
  }
2776
2859
  process.exitCode = 0;
2777
2860
  } catch (err) {
@@ -2804,13 +2887,13 @@ async function webhooksTest(id, opts, deps) {
2804
2887
  if (opts.json) console.log(JSON.stringify(ld));
2805
2888
  if (ld.status === "succeeded") {
2806
2889
  if (!opts.json) {
2807
- console.log(chalk17.green(`\u2713 Delivered (HTTP ${ld.response_code})`));
2890
+ console.log(chalk18.green(`\u2713 Delivered (HTTP ${ld.response_code})`));
2808
2891
  }
2809
2892
  process.exitCode = 0;
2810
2893
  } else {
2811
2894
  if (!opts.json) {
2812
2895
  const code = ld.response_code != null ? `HTTP ${ld.response_code}` : "no response";
2813
- console.error(chalk17.red(`\u2717 Failed (${code}) at ${ld.at}`));
2896
+ console.error(chalk18.red(`\u2717 Failed (${code}) at ${ld.at}`));
2814
2897
  }
2815
2898
  process.exitCode = 1;
2816
2899
  }
@@ -2819,7 +2902,7 @@ async function webhooksTest(id, opts, deps) {
2819
2902
  }
2820
2903
  if (opts.json) console.log(JSON.stringify({ status: "pending" }));
2821
2904
  console.error(
2822
- chalk17.yellow(
2905
+ chalk18.yellow(
2823
2906
  `Delivery still pending after ${TEST_POLL_BUDGET_MS / 1e3}s \u2014 check \`vibeview webhooks list\` later for the result.`
2824
2907
  )
2825
2908
  );
@@ -2832,41 +2915,29 @@ async function webhooksTest(id, opts, deps) {
2832
2915
  function printError6(opts, err) {
2833
2916
  const message = err instanceof Error ? err.message : String(err);
2834
2917
  if (opts.json) console.log(JSON.stringify({ event: "error", message }));
2835
- console.error(chalk17.red(`error: ${message}`));
2836
- }
2837
-
2838
- // src/lib/formatters/devices.ts
2839
- import chalk18 from "chalk";
2840
- var HEADERS3 = ["DEVICE_ID", "NAME", "TYPE", "CATEGORY", "OS", "STATUS"];
2841
- function formatDevicesHuman(devices) {
2842
- if (devices.length === 0) {
2843
- return "No devices registered.\n";
2844
- }
2845
- const rows = devices.map((d) => [
2846
- d.device_id,
2847
- d.name,
2848
- d.type,
2849
- d.category,
2850
- d.os_version,
2851
- d.status
2852
- ]);
2853
- const widths = HEADERS3.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
2854
- const fmt = (cells) => cells.map((c, i) => c.padEnd(widths[i])).join(" ").trimEnd();
2855
- const lines = [chalk18.bold(fmt(HEADERS3))];
2856
- for (const row of rows.sort(rowSort)) {
2857
- const statusColored = row[5] === "available" ? chalk18.green(row[5]) : row[5] === "busy" ? chalk18.yellow(row[5]) : chalk18.dim(row[5]);
2858
- lines.push(fmt([row[0], row[1], row[2], row[3], row[4], statusColored]));
2859
- }
2860
- return lines.join("\n") + "\n";
2861
- }
2862
- function rowSort(a, b) {
2863
- return a[2].localeCompare(b[2]) || a[3].localeCompare(b[3]) || a[0].localeCompare(b[0]);
2918
+ console.error(chalk18.red(`error: ${message}`));
2864
2919
  }
2865
2920
 
2866
2921
  // src/commands/list-devices.ts
2867
2922
  async function listDevicesCommand(options) {
2868
2923
  const { token, baseUrl } = resolveAuth(options);
2869
2924
  const client = new ApiClient(baseUrl, token);
2925
+ if (options.models) {
2926
+ try {
2927
+ const models = filterDeviceModels(await client.listDeviceModels(), options);
2928
+ if (options.output === "json") {
2929
+ process.stdout.write(JSON.stringify(models, null, 2) + "\n");
2930
+ } else {
2931
+ process.stdout.write(formatDeviceModelsHuman(models));
2932
+ }
2933
+ process.exit(EXIT_PASS);
2934
+ } catch (err) {
2935
+ process.stderr.write(`error: ${err instanceof Error ? err.message : String(err)}
2936
+ `);
2937
+ process.exit(EXIT_INFRA_ERROR);
2938
+ }
2939
+ return;
2940
+ }
2870
2941
  try {
2871
2942
  const devices = await client.listDevices({
2872
2943
  type: options.type,
@@ -3151,8 +3222,14 @@ program.command("list-suites").description("List available test suites").option(
3151
3222
  []
3152
3223
  ).option("--match <mode>", "When multiple --tag flags: any (default) or all", "any").action((opts) => listSuites(opts));
3153
3224
  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));
3154
- 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").option("--category <category>", "Filter by category: phone, tablet, tv").option("--status <status>", "Filter by status: available, busy, offline").action((opts) => listDevicesCommand(opts));
3155
- program.command("upload-app <file>").description("Upload an app build (zipped simulator .app for iOS, .apk for Android)").option("--token <token>", "API token").option("--output <format>", "Output format: human, json", "human").option("--name <name>", "App name (auto-detected if not provided)").action((file, opts) => uploadApp(file, opts));
3225
+ 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
+ "--models",
3227
+ "List the device models you can start instead (the names `dev --model` takes)",
3228
+ false
3229
+ ).action((opts) => listDevicesCommand(opts));
3230
+ program.command("upload-app <file>").description(
3231
+ "Upload an app build (zipped simulator .app for iOS/tvOS, .apk for Android/Android TV, or a Roku channel .zip)"
3232
+ ).option("--token <token>", "API token").option("--output <format>", "Output format: human, json", "human").option("--name <name>", "App name (auto-detected if not provided)").action((file, opts) => uploadApp(file, opts));
3156
3233
  program.command("create-test [name]").description("Create a standalone AI test case").option("--token <token>", "API token").option("--output <format>", "Output format: human, json", "human").option("--app <app>", "App ID (public_id) or package name (requires --platform)").option("--platform <platform>", "Platform (ios, android) when using package name for --app").option("--steps <json>", "Test steps as inline JSON array").option("--steps-file <path>", "Path to JSON file containing test steps").option("--context <text>", "Additional context for AI test execution").option("--test-name <name>", "Test case name (alternative to positional arg)").action((name, opts) => createTest({ ...opts, testName: name || opts.testName || "CLI Test" }));
3157
3234
  program.command("login").description("Log in to VibeView (interactive)").option("--base-url <url>", "VibeView server URL (defaults to https://vibeview.io)").action((opts) => login(opts));
3158
3235
  program.command("logout").description("Revoke the saved login token and remove it from ~/.vibeview/config.json").action(() => logout());
@@ -3165,9 +3242,12 @@ program.command("dev").description(
3165
3242
  "--device-category <category>",
3166
3243
  "phone or tablet (default: phone; TV platforms always use tv)",
3167
3244
  parseDeviceCategory
3245
+ ).option(
3246
+ "--model <name>",
3247
+ 'Run on this exact device model, e.g. "iPhone Duo" (see `vibeview list-devices --models`)'
3168
3248
  ).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(
3169
3249
  "--detach",
3170
- "Run the dev session in the background; prints session info and exits (see `dev-status`, `dev-stop`)",
3250
+ "Run the dev session in the background; prints session info and exits (see `dev-status`, `dev-stop`, and `dev-reload` for Roku)",
3171
3251
  false
3172
3252
  ).option(
3173
3253
  "--build",
@@ -3307,10 +3387,13 @@ webhooks.command("rm <id>").description("Remove a webhook endpoint (rotate the s
3307
3387
  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));
3308
3388
  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));
3309
3389
  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));
3390
+ program.command("dev-reload").description(
3391
+ "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
+ ).option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--json", "Print as one JSON line", false).action((opts) => devReload(opts));
3310
3393
  program.command("mcp").description(
3311
- "Run an MCP stdio server exposing the agent verbs (tap, swipe, type, ...) plus dev_start/dev_stop as tools"
3394
+ "Run an MCP stdio server exposing the agent verbs (tap, swipe, type, ...) plus dev_start/dev_stop/dev_reload as tools"
3312
3395
  ).option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").action(async (opts) => {
3313
- const { runMcpServer } = await import("./server-WWNWYD4Q.js");
3396
+ const { runMcpServer } = await import("./server-XMSBLIWY.js");
3314
3397
  await runMcpServer(opts);
3315
3398
  });
3316
3399
  program.command("agent-setup").description(