vibeview 0.25.0 → 0.27.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.
@@ -42,6 +42,21 @@ import { createWriteStream, mkdirSync as mkdirSync2, renameSync, rmSync } from "
42
42
  import { basename, join as join2, resolve, sep } from "path";
43
43
  import { pipeline } from "stream/promises";
44
44
  import { Readable } from "stream";
45
+ var AuthError = class extends Error {
46
+ constructor(message = "Authentication failed. Check your API token.") {
47
+ super(message);
48
+ this.name = "AuthError";
49
+ }
50
+ };
51
+ function errorExitCode(err, otherwise = 1) {
52
+ return err instanceof AuthError ? 2 : otherwise;
53
+ }
54
+ function reportCommandError(json, err) {
55
+ const message = err instanceof Error ? err.message : String(err);
56
+ if (json) console.log(JSON.stringify({ event: "error", message }));
57
+ process.stderr.write(`error: ${message}
58
+ `);
59
+ }
45
60
  var NotFoundError = class extends Error {
46
61
  constructor(message) {
47
62
  super(message);
@@ -52,6 +67,8 @@ function extractDetail(text) {
52
67
  try {
53
68
  const parsed = JSON.parse(text);
54
69
  if (parsed && typeof parsed.detail === "string") return parsed.detail;
70
+ if (parsed && parsed.detail && typeof parsed.detail.message === "string")
71
+ return parsed.detail.message;
55
72
  } catch {
56
73
  }
57
74
  return void 0;
@@ -96,7 +113,7 @@ var ApiClient = class {
96
113
  });
97
114
  if (!resp.ok) {
98
115
  const text = await resp.text().catch(() => "");
99
- if (resp.status === 401) throw new Error("Authentication failed. Check your API token.");
116
+ if (resp.status === 401) throw new AuthError();
100
117
  if (resp.status === 404) throw new NotFoundError(`Not found: ${extractDetail(text) ?? path}`);
101
118
  throw new Error(extractDetail(text) ?? `API error ${resp.status}: ${text}`);
102
119
  }
@@ -168,7 +185,7 @@ var ApiClient = class {
168
185
  });
169
186
  if (!resp.ok) {
170
187
  const text = await resp.text().catch(() => "");
171
- if (resp.status === 401) throw new Error("Authentication failed. Check your API token.");
188
+ if (resp.status === 401) throw new AuthError();
172
189
  if (resp.status === 404) throw new NotFoundError(`Not found: ${extractDetail(text) ?? path}`);
173
190
  throw new Error(extractDetail(text) ?? `API error ${resp.status}: ${text}`);
174
191
  }
@@ -188,7 +205,7 @@ var ApiClient = class {
188
205
  });
189
206
  if (!resp.ok) {
190
207
  const text = await resp.text().catch(() => "");
191
- if (resp.status === 401) throw new Error("Authentication failed. Check your API token.");
208
+ if (resp.status === 401) throw new AuthError();
192
209
  if (resp.status === 404) throw new NotFoundError(`Not found: ${extractDetail(text) ?? path}`);
193
210
  throw new Error(extractDetail(text) ?? `API error ${resp.status}: ${text}`);
194
211
  }
