vibeview 0.27.0 → 0.27.1

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.
@@ -6,10 +6,12 @@ import {
6
6
  UsageError,
7
7
  createSpinner,
8
8
  errorExitCode,
9
+ formatUtcTimestamp,
9
10
  loadConfig,
11
+ queueReasonCopy,
10
12
  resolveAuth,
11
13
  uploadAppBuild
12
- } from "./chunk-U7FCGMWG.js";
14
+ } from "./chunk-FTTWLCTE.js";
13
15
 
14
16
  // src/lib/dev-config.ts
15
17
  import { readFileSync, writeFileSync } from "fs";
@@ -249,17 +251,6 @@ function checkDevBuild(app, buildId, platform) {
249
251
 
250
252
  // src/commands/list-builds.ts
251
253
  import chalk from "chalk";
252
-
253
- // src/lib/formatters/time.ts
254
- function formatUtcTimestamp(iso) {
255
- if (!iso) return "";
256
- const hasZone = /(?:Z|[+-]\d{2}:?\d{2})$/i.test(iso);
257
- const d = new Date(hasZone ? iso : `${iso}Z`);
258
- if (Number.isNaN(d.getTime())) return iso;
259
- return `${d.toISOString().slice(0, 19).replace("T", " ")} UTC`;
260
- }
261
-
262
- // src/commands/list-builds.ts
263
254
  function buildRows(app) {
264
255
  return app.builds.map((b) => ({
265
256
  id: b.id,
@@ -2196,6 +2187,11 @@ function parseWatchSeconds(flag) {
2196
2187
  }
2197
2188
  return seconds;
2198
2189
  }
2190
+ function queuedLine(position, reasonMessage) {
2191
+ const where = position ? ` (position ${position})` : "";
2192
+ const why = reasonMessage ?? "Every device is busy.";
2193
+ return `\u23F3 ${why} Waiting in the queue${where}, Ctrl-C to give up.`;
2194
+ }
2199
2195
  function emit(json, evt) {
2200
2196
  if (json) console.log(JSON.stringify(evt));
2201
2197
  }
@@ -2232,7 +2228,9 @@ async function startDevSession(cfg, onEvent = () => {
2232
2228
  event: "queued",
2233
2229
  session_id: sessionId,
2234
2230
  position: started.position ?? null,
2235
- queue_entry_id: queueEntryId
2231
+ queue_entry_id: queueEntryId,
2232
+ queue_reason: started.queue_reason ?? null,
2233
+ message: queueReasonCopy(started.queue_reason)
2236
2234
  });
2237
2235
  await pollWhileQueued(api, sessionId, QUEUE_WAIT_MS);
2238
2236
  queueEntryId = null;
@@ -2323,6 +2321,10 @@ function findBlockingWarning(info) {
2323
2321
  return (info.warnings ?? []).find((w) => BLOCKING_WARNING_PREFIXES.some((p) => w.includes(p)));
2324
2322
  }
2325
2323
  var QUEUE_WAIT_MS = 30 * 6e4;
2324
+ function sessionEndedMessage(info, when) {
2325
+ const reason = info.status === "failed" && info.failure_reason ? `: ${info.failure_reason}` : "";
2326
+ return `session ${info.status} ${when}${reason}`;
2327
+ }
2326
2328
  async function pollWhileQueued(api, sessionId, timeoutMs, intervalMs = 3e3) {
2327
2329
  const deadline = Date.now() + timeoutMs;
2328
2330
  for (; ; ) {
@@ -2330,7 +2332,7 @@ async function pollWhileQueued(api, sessionId, timeoutMs, intervalMs = 3e3) {
2330
2332
  const blocking = findBlockingWarning(info);
2331
2333
  if (blocking) throw new Error(blocking);
2332
2334
  if (info.status === "stopped" || info.status === "failed") {
2333
- throw new Error(`session ${info.status} while waiting for a device`);
2335
+ throw new Error(sessionEndedMessage(info, "while waiting for a device"));
2334
2336
  }
2335
2337
  if (info.status !== "pending" && info.status !== "queued") return info;
2336
2338
  if (Date.now() > deadline) {
@@ -2349,7 +2351,7 @@ async function pollUntilTunnelReady(api, sessionId, timeoutMs, intervalMs = 2e3)
2349
2351
  if (blocking) throw new Error(blocking);
2350
2352
  if (info.tunnel_url) return info;
2351
2353
  if (info.status === "stopped" || info.status === "failed") {
2352
- throw new Error(`session ${info.status} before the tunnel came up`);
2354
+ throw new Error(sessionEndedMessage(info, "before the tunnel came up"));
2353
2355
  }
2354
2356
  if (Date.now() > deadline) throw new Error("timed out waiting for the device to start");
2355
2357
  await new Promise((r) => setTimeout(r, intervalMs));
@@ -2368,7 +2370,7 @@ async function pollUntilActive(api, sessionId, timeoutMs, intervalMs = 2e3, onIn
2368
2370
  }
2369
2371
  if (info.status === "active" && !info.app_install_pending) return info;
2370
2372
  if (info.status === "stopped" || info.status === "failed") {
2371
- throw new Error(`session ${info.status} before it became active`);
2373
+ throw new Error(sessionEndedMessage(info, "before it became active"));
2372
2374
  }
2373
2375
  if (Date.now() > deadline) {
2374
2376
  throw new Error("timed out waiting for the session to become active");
@@ -2418,6 +2420,19 @@ function validateAppChoice(requested, fromFlag, apps) {
2418
2420
  if (apps.some((a) => a.public_id === requested)) return { appId: requested };
2419
2421
  return fromFlag ? { staleFlag: requested } : { staleCached: requested };
2420
2422
  }
2423
+ function staleAppFlagError(requested, platform, allApps) {
2424
+ const other = allApps.find((a) => a.public_id === requested);
2425
+ if (other) {
2426
+ return {
2427
+ message: `app "${requested}" is ${platformWithArticle(other.platform)} app, not ${platformWithArticle(platform)} app.`,
2428
+ hint: `Run \`vibeview dev --platform ${other.platform} --app ${requested}\`, or \`vibeview dev --platform ${platform}\` without --app to pick ${platformWithArticle(platform)} app.`
2429
+ };
2430
+ }
2431
+ return {
2432
+ message: `app "${requested}" not found \u2014 it may have been deleted.`,
2433
+ hint: "Run `vibeview dev` without --app to pick from the list."
2434
+ };
2435
+ }
2421
2436
  function buildAppStrategy(apps, buildFlag, json, isTTY) {
2422
2437
  if (buildFlag) return "build";
2423
2438
  if (apps.length === 0) return "build";
@@ -2495,11 +2510,9 @@ async function ensureConfig(api, opts) {
2495
2510
  const apps = appsForPlatform(allApps, platform);
2496
2511
  const choice = validateAppChoice(opts.app || entry?.appId, Boolean(opts.app), apps);
2497
2512
  if (choice.staleFlag) {
2498
- const message = `app "${choice.staleFlag}" not found \u2014 it may have been deleted.`;
2499
- console.error(
2500
- chalk4.red(`error: ${message}`) + `
2501
- Run \`vibeview dev\` without --app to pick from the list.`
2502
- );
2513
+ const { message, hint } = staleAppFlagError(choice.staleFlag, platform, allApps);
2514
+ console.error(chalk4.red(`error: ${message}`) + `
2515
+ ${hint}`);
2503
2516
  emit(opts.json, { event: "error", message });
2504
2517
  process.exit(2);
2505
2518
  }
@@ -2806,12 +2819,7 @@ async function runDevInProcess(opts) {
2806
2819
  console.error(chalk4.yellow(`\u26A0 ${evt.message}`));
2807
2820
  }
2808
2821
  } else if (evt.event === "queued") {
2809
- const where = evt.position ? ` (position ${evt.position})` : "";
2810
- console.error(
2811
- chalk4.yellow(
2812
- `\u23F3 every device is busy \u2014 waiting in the queue${where}, Ctrl-C to give up`
2813
- )
2814
- );
2822
+ console.error(chalk4.yellow(queuedLine(evt.position, evt.message)));
2815
2823
  } else if (evt.event === "tunnel_connected") {
2816
2824
  everConnected = true;
2817
2825
  console.error(chalk4.green("\u2713 tunnel connected \u2014 edit away, Fast Refresh is live"));
@@ -3056,7 +3064,7 @@ function formatDevicesHuman(devices) {
3056
3064
  const fmt = (cells) => cells.map((c, i) => c.padEnd(widths[i])).join(" ").trimEnd();
3057
3065
  const lines = [chalk5.bold(fmt(HEADERS2))];
3058
3066
  for (const row of rows.sort(rowSort)) {
3059
- const statusColored = row[5] === "available" ? chalk5.green(row[5]) : row[5] === "busy" ? chalk5.yellow(row[5]) : chalk5.dim(row[5]);
3067
+ const statusColored = row[5] === "available" ? chalk5.green(row[5]) : row[5] === "busy" || row[5] === "updating" ? chalk5.yellow(row[5]) : chalk5.dim(row[5]);
3060
3068
  lines.push(fmt([row[0], row[1], row[2], row[3], row[4], statusColored]));
3061
3069
  }
3062
3070
  return lines.join("\n") + "\n";
@@ -3087,25 +3095,65 @@ function formatDeviceModelsHuman(models) {
3087
3095
  }
3088
3096
 
3089
3097
  // src/agent/registry.ts
3090
- var PRESS_BUTTONS_BY_PLATFORM = [
3091
- ["iPhone / iPad", ["home", "back", "lock", "siri"]],
3092
- ["Android phone / tablet", ["home", "back", "lock"]],
3093
- [
3094
- "Apple TV / Android TV",
3095
- ["dpad_up", "dpad_down", "dpad_left", "dpad_right", "dpad_center", "back", "home"]
3098
+ var PRESS_BUTTONS = {
3099
+ ios: ["home", "back", "lock", "siri", "enter"],
3100
+ android: ["home", "back", "lock", "enter"],
3101
+ appleTv: [
3102
+ "dpad_up",
3103
+ "dpad_down",
3104
+ "dpad_left",
3105
+ "dpad_right",
3106
+ "dpad_center",
3107
+ "back",
3108
+ "home",
3109
+ "menu"
3110
+ ],
3111
+ androidTv: [
3112
+ "dpad_up",
3113
+ "dpad_down",
3114
+ "dpad_left",
3115
+ "dpad_right",
3116
+ "dpad_center",
3117
+ "back",
3118
+ "home",
3119
+ "enter"
3096
3120
  ],
3097
- [
3098
- "Roku (beta)",
3099
- ["up", "down", "left", "right", "select", "back", "(dpad_* also work; home is not forwarded)"]
3121
+ roku: [
3122
+ "dpad_up",
3123
+ "dpad_down",
3124
+ "dpad_left",
3125
+ "dpad_right",
3126
+ "dpad_center",
3127
+ "back",
3128
+ "enter",
3129
+ "up",
3130
+ "down",
3131
+ "left",
3132
+ "right",
3133
+ "select",
3134
+ "backspace",
3135
+ "play",
3136
+ "rewind",
3137
+ "forward",
3138
+ "replay",
3139
+ "info"
3100
3140
  ]
3141
+ };
3142
+ var PRESS_PLATFORM_LABELS = [
3143
+ ["ios", "iPhone / iPad"],
3144
+ ["android", "Android phone / tablet"],
3145
+ ["appleTv", "Apple TV"],
3146
+ ["androidTv", "Android TV"],
3147
+ ["roku", "Roku (beta)"]
3101
3148
  ];
3149
+ var PRESS_BUTTONS_BY_PLATFORM = PRESS_PLATFORM_LABELS.map(([key, label]) => [label, PRESS_BUTTONS[key]]);
3150
+ function pressButtonLines(indent) {
3151
+ return PRESS_BUTTONS_BY_PLATFORM.map(
3152
+ ([platform, buttons]) => `${indent}${platform}: ${buttons.join(", ")}`
3153
+ );
3154
+ }
3102
3155
  function pressButtonsHelp() {
3103
- return [
3104
- "Valid buttons:",
3105
- ...PRESS_BUTTONS_BY_PLATFORM.map(
3106
- ([platform, buttons]) => ` ${platform}: ${buttons.join(", ")}`
3107
- )
3108
- ].join("\n");
3156
+ return ["Valid buttons:", ...pressButtonLines(" ")].join("\n");
3109
3157
  }
3110
3158
  var AGENT_COMMANDS = [
3111
3159
  {
@@ -3401,7 +3449,7 @@ var AGENT_COMMANDS = [
3401
3449
  cli: "positional",
3402
3450
  type: "string",
3403
3451
  required: true,
3404
- description: "Button to press. iPhone/iPad: home, back, lock, siri. Android: home, back, lock. Apple TV / Android TV: dpad_up, dpad_down, dpad_left, dpad_right, dpad_center, back, home. Roku: up, down, left, right, select, back (dpad_* also work; home is not forwarded)."
3452
+ description: `Button to press. ${pressButtonLines("").join(". ")}.`
3405
3453
  }
3406
3454
  ]
3407
3455
  },
@@ -3883,7 +3931,6 @@ export {
3883
3931
  loadDevConfig,
3884
3932
  parseBuildId,
3885
3933
  checkDevBuild,
3886
- formatUtcTimestamp,
3887
3934
  buildRows,
3888
3935
  formatBuildRowsHuman,
3889
3936
  listBuilds,
@@ -42,6 +42,32 @@ 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
+
46
+ // src/lib/formatters/time.ts
47
+ function formatUtcTimestamp(iso) {
48
+ if (!iso) return "";
49
+ const hasZone = /(?:Z|[+-]\d{2}:?\d{2})$/i.test(iso);
50
+ const d = new Date(hasZone ? iso : `${iso}Z`);
51
+ if (Number.isNaN(d.getTime())) return iso;
52
+ return `${d.toISOString().slice(0, 19).replace("T", " ")} UTC`;
53
+ }
54
+ var NAIVE_ISO_DATETIME = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?$/;
55
+ function withUtcTimestamps(value) {
56
+ if (typeof value === "string") {
57
+ return NAIVE_ISO_DATETIME.test(value) ? `${value}Z` : value;
58
+ }
59
+ if (Array.isArray(value)) return value.map((v) => withUtcTimestamps(v));
60
+ if (value !== null && typeof value === "object") {
61
+ const out = {};
62
+ for (const [k, v] of Object.entries(value)) {
63
+ out[k] = withUtcTimestamps(v);
64
+ }
65
+ return out;
66
+ }
67
+ return value;
68
+ }
69
+
70
+ // src/lib/api-client.ts
45
71
  var AuthError = class extends Error {
46
72
  constructor(message = "Authentication failed. Check your API token.") {
47
73
  super(message);
@@ -53,10 +79,14 @@ function errorExitCode(err, otherwise = 1) {
53
79
  }
54
80
  function reportCommandError(json, err) {
55
81
  const message = err instanceof Error ? err.message : String(err);
56
- if (json) console.log(JSON.stringify({ event: "error", message }));
82
+ if (json) printJsonError(err);
57
83
  process.stderr.write(`error: ${message}
58
84
  `);
59
85
  }
86
+ function printJsonError(err) {
87
+ const message = err instanceof Error ? err.message : String(err);
88
+ console.log(JSON.stringify({ event: "error", message }));
89
+ }
60
90
  var NotFoundError = class extends Error {
61
91
  constructor(message) {
62
92
  super(message);
@@ -75,7 +105,7 @@ function extractDetail(text) {
75
105
  }
76
106
  async function parseJsonBody(resp) {
77
107
  if (resp.status === 204 || resp.status === 205) return void 0;
78
- return resp.json();
108
+ return withUtcTimestamps(await resp.json());
79
109
  }
80
110
  var ApiClient = class {
81
111
  constructor(baseUrl, token) {
@@ -663,6 +693,16 @@ var UsageError = class extends Error {
663
693
  this.name = "UsageError";
664
694
  }
665
695
  };
696
+ var DEVICE_STATUS_FILTERS = ["available", "busy", "updating"];
697
+ var QUEUE_REASON_COPY = {
698
+ devices_busy: "All devices are in use right now.",
699
+ devices_updating: "Devices are being updated and will be back shortly.",
700
+ plan_limit: "The most sessions this app can run at once are already running.",
701
+ link_limit: "The most people who can play at once are already playing."
702
+ };
703
+ function queueReasonCopy(reason) {
704
+ return reason ? QUEUE_REASON_COPY[reason] ?? null : null;
705
+ }
666
706
 
667
707
  // src/commands/upload-app.ts
668
708
  async function uploadAppBuild(filePath, options) {
@@ -746,9 +786,11 @@ export {
746
786
  loadConfig,
747
787
  saveConfig,
748
788
  resolveAuth,
789
+ formatUtcTimestamp,
749
790
  AuthError,
750
791
  errorExitCode,
751
792
  reportCommandError,
793
+ printJsonError,
752
794
  NotFoundError,
753
795
  ApiClient,
754
796
  createSpinner,
@@ -756,6 +798,8 @@ export {
756
798
  EXIT_TEST_FAIL,
757
799
  EXIT_INFRA_ERROR,
758
800
  UsageError,
801
+ DEVICE_STATUS_FILTERS,
802
+ queueReasonCopy,
759
803
  uploadAppBuild,
760
804
  describeBuild,
761
805
  uploadApp
package/dist/cli.js CHANGED
@@ -11,7 +11,6 @@ import {
11
11
  formatDeviceModelsHuman,
12
12
  formatDevicesHuman,
13
13
  formatDuration,
14
- formatUtcTimestamp,
15
14
  killOwnedMetro,
16
15
  listBuilds,
17
16
  loadDevConfig,
@@ -23,9 +22,10 @@ import {
23
22
  readDevState,
24
23
  readMetroState,
25
24
  runAgentVerb
26
- } from "./chunk-46X7UN6Y.js";
25
+ } from "./chunk-7IOGMM5W.js";
27
26
  import {
28
27
  ApiClient,
28
+ DEVICE_STATUS_FILTERS,
29
29
  EXIT_INFRA_ERROR,
30
30
  EXIT_PASS,
31
31
  EXIT_TEST_FAIL,
@@ -33,12 +33,14 @@ import {
33
33
  UsageError,
34
34
  createSpinner,
35
35
  errorExitCode,
36
+ formatUtcTimestamp,
36
37
  loadConfig,
38
+ printJsonError,
37
39
  reportCommandError,
38
40
  resolveAuth,
39
41
  saveConfig,
40
42
  uploadApp
41
- } from "./chunk-U7FCGMWG.js";
43
+ } from "./chunk-FTTWLCTE.js";
42
44
 
43
45
  // src/cli.ts
44
46
  import { createRequire } from "module";
@@ -52,6 +54,26 @@ import chalk2 from "chalk";
52
54
 
53
55
  // src/lib/formatters/human.ts
54
56
  import chalk from "chalk";
57
+
58
+ // src/lib/formatters/cases.ts
59
+ function resultCases(result) {
60
+ if (Array.isArray(result.test_runs)) return result.test_runs;
61
+ const failedStep = result.steps?.find((s) => s.status !== "passed");
62
+ return [
63
+ {
64
+ case_name: result.case_name || result.run_id,
65
+ status: result.status,
66
+ duration_ms: result.duration_ms,
67
+ error_message: result.error_message ?? failedStep?.error ?? null,
68
+ error_kind: failedStep?.error_kind ?? null,
69
+ error_kind_description: failedStep?.error_kind_description ?? null,
70
+ screenshot_signed_url: failedStep?.screenshot_signed_url ?? null,
71
+ url: result.url
72
+ }
73
+ ];
74
+ }
75
+
76
+ // src/lib/formatters/human.ts
55
77
  function failureLines(failure) {
56
78
  const lines = [];
57
79
  if (failure.error_kind) {
@@ -77,15 +99,17 @@ function formatHuman(result, verbose) {
77
99
  } else {
78
100
  lines.push(` ${icon} ${chalk.bold(result.run_id)} ${chalk.dim(duration)}`);
79
101
  }
80
- const passed = result.test_runs.filter((t) => t.status === "passed").length;
81
- const total = result.test_runs.length;
102
+ const cases = resultCases(result);
103
+ const passed = cases.filter((t) => t.status === "passed").length;
104
+ const total = cases.length;
82
105
  lines.push(` ${chalk.dim(`${passed}/${total} cases passed`)}`);
83
106
  if (result.url) {
84
107
  lines.push(` ${chalk.dim(result.url)}`);
85
108
  }
86
- if (result.test_runs.length > 0) {
109
+ const suiteCases = result.test_runs ?? [];
110
+ if (suiteCases.length > 0) {
87
111
  lines.push("");
88
- for (const tr of result.test_runs) {
112
+ for (const tr of suiteCases) {
89
113
  const caseIcon = tr.status === "passed" ? chalk.green(" PASS") : tr.status === "cancelled" || tr.status === "pending" || tr.status === "running" ? chalk.yellow(" SKIP") : chalk.red(" FAIL");
90
114
  const caseDuration = tr.duration_ms ? `${(tr.duration_ms / 1e3).toFixed(1)}s` : "-";
91
115
  lines.push(` ${caseIcon} ${tr.case_name} ${chalk.dim(caseDuration)}`);
@@ -134,10 +158,11 @@ function escapeXml(s) {
134
158
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
135
159
  }
136
160
  function formatJunit(result) {
137
- const suiteName = escapeXml(result.suite_name || result.run_id);
138
- const tests = result.test_runs.length;
139
- const failures = result.test_runs.filter((t) => t.status === "failed").length;
140
- const errors = result.test_runs.filter((t) => t.status === "error").length;
161
+ const suiteName = escapeXml(result.suite_name || result.case_name || result.run_id);
162
+ const cases = resultCases(result);
163
+ const tests = cases.length;
164
+ const failures = cases.filter((t) => t.status === "failed").length;
165
+ const errors = cases.filter((t) => t.status === "error").length;
141
166
  const totalTime = result.duration_ms ? (result.duration_ms / 1e3).toFixed(1) : "0";
142
167
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
143
168
  const lines = [];
@@ -148,7 +173,7 @@ function formatJunit(result) {
148
173
  lines.push(
149
174
  ` <testsuite name="${suiteName}" tests="${tests}" failures="${failures}" errors="${errors}" time="${totalTime}" timestamp="${timestamp}">`
150
175
  );
151
- for (const tr of result.test_runs) {
176
+ for (const tr of cases) {
152
177
  const caseName = escapeXml(tr.case_name);
153
178
  const caseTime = tr.duration_ms ? (tr.duration_ms / 1e3).toFixed(1) : "0";
154
179
  if (tr.status === "passed") {
@@ -505,6 +530,7 @@ ${chalk3.dim("Run:")} ${response.run_id}
505
530
  process.exit(mapExitCode(result.status));
506
531
  } catch (err) {
507
532
  spinner.fail(err instanceof Error ? err.message : "Unknown error");
533
+ if (options.output === "json") printJsonError(err);
508
534
  if (lastQueueEntryId !== void 0) {
509
535
  try {
510
536
  await client.cancelQueueEntry(lastQueueEntryId);
@@ -593,10 +619,13 @@ ${signal} received.
593
619
  const msg = err instanceof Error ? err.message : String(err);
594
620
  if (msg.startsWith("Not found:") || msg.includes("404") || /No suites match tags/.test(msg)) {
595
621
  const suffix = opts.platform ? ` on platform ${opts.platform}` : "";
596
- process.stderr.write(chalk4.yellow(`No suites match tags: ${opts.tag.join(", ")}${suffix}
622
+ const noMatch = `No suites match tags: ${opts.tag.join(", ")}${suffix}`;
623
+ if (opts.output === "json") printJsonError(noMatch);
624
+ process.stderr.write(chalk4.yellow(`${noMatch}
597
625
  `));
598
626
  process.exit(EXIT_INFRA_ERROR);
599
627
  }
628
+ if (opts.output === "json") printJsonError(err);
600
629
  process.stderr.write(chalk4.red(`Failed to start: ${msg}
601
630
  `));
602
631
  process.exit(EXIT_INFRA_ERROR);
@@ -680,6 +709,7 @@ ${chalk4.dim("Matched:")} ${totalMatched} suite(s)${matchedNames.length ? " \u20
680
709
  process.stdin.pause();
681
710
  }
682
711
  if (!last) {
712
+ if (opts.output === "json") printJsonError("Timed out before any status was returned.");
683
713
  process.stderr.write(chalk4.red("Timed out before any status was returned.\n"));
684
714
  process.exit(EXIT_INFRA_ERROR);
685
715
  }
@@ -817,6 +847,7 @@ ${chalk5.dim("Run:")} ${response.run_id}
817
847
  process.exit(mapExitCode(result.status));
818
848
  } catch (err) {
819
849
  spinner.fail(err instanceof Error ? err.message : "Unknown error");
850
+ if (options.output === "json") printJsonError(err);
820
851
  if (lastQueueEntryId !== void 0) {
821
852
  try {
822
853
  await client.cancelQueueEntry(lastQueueEntryId);
@@ -3291,7 +3322,12 @@ program.command("list-devices").description("List devices registered with the pl
3291
3322
  new Option("--type <type>", "Filter by device type").choices(["ios", "android", "roku"])
3292
3323
  ).addOption(
3293
3324
  new Option("--category <category>", "Filter by category").choices(["phone", "tablet", "tv"])
3294
- ).addOption(new Option("--status <status>", "Filter by status").choices(["available", "busy"])).option(
3325
+ ).addOption(
3326
+ new Option(
3327
+ "--status <status>",
3328
+ "Filter by status (updating = being updated, takes no new session yet)"
3329
+ ).choices([...DEVICE_STATUS_FILTERS])
3330
+ ).option(
3295
3331
  "--models",
3296
3332
  "List the device models you can start instead (the names `dev --model` takes)",
3297
3333
  false
@@ -3465,7 +3501,7 @@ program.command("dev-reload").description(
3465
3501
  program.command("mcp").description(
3466
3502
  "Run an MCP stdio server exposing the agent verbs (tap, swipe, type, ...) plus dev_start/dev_stop/dev_reload as tools"
3467
3503
  ).option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").action(async (opts) => {
3468
- const { runMcpServer } = await import("./server-XJREB7KU.js");
3504
+ const { runMcpServer } = await import("./server-ZIUZ7U5T.js");
3469
3505
  await runMcpServer({ ...opts, version: pkg.version });
3470
3506
  });
3471
3507
  program.command("agent-setup").description(
@@ -21,11 +21,11 @@ import {
21
21
  readDevState,
22
22
  renderEnvelope,
23
23
  startDevSession
24
- } from "./chunk-46X7UN6Y.js";
24
+ } from "./chunk-7IOGMM5W.js";
25
25
  import {
26
26
  ApiClient,
27
27
  resolveAuth
28
- } from "./chunk-U7FCGMWG.js";
28
+ } from "./chunk-FTTWLCTE.js";
29
29
 
30
30
  // src/mcp/server.ts
31
31
  import { writeFileSync } from "fs";
@@ -377,7 +377,7 @@ async function handleUploadApp(args, deps) {
377
377
  }
378
378
  const name = typeof args.name === "string" && args.name ? args.name : void 0;
379
379
  const upload = deps.uploadApp ?? (async (f, n) => {
380
- const { uploadAppBuild } = await import("./upload-app-QUUXKNR2.js");
380
+ const { uploadAppBuild } = await import("./upload-app-MDKTSG4O.js");
381
381
  const { result: result2 } = await uploadAppBuild(f, { ...deps.auth, name: n });
382
382
  return result2;
383
383
  });
@@ -3,7 +3,7 @@ import {
3
3
  describeBuild,
4
4
  uploadApp,
5
5
  uploadAppBuild
6
- } from "./chunk-U7FCGMWG.js";
6
+ } from "./chunk-FTTWLCTE.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.27.0",
3
+ "version": "0.27.1",
4
4
  "description": "VibeView CLI -- run mobile tests from CI/CD pipelines",
5
5
  "mcpName": "io.github.vibeview/vibeview",
6
6
  "type": "module",