@@ -347,7 +364,7 @@ var ApiClient = class {
347
364
  const resp = await this.fetchOrExplain(url, { method: "GET", headers });
348
365
  if (!resp.ok) {
349
366
  const text = await resp.text().catch(() => "");
350
- if (resp.status === 401) throw new Error("Authentication failed. Check your API token.");
367
+ if (resp.status === 401) throw new AuthError();
351
368
  if (resp.status === 404)
352
369
  throw new NotFoundError(`Not found: ${extractDetail(text) ?? `/builds/${id}/artifact`}`);
353
370
  throw new Error(extractDetail(text) ?? `API error ${resp.status}: ${text}`);
@@ -729,6 +746,9 @@ export {
729
746
  loadConfig,
730
747
  saveConfig,
731
748
  resolveAuth,
749
+ AuthError,
750
+ errorExitCode,
751
+ reportCommandError,
732
752
  NotFoundError,
733
753
  ApiClient,
734
754
  createSpinner,
package/dist/cli.js CHANGED
@@ -11,16 +11,19 @@ import {
11
11
  formatDeviceModelsHuman,
12
12
  formatDevicesHuman,
13
13
  formatDuration,
14
+ formatUtcTimestamp,
14
15
  killOwnedMetro,
15
16
  listBuilds,
16
17
  loadDevConfig,
17
18
  newTransitionState,
19
+ platformWithArticle,
20
+ pressButtonsHelp,
18
21
  printOutcomeLine,
19
22
  printTransition,
20
23
  readDevState,
21
24
  readMetroState,
22
25
  runAgentVerb
23
- } from "./chunk-GEKYHEWU.js";
26
+ } from "./chunk-46X7UN6Y.js";
24
27
  import {
25
28
  ApiClient,
26
29
  EXIT_INFRA_ERROR,
@@ -29,15 +32,17 @@ import {
29
32
  NotFoundError,
30
33
  UsageError,
31
34
  createSpinner,
35
+ errorExitCode,
32
36
  loadConfig,
37
+ reportCommandError,
33
38
  resolveAuth,
34
39
  saveConfig,
35
40
  uploadApp
36
- } from "./chunk-RFFCCPGZ.js";
41
+ } from "./chunk-U7FCGMWG.js";
37
42
 
38
43
  // src/cli.ts
39
44
  import { createRequire } from "module";
40
- import { Command } from "commander";
45
+ import { Command, Option } from "commander";
41
46
 
42
47
  // src/commands/run-suite.ts
43
48
  import chalk3 from "chalk";
@@ -833,10 +838,16 @@ import chalk6 from "chalk";
833
838
  async function listSuites(options) {
834
839
  const { token, baseUrl } = resolveAuth(options);
835
840
  const client = new ApiClient(baseUrl, token);
836
- const suites = await client.listSuites({
837
- tags: options.tag && options.tag.length > 0 ? options.tag : void 0,
838
- match: options.match
839
- });
841
+ let suites;
842
+ try {
843
+ suites = await client.listSuites({
844
+ tags: options.tag && options.tag.length > 0 ? options.tag : void 0,
845
+ match: options.match
846
+ });
847
+ } catch (err) {
848
+ reportCommandError(options.output === "json", err);
849
+ process.exit(EXIT_INFRA_ERROR);
850
+ }
840
851
  if (options.output === "json") {
841
852
  console.log(JSON.stringify(suites, null, 2));
842
853
  return;
@@ -861,7 +872,13 @@ import chalk7 from "chalk";
861
872
  async function listApps(options) {
862
873
  const { token, baseUrl } = resolveAuth(options);
863
874
  const client = new ApiClient(baseUrl, token);
864
- const apps = await client.listApps();
875
+ let apps;
876
+ try {
877
+ apps = await client.listApps();
878
+ } catch (err) {
879
+ reportCommandError(options.output === "json", err);
880
+ process.exit(EXIT_INFRA_ERROR);
881
+ }
865
882
  if (options.output === "json") {
866
883
  console.log(JSON.stringify(apps, null, 2));
867
884
  return;
@@ -895,7 +912,7 @@ async function stopCommand(sessionId, opts, deps) {
895
912
  const message = err instanceof Error ? err.message : String(err);
896
913
  if (opts.json) console.log(JSON.stringify({ stopped: false, session_id: id, message }));
897
914
  console.error(chalk8.red(`error: ${message}`));
898
- process.exitCode = 1;
915
+ process.exitCode = errorExitCode(err);
899
916
  return;
900
917
  }
901
918
  const state = readDevState(process.cwd());
@@ -1217,7 +1234,7 @@ async function devReload(opts) {
1217
1234
  fail(
1218
1235
  opts,
1219
1236
  "not_roku",
1220
- `dev-reload is for Roku channels; a ${state.platform} session hot-reloads through Metro on its own.`,
1237
+ `dev-reload is for Roku channels; ${platformWithArticle(state.platform)} session hot-reloads through Metro on its own.`,
1221
1238
  2
1222
1239
  );
1223
1240
  }
@@ -1277,8 +1294,10 @@ import chalk13 from "chalk";
1277
1294
  // src/lib/formatters/builds.ts
1278
1295
  import chalk12 from "chalk";
1279
1296
  var HEADERS = ["ID", "PLATFORM", "STATUS", "CREATED", "COST", "APP"];
1280
- function formatCostCell(costMicro) {
1281
- return costMicro > 0 ? `$${(costMicro / 1e6).toFixed(2)}` : "";
1297
+ var UNBILLED_STATUSES = /* @__PURE__ */ new Set(["queued", "running"]);
1298
+ function formatCostCell(costMicro, status) {
1299
+ if (costMicro <= 0 && UNBILLED_STATUSES.has(status)) return "-";
1300
+ return `$${(Math.max(costMicro, 0) / 1e6).toFixed(2)}`;
1282
1301
  }
1283
1302
  var STATUS_COLOR = {
1284
1303
  succeeded: chalk12.green,
@@ -1297,8 +1316,8 @@ function formatBuildsHuman(builds2) {
1297
1316
  b.id,
1298
1317
  b.platform,
1299
1318
  b.status,
1300
- b.created_at ?? "",
1301
- formatCostCell(b.cost_micro),
1319
+ formatUtcTimestamp(b.created_at),
1320
+ formatCostCell(b.cost_micro, b.status),
1302
1321
  b.app_public_id ?? ""
1303
1322
  ]);
1304
1323
  const widths = HEADERS.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
@@ -1349,7 +1368,7 @@ async function buildsList(opts, deps) {
1349
1368
  process.exitCode = 0;
1350
1369
  } catch (err) {
1351
1370
  printError(opts, err);
1352
- process.exitCode = 1;
1371
+ process.exitCode = errorExitCode(err);
1353
1372
  }
1354
1373
  }
1355
1374
  async function buildsLogs(id, opts, deps) {
@@ -1372,7 +1391,7 @@ async function buildsLogs(id, opts, deps) {
1372
1391
  process.exitCode = final.status === "succeeded" ? 0 : 1;
1373
1392
  } catch (err) {
1374
1393
  printError(opts, err);
1375
- process.exitCode = 1;
1394
+ process.exitCode = errorExitCode(err);
1376
1395
  }
1377
1396
  }
1378
1397
  var MAX_CONSECUTIVE_POLL_FAILURES = 5;
@@ -1465,7 +1484,7 @@ async function buildsDownload(id, opts, deps) {
1465
1484
  process.exitCode = 0;
1466
1485
  } catch (err) {
1467
1486
  printError(opts, err);
1468
- process.exitCode = 1;
1487
+ process.exitCode = errorExitCode(err);
1469
1488
  }
1470
1489
  }
1471
1490
  async function buildsCancel(id, opts, deps) {
@@ -1482,7 +1501,7 @@ async function buildsCancel(id, opts, deps) {
1482
1501
  process.exitCode = 0;
1483
1502
  } catch (err) {
1484
1503
  printError(opts, err);
1485
- process.exitCode = 1;
1504
+ process.exitCode = errorExitCode(err);
1486
1505
  }
1487
1506
  }
1488
1507
  async function buildsDeleteArtifact(id, opts, deps) {
@@ -1499,7 +1518,7 @@ async function buildsDeleteArtifact(id, opts, deps) {
1499
1518
  process.exitCode = 0;
1500
1519
  } catch (err) {
1501
1520
  printError(opts, err);
1502
- process.exitCode = 1;
1521
+ process.exitCode = errorExitCode(err);
1503
1522
  }
1504
1523
  }
1505
1524
  function printError(opts, err) {
@@ -1691,7 +1710,7 @@ async function submitCommand(opts, deps) {
1691
1710
  process.exitCode = final.status === "succeeded" ? 0 : 1;
1692
1711
  } catch (err) {
1693
1712
  printError2(opts, err);
1694
- process.exitCode = 1;
1713
+ process.exitCode = errorExitCode(err);
1695
1714
  }
1696
1715
  }
1697
1716
  async function checkStore(platform, androidFamily, ascKey2, opts, deps) {
@@ -1722,7 +1741,7 @@ async function checkStore(platform, androidFamily, ascKey2, opts, deps) {
1722
1741
  process.exitCode = result.ready ? 0 : 1;
1723
1742
  } catch (err) {
1724
1743
  printError2(opts, err);
1725
- process.exitCode = 1;
1744
+ process.exitCode = errorExitCode(err);
1726
1745
  }
1727
1746
  }
1728
1747
  async function resolveLatestBuild(platform, deps) {
@@ -1852,7 +1871,7 @@ async function versionsList(opts, deps) {
1852
1871
  process.exitCode = 0;
1853
1872
  } catch (err) {
1854
1873
  printError3(opts, err);
1855
- process.exitCode = 1;
1874
+ process.exitCode = errorExitCode(err);
1856
1875
  }
1857
1876
  }
1858
1877
  async function versionsSet(buildNumber, opts, deps) {
@@ -1885,7 +1904,7 @@ async function versionsSet(buildNumber, opts, deps) {
1885
1904
  process.exitCode = 0;
1886
1905
  } catch (err) {
1887
1906
  printError3(opts, err);
1888
- process.exitCode = 1;
1907
+ process.exitCode = errorExitCode(err);
1889
1908
  }
1890
1909
  }
1891
1910
  var HEADERS2 = ["PLATFORM", "BUILD NUMBER", "UPDATED"];
@@ -1961,7 +1980,7 @@ Create an admin API token in the dashboard and use ${chalk16.bold("--token")} or
1961
1980
  console.log(JSON.stringify({ event: "error", message }));
1962
1981
  }
1963
1982
  console.error(chalk16.red(`error: ${message}`));
1964
- process.exitCode = 1;
1983
+ process.exitCode = errorExitCode(err);
1965
1984
  }
1966
1985
  }
1967
1986
 
@@ -2039,7 +2058,7 @@ async function envList(opts, deps) {
2039
2058
  process.exitCode = 0;
2040
2059
  } catch (err) {
2041
2060
  printError4(opts, err);
2042
- process.exitCode = 1;
2061
+ process.exitCode = errorExitCode(err);
2043
2062
  }
2044
2063
  }
2045
2064
  function formatEnvVarsHuman(variables) {
@@ -2088,7 +2107,7 @@ async function envSet(name, value, opts, deps) {
2088
2107
  process.exitCode = 0;
2089
2108
  } catch (err) {
2090
2109
  printError4(opts, err);
2091
- process.exitCode = 1;
2110
+ process.exitCode = errorExitCode(err);
2092
2111
  }
2093
2112
  }
2094
2113
  function resolveVisibility(opts) {
@@ -2121,7 +2140,7 @@ async function envRm(name, opts, deps) {
2121
2140
  process.exitCode = 0;
2122
2141
  } catch (err) {
2123
2142
  printError4(opts, err);
2124
- process.exitCode = 1;
2143
+ process.exitCode = errorExitCode(err);
2125
2144
  }
2126
2145
  }
2127
2146
  async function envPull(opts, deps) {
@@ -2153,7 +2172,7 @@ async function envPull(opts, deps) {
2153
2172
  process.exitCode = 0;
2154
2173
  } catch (err) {
2155
2174
  printError4(opts, err);
2156
- process.exitCode = 1;
2175
+ process.exitCode = errorExitCode(err);
2157
2176
  }
2158
2177
  }
2159
2178
  function isLossyEnvValue(value) {
@@ -2346,7 +2365,7 @@ async function credentialsAdd(opts, deps) {
2346
2365
  process.exitCode = 0;
2347
2366
  } catch (err) {
2348
2367
  printError5(opts, err);
2349
- process.exitCode = 1;
2368
+ process.exitCode = errorExitCode(err);
2350
2369
  }
2351
2370
  }
2352
2371
  async function credentialsGenerate(opts, deps) {
@@ -2420,7 +2439,7 @@ async function credentialsGenerate(opts, deps) {
2420
2439
  process.exitCode = 0;
2421
2440
  } catch (err) {
2422
2441
  printError5(opts, err);
2423
- process.exitCode = 1;
2442
+ process.exitCode = errorExitCode(err);
2424
2443
  }
2425
2444
  }
2426
2445
  async function generateAppleCredential(platform, opts, d) {
@@ -2456,7 +2475,7 @@ async function generateAppleCredential(platform, opts, d) {
2456
2475
  process.exitCode = 0;
2457
2476
  } catch (err) {
2458
2477
  printError5(opts, err);
2459
- process.exitCode = 1;
2478
+ process.exitCode = errorExitCode(err);
2460
2479
  }
2461
2480
  }
2462
2481
  async function credentialsDownload(name, opts, deps) {
@@ -2531,7 +2550,7 @@ async function credentialsDownload(name, opts, deps) {
2531
2550
  process.exitCode = 0;
2532
2551
  } catch (err) {
2533
2552
  printError5(opts, err);
2534
- process.exitCode = 1;
2553
+ process.exitCode = errorExitCode(err);
2535
2554
  }
2536
2555
  }
2537
2556
  async function ascKeyAdd(opts, deps) {
@@ -2571,7 +2590,7 @@ async function ascKeyAdd(opts, deps) {
2571
2590
  process.exitCode = 0;
2572
2591
  } catch (err) {
2573
2592
  printError5(opts, err);
2574
- process.exitCode = 1;
2593
+ process.exitCode = errorExitCode(err);
2575
2594
  }
2576
2595
  }
2577
2596
  async function ascKeyList(opts, deps) {
@@ -2586,7 +2605,7 @@ async function ascKeyList(opts, deps) {
2586
2605
  process.exitCode = 0;
2587
2606
  } catch (err) {
2588
2607
  printError5(opts, err);
2589
- process.exitCode = 1;
2608
+ process.exitCode = errorExitCode(err);
2590
2609
  }
2591
2610
  }
2592
2611
  function formatAscKeysHuman(keys) {
@@ -2594,7 +2613,7 @@ function formatAscKeysHuman(keys) {
2594
2613
  return "No App Store Connect keys found.\n";
2595
2614
  }
2596
2615
  const HEADERS3 = ["NAME", "KEY ID", "ISSUER ID", "CREATED"];
2597
- const rows = keys.map((k) => [k.name, k.key_id, k.issuer_id, k.created_at ?? ""]);
2616
+ const rows = keys.map((k) => [k.name, k.key_id, k.issuer_id, formatUtcTimestamp(k.created_at)]);
2598
2617
  const widths = HEADERS3.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
2599
2618
  const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
2600
2619
  const join3 = (cells) => cells.join(" ").trimEnd();
@@ -2614,7 +2633,7 @@ async function ascKeyRm(name, opts, deps) {
2614
2633
  process.exitCode = 0;
2615
2634
  } catch (err) {
2616
2635
  printError5(opts, err);
2617
- process.exitCode = 1;
2636
+ process.exitCode = errorExitCode(err);
2618
2637
  }
2619
2638
  }
2620
2639
  async function playKeyAdd(opts, deps) {
@@ -2647,7 +2666,7 @@ async function playKeyAdd(opts, deps) {
2647
2666
  process.exitCode = 0;
2648
2667
  } catch (err) {
2649
2668
  printError5(opts, err);
2650
- process.exitCode = 1;
2669
+ process.exitCode = errorExitCode(err);
2651
2670
  }
2652
2671
  }
2653
2672
  async function playKeyList(opts, deps) {
@@ -2662,7 +2681,7 @@ async function playKeyList(opts, deps) {
2662
2681
  process.exitCode = 0;
2663
2682
  } catch (err) {
2664
2683
  printError5(opts, err);
2665
- process.exitCode = 1;
2684
+ process.exitCode = errorExitCode(err);
2666
2685
  }
2667
2686
  }
2668
2687
  function formatPlayKeysHuman(keys) {
@@ -2670,7 +2689,7 @@ function formatPlayKeysHuman(keys) {
2670
2689
  return "No Google Play keys found.\n";
2671
2690
  }
2672
2691
  const HEADERS3 = ["NAME", "CLIENT EMAIL", "CREATED"];
2673
- const rows = keys.map((k) => [k.name, k.client_email, k.created_at ?? ""]);
2692
+ const rows = keys.map((k) => [k.name, k.client_email, formatUtcTimestamp(k.created_at)]);
2674
2693
  const widths = HEADERS3.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
2675
2694
  const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
2676
2695
  const join3 = (cells) => cells.join(" ").trimEnd();
@@ -2690,7 +2709,7 @@ async function playKeyRm(name, opts, deps) {
2690
2709
  process.exitCode = 0;
2691
2710
  } catch (err) {
2692
2711
  printError5(opts, err);
2693
- process.exitCode = 1;
2712
+ process.exitCode = errorExitCode(err);
2694
2713
  }
2695
2714
  }
2696
2715
  async function credentialsList(opts, deps) {
@@ -2711,7 +2730,7 @@ async function credentialsList(opts, deps) {
2711
2730
  process.exitCode = 0;
2712
2731
  } catch (err) {
2713
2732
  printError5(opts, err);
2714
- process.exitCode = 1;
2733
+ process.exitCode = errorExitCode(err);
2715
2734
  }
2716
2735
  }
2717
2736
  function formatIdentity(c) {
@@ -2730,10 +2749,10 @@ function earliestExpiry(c) {
2730
2749
  function formatExpires(c) {
2731
2750
  const parts = [];
2732
2751
  if (c.profile_expires_at) {
2733
- if (c.expires_at) parts.push(`cert ${c.expires_at}`);
2734
- parts.push(`profile ${c.profile_expires_at}`);
2752
+ if (c.expires_at) parts.push(`cert ${formatUtcTimestamp(c.expires_at)}`);
2753
+ parts.push(`profile ${formatUtcTimestamp(c.profile_expires_at)}`);
2735
2754
  } else if (c.expires_at) {
2736
- parts.push(c.expires_at);
2755
+ parts.push(formatUtcTimestamp(c.expires_at));
2737
2756
  }
2738
2757
  if (parts.length === 0) return "";
2739
2758
  const cell = parts.join(" \xB7 ");
@@ -2770,7 +2789,7 @@ async function credentialsRm(name, opts, deps) {
2770
2789
  process.exitCode = 0;
2771
2790
  } catch (err) {
2772
2791
  printError5(opts, err);
2773
- process.exitCode = 1;
2792
+ process.exitCode = errorExitCode(err);
2774
2793
  }
2775
2794
  }
2776
2795
  function printKeystoreWriteFailure(opts, err, result) {
@@ -2848,14 +2867,14 @@ async function webhooksAdd(opts, deps) {
2848
2867
  process.exitCode = 0;
2849
2868
  } catch (err) {
2850
2869
  printError6(opts, err);
2851
- process.exitCode = 1;
2870
+ process.exitCode = errorExitCode(err);
2852
2871
  }
2853
2872
  }
2854
2873
  function formatLastDelivery(ld) {
2855
2874
  if (!ld) return "\u2014";
2856
2875
  if (ld.status === "pending") return "pending";
2857
2876
  const code = ld.response_code != null ? ` (${ld.response_code})` : "";
2858
- const at = ld.at != null ? ` ${ld.at}` : "";
2877
+ const at = ld.at != null ? ` ${formatUtcTimestamp(ld.at)}` : "";
2859
2878
  return `${ld.status}${code}${at}`;
2860
2879
  }
2861
2880
  function formatWebhooksHuman(webhooks2) {
@@ -2868,7 +2887,7 @@ function formatWebhooksHuman(webhooks2) {
2868
2887
  w.url,
2869
2888
  w.active ? "yes" : "no",
2870
2889
  formatLastDelivery(w.last_delivery),
2871
- w.created_at
2890
+ formatUtcTimestamp(w.created_at)
2872
2891
  ]);
2873
2892
  const widths = HEADERS3.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
2874
2893
  const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
@@ -2889,7 +2908,7 @@ async function webhooksList(opts, deps) {
2889
2908
  process.exitCode = 0;
2890
2909
  } catch (err) {
2891
2910
  printError6(opts, err);
2892
- process.exitCode = 1;
2911
+ process.exitCode = errorExitCode(err);
2893
2912
  }
2894
2913
  }
2895
2914
  async function webhooksRm(id, opts, deps) {
@@ -2904,7 +2923,7 @@ async function webhooksRm(id, opts, deps) {
2904
2923
  process.exitCode = 0;
2905
2924
  } catch (err) {
2906
2925
  printError6(opts, err);
2907
- process.exitCode = 1;
2926
+ process.exitCode = errorExitCode(err);
2908
2927
  }
2909
2928
  }
2910
2929
  function parseUtcTimestamp(iso) {
@@ -2938,7 +2957,7 @@ async function webhooksTest(id, opts, deps) {
2938
2957
  } else {
2939
2958
  if (!opts.json) {
2940
2959
  const code = ld.response_code != null ? `HTTP ${ld.response_code}` : "no response";
2941
- console.error(chalk19.red(`\u2717 Failed (${code}) at ${ld.at}`));
2960
+ console.error(chalk19.red(`\u2717 Failed (${code}) at ${formatUtcTimestamp(ld.at)}`));
2942
2961
  }
2943
2962
  process.exitCode = 1;
2944
2963
  }
@@ -2954,7 +2973,7 @@ async function webhooksTest(id, opts, deps) {
2954
2973
  process.exitCode = 1;
2955
2974
  } catch (err) {
2956
2975
  printError6(opts, err);
2957
- process.exitCode = 1;
2976
+ process.exitCode = errorExitCode(err);
2958
2977
  }
2959
2978
  }
2960
2979
  function printError6(opts, err) {
@@ -2977,8 +2996,7 @@ async function listDevicesCommand(options) {
2977
2996
  }
2978
2997
  process.exit(EXIT_PASS);
2979
2998
  } catch (err) {
2980
- process.stderr.write(`error: ${err instanceof Error ? err.message : String(err)}
2981
- `);
2999
+ reportCommandError(options.output === "json", err);
2982
3000
  process.exit(EXIT_INFRA_ERROR);
2983
3001
  }
2984
3002
  return;
@@ -2996,8 +3014,7 @@ async function listDevicesCommand(options) {
2996
3014
  }
2997
3015
  process.exit(EXIT_PASS);
2998
3016
  } catch (err) {
2999
- process.stderr.write(`error: ${err instanceof Error ? err.message : String(err)}
3000
- `);
3017
+ reportCommandError(options.output === "json", err);
3001
3018
  process.exit(EXIT_INFRA_ERROR);
3002
3019
  }
3003
3020
  }
@@ -3270,7 +3287,11 @@ program.command("list-apps").description("List apps in your organization").optio
3270
3287
  program.command("list-builds <app>").description(
3271
3288
  "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
3289
  ).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));
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(
3290
+ 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").addOption(
3291
+ new Option("--type <type>", "Filter by device type").choices(["ios", "android", "roku"])
3292
+ ).addOption(
3293
+ new Option("--category <category>", "Filter by category").choices(["phone", "tablet", "tv"])
3294
+ ).addOption(new Option("--status <status>", "Filter by status").choices(["available", "busy"])).option(
3274
3295
  "--models",
3275
3296
  "List the device models you can start instead (the names `dev --model` takes)",
3276
3297
  false
@@ -3292,7 +3313,7 @@ program.command("dev").description(
3292
3313
  parseDeviceCategory
3293
3314
  ).option(
3294
3315
  "--model <name>",
3295
- 'Run on this exact device model, e.g. "iPhone Duo" (see `vibeview list-devices --models`)'
3316
+ 'Run on this exact device model, e.g. "iPhone Duo" or "Pixel 9 Pro Fold" (see `vibeview list-devices --models`)'
3296
3317
  ).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(
3297
3318
  "--detach",
3298
3319
  "Run the dev session in the background; prints session info and exits (see `dev-status`, `dev-stop`, and `dev-reload` for Roku)",
@@ -3444,8 +3465,8 @@ program.command("dev-reload").description(
3444
3465
  program.command("mcp").description(
3445
3466
  "Run an MCP stdio server exposing the agent verbs (tap, swipe, type, ...) plus dev_start/dev_stop/dev_reload as tools"
3446
3467
  ).option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").action(async (opts) => {
3447
- const { runMcpServer } = await import("./server-6EUBLXKE.js");
3448
- await runMcpServer(opts);
3468
+ const { runMcpServer } = await import("./server-XJREB7KU.js");
3469
+ await runMcpServer({ ...opts, version: pkg.version });
3449
3470
  });
3450
3471
  program.command("agent-setup").description(
3451
3472
  "Make VibeView discoverable to your coding agent: install the vibeview-agent skill (and optionally register the MCP server with Claude Code)"
@@ -3455,6 +3476,8 @@ for (const cmd of AGENT_COMMANDS) {
3455
3476
  const flagParams = cmd.params.filter((p) => p.cli !== "positional");
3456
3477
  const argSignature = positionalParams.map((p) => p.required ? `<${p.name}>` : `[${p.name}]`).join(" ");
3457
3478
  let command = program.command(argSignature ? `${cmd.verb} ${argSignature}` : cmd.verb).description(cmd.summary);
3479
+ if (cmd.verb === "press") command.addHelpText("after", `
3480
+ ${pressButtonsHelp()}`);
3458
3481
  for (const param of flagParams) {
3459
3482
  const flagArg = param.type === "number" ? `${param.cli} <n>` : `${param.cli} <value>`;
3460
3483
  command = command.option(flagArg, param.description);
@@ -17,14 +17,15 @@ import {
17
17
  isDevPlatform,
18
18
  loadDevConfig,
19
19
  parseBuildId,
20
+ platformWithArticle,
20
21
  readDevState,
21
22
  renderEnvelope,
22
23
  startDevSession
23
- } from "./chunk-GEKYHEWU.js";
24
+ } from "./chunk-46X7UN6Y.js";
24
25
  import {
25
26
  ApiClient,
26
27
  resolveAuth
27
- } from "./chunk-RFFCCPGZ.js";
28
+ } from "./chunk-U7FCGMWG.js";
28
29
 
29
30
  // src/mcp/server.ts
30
31
  import { writeFileSync } from "fs";
@@ -87,7 +88,7 @@ var DEV_START_DEF = {
87
88
  },
88
89
  model: {
89
90
  type: "string",
90
- description: 'Run on this exact device model, e.g. "iPhone Duo" for the foldable. Names come from list_device_models. Omit for a default phone (or TV on TV platforms).'
91
+ description: 'Run on this exact device model, e.g. "iPhone Duo" or "Pixel 9 Pro Fold" for a foldable (any model list_device_models marks form_factor "foldable"). Names come from list_device_models. Omit for a default phone (or TV on TV platforms).'
91
92
  },
92
93
  build_id: {
93
94
  type: "number",
@@ -116,7 +117,7 @@ var STOP_SESSION_DEF = {
116
117
  };
117
118
  var LIST_DEVICE_MODELS_DEF = {
118
119
  name: "list_device_models",
119
- description: 'List the device models you can start right now (model, OS version, platform, category, and form_factor "foldable" for a device that folds, like the iPhone Duo). Pass a model name to dev_start to run on it.',
120
+ description: 'List the device models you can start right now (model, OS version, platform, category, and form_factor "foldable" for any device with a hinge \u2014 the iPhone Duo, Android foldables such as the Pixel 9 Pro Fold). Pass a model name to dev_start to run on it.',
120
121
  mutating: false,
121
122
  properties: {
122
123
  device_type: {
@@ -376,7 +377,7 @@ async function handleUploadApp(args, deps) {
376
377
  }
377
378
  const name = typeof args.name === "string" && args.name ? args.name : void 0;
378
379
  const upload = deps.uploadApp ?? (async (f, n) => {
379
- const { uploadAppBuild } = await import("./upload-app-E32EZXYQ.js");
380
+ const { uploadAppBuild } = await import("./upload-app-QUUXKNR2.js");
380
381
  const { result: result2 } = await uploadAppBuild(f, { ...deps.auth, name: n });
381
382
  return result2;
382
383
  });
@@ -535,7 +536,7 @@ function createDevSessionController(auth, webUrl) {
535
536
  }
536
537
  if (target.platform !== "roku") {
537
538
  throw new Error(
538
- `dev_reload is for Roku channels; a ${target.platform} session hot-reloads through Metro on its own`
539
+ `dev_reload is for Roku channels; ${platformWithArticle(target.platform)} session hot-reloads through Metro on its own`
539
540
  );
540
541
  }
541
542
  if (!target.build) {
@@ -577,7 +578,7 @@ async function createMcpServer(opts) {
577
578
  agentTool: client.agentTool.bind(client),
578
579
  devSession
579
580
  };
580
- const server = new McpServer({ name: "vibeview", version: "1.0.0" });
581
+ const server = new McpServer({ name: "vibeview", version: opts.version ?? "0.0.0" });
581
582
  function zodShape(def) {
582
583
  const shape = {};
583
584
  for (const [key, prop] of Object.entries(def.properties)) {
@@ -3,7 +3,7 @@ import {
3
3
  describeBuild,
4
4
  uploadApp,
5
5
  uploadAppBuild
6
- } from "./chunk-RFFCCPGZ.js";
6
+ } from "./chunk-U7FCGMWG.js";
7
7
  export {
8
8
  describeBuild,
9
9
  uploadApp,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibeview",
3
- "version": "0.25.0",
3
+ "version": "0.27.0",
4
4
  "description": "VibeView CLI -- run mobile tests from CI/CD pipelines",
5
5
  "mcpName": "io.github.vibeview/vibeview",
6
6
  "type": "module",