vibeview 0.37.0 → 0.38.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
@@ -25,7 +25,7 @@ import {
25
25
  readMetroState,
26
26
  recordDevUse,
27
27
  runAgentVerb
28
- } from "./chunk-6LNOXEPC.js";
28
+ } from "./chunk-AV674D7T.js";
29
29
  import {
30
30
  ApiClient,
31
31
  ApiStatusError,
@@ -46,7 +46,7 @@ import {
46
46
  resolveAuth,
47
47
  saveConfig,
48
48
  uploadApp
49
- } from "./chunk-QRJAFBH3.js";
49
+ } from "./chunk-MYXUGCR5.js";
50
50
 
51
51
  // src/cli.ts
52
52
  import { createRequire } from "module";
@@ -90,6 +90,7 @@ function resultCases(result) {
90
90
  error_kind_description: failedStep?.error_kind_description ?? null,
91
91
  screenshot_signed_url: failedStep?.screenshot_signed_url ?? null,
92
92
  url: result.url,
93
+ quarantined: result.quarantined,
93
94
  steps_not_checked: runStepsNotChecked(result),
94
95
  not_checked_steps: notCheckedSteps(result.steps).map((s) => ({
95
96
  step: s.step,
@@ -117,7 +118,7 @@ function failureLines(failure) {
117
118
  }
118
119
  function formatHuman(result, verbose) {
119
120
  const lines = [];
120
- const icon = result.status === "passed" ? chalk.green("PASS") : chalk.red("FAIL");
121
+ const icon = result.status === "passed" ? chalk.green("PASS") : result.status === "failed" && result.quarantined === true ? chalk.yellow("FAIL (quarantined)") : chalk.red("FAIL");
121
122
  const duration = result.duration_ms ? `${(result.duration_ms / 1e3).toFixed(1)}s` : "-";
122
123
  lines.push("");
123
124
  const title = result.suite_name || result.case_name || result.run_id;
@@ -138,7 +139,7 @@ function formatHuman(result, verbose) {
138
139
  if (suiteCases.length > 0) {
139
140
  lines.push("");
140
141
  for (const tr of suiteCases) {
141
- const caseIcon = tr.status === "passed" ? chalk.green(" PASS") : tr.status === "cancelled" || tr.status === "pending" || tr.status === "running" ? chalk.yellow(" SKIP") : chalk.red(" FAIL");
142
+ const caseIcon = tr.status === "passed" ? chalk.green(" PASS") : tr.status === "failed" && tr.quarantined === true ? chalk.yellow(" FAIL (quarantined)") : tr.status === "cancelled" || tr.status === "pending" || tr.status === "running" ? chalk.yellow(" SKIP") : chalk.red(" FAIL");
142
143
  const caseDuration = tr.duration_ms ? `${(tr.duration_ms / 1e3).toFixed(1)}s` : "-";
143
144
  const caseSuffix = notCheckedSuffix(tr.status, tr.steps_not_checked);
144
145
  lines.push(
@@ -214,21 +215,24 @@ function caseDetailLines(c) {
214
215
  if (out.length) lines.push(` <system-out>${escapeXml(out.join("\n"))}</system-out>`);
215
216
  return lines;
216
217
  }
218
+ var isQuarantinedFailure = (t) => t.status === "failed" && t.quarantined === true;
217
219
  function formatJunit(result) {
218
220
  const suiteName = escapeXml(result.suite_name || result.case_name || result.run_id);
219
221
  const cases = resultCases(result);
220
222
  const tests = cases.length;
221
- const failures = cases.filter((t) => t.status === "failed").length;
223
+ const failures = cases.filter((t) => t.status === "failed" && !isQuarantinedFailure(t)).length;
222
224
  const errors = cases.filter((t) => t.status === "error").length;
225
+ const skipped = cases.filter(isQuarantinedFailure).length;
226
+ const skippedAttr = skipped > 0 ? ` skipped="${skipped}"` : "";
223
227
  const totalTime = result.duration_ms ? (result.duration_ms / 1e3).toFixed(1) : "0";
224
228
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
225
229
  const lines = [];
226
230
  lines.push('<?xml version="1.0" encoding="UTF-8"?>');
227
231
  lines.push(
228
- `<testsuites name="vibeview" tests="${tests}" failures="${failures}" errors="${errors}" time="${totalTime}">`
232
+ `<testsuites name="vibeview" tests="${tests}" failures="${failures}" errors="${errors}"${skippedAttr} time="${totalTime}">`
229
233
  );
230
234
  lines.push(
231
- ` <testsuite name="${suiteName}" tests="${tests}" failures="${failures}" errors="${errors}" time="${totalTime}" timestamp="${timestamp}">`
235
+ ` <testsuite name="${suiteName}" tests="${tests}" failures="${failures}" errors="${errors}"${skippedAttr} time="${totalTime}" timestamp="${timestamp}">`
232
236
  );
233
237
  if (result.url || result.build_warning) {
234
238
  lines.push(" <properties>");
@@ -251,7 +255,10 @@ function formatJunit(result) {
251
255
  continue;
252
256
  }
253
257
  lines.push(` <testcase name="${caseName}" classname="${suiteName}" time="${caseTime}">`);
254
- if (tr.status === "failed") {
258
+ if (isQuarantinedFailure(tr)) {
259
+ const msg = escapeXml(tr.error_message || "Test failed");
260
+ lines.push(` <skipped message="quarantined: ${msg}" />`);
261
+ } else if (tr.status === "failed") {
255
262
  const msg = escapeXml(tr.error_message || "Test failed");
256
263
  lines.push(` <failure message="${msg}" type="TestFailure">${msg}</failure>`);
257
264
  } else if (tr.status !== "passed") {
@@ -324,6 +331,13 @@ function formatMultiSuiteJunit(result, opts = {}) {
324
331
  url: c.url || void 0,
325
332
  steps_not_checked: c.steps_not_checked
326
333
  };
334
+ if (c.status === "failed" && c.quarantined === true) {
335
+ return {
336
+ ...base,
337
+ verdict: "skipped",
338
+ message: `quarantined: ${c.error_message || "Test failed"}`
339
+ };
340
+ }
327
341
  switch (c.status) {
328
342
  case "passed":
329
343
  return { ...base, verdict: "passed" };
@@ -699,10 +713,26 @@ function mapExitCode(status) {
699
713
  if (status === "failed") return EXIT_TEST_FAIL;
700
714
  return EXIT_INFRA_ERROR;
701
715
  }
716
+ function isQuarantinedFailure2(result) {
717
+ return result.status === "failed" && result.quarantined === true;
718
+ }
719
+ function runExitCode(result) {
720
+ return isQuarantinedFailure2(result) ? EXIT_PASS : mapExitCode(result.status);
721
+ }
702
722
 
703
723
  // src/lib/run-target.ts
704
724
  import chalk3 from "chalk";
705
725
  var RUN_PLATFORMS = ["ios", "android", "tvos", "androidtv"];
726
+ var SUITE_FILTER_PLATFORMS = [...RUN_PLATFORMS, "roku"];
727
+ function parseSuitePlatformFilter(value) {
728
+ if (value === void 0) return void 0;
729
+ if (!SUITE_FILTER_PLATFORMS.includes(value)) {
730
+ throw new Error(
731
+ `--platform must be one of ${SUITE_FILTER_PLATFORMS.join(", ")}, got "${value}"`
732
+ );
733
+ }
734
+ return value;
735
+ }
706
736
  function parseRunPlatform(value) {
707
737
  if (value === void 0) return void 0;
708
738
  if (!RUN_PLATFORMS.includes(value)) {
@@ -802,6 +832,7 @@ ${signal} received.
802
832
  device: options.device,
803
833
  build: options.build,
804
834
  metadata: options.metadata,
835
+ retryFailed: options.retry === false ? false : void 0,
805
836
  platform: options.platform,
806
837
  app: options.app
807
838
  });
@@ -838,7 +869,7 @@ ${signal} received.
838
869
  response.build_warning ? { ...result, build_warning: response.build_warning } : result,
839
870
  options
840
871
  );
841
- process.exit(mapExitCode(result.status));
872
+ process.exit(runExitCode(result));
842
873
  } catch (err) {
843
874
  spinner.fail(err instanceof Error ? err.message : "Unknown error");
844
875
  outputRunError(err, options, {
@@ -928,7 +959,8 @@ ${signal} received.
928
959
  device_category: opts.device ? void 0 : opts.deviceCategory,
929
960
  device_id: opts.device,
930
961
  build_id: opts.build,
931
- metadata: opts.metadata
962
+ metadata: opts.metadata,
963
+ retry_failed: opts.retry === false ? false : void 0
932
964
  });
933
965
  } catch (err) {
934
966
  spinner.stop();
@@ -1206,6 +1238,7 @@ ${signal} received.
1206
1238
  device: options.device,
1207
1239
  build: options.build,
1208
1240
  metadata: options.metadata,
1241
+ retryFailed: options.retry === false ? false : void 0,
1209
1242
  platform: options.platform,
1210
1243
  app: options.app
1211
1244
  });
@@ -1243,7 +1276,7 @@ ${chalk6.dim("Test:")} ${testId}
1243
1276
  response.build_warning ? { ...result, build_warning: response.build_warning } : result,
1244
1277
  options
1245
1278
  );
1246
- process.exit(mapExitCode(result.status));
1279
+ process.exit(runExitCode(result));
1247
1280
  } catch (err) {
1248
1281
  spinner.fail(err instanceof Error ? err.message : "Unknown error");
1249
1282
  outputRunError(err, options, {
@@ -1383,11 +1416,11 @@ async function stopCommand(sessionId, opts, deps) {
1383
1416
  import chalk10 from "chalk";
1384
1417
  import { existsSync, readFileSync } from "fs";
1385
1418
  async function readStdin() {
1386
- return new Promise((resolve, reject) => {
1419
+ return new Promise((resolve2, reject) => {
1387
1420
  let data = "";
1388
1421
  const timeout = setTimeout(() => {
1389
1422
  process.stdin.removeAllListeners();
1390
- resolve(data);
1423
+ resolve2(data);
1391
1424
  }, 5e3);
1392
1425
  process.stdin.setEncoding("utf-8");
1393
1426
  process.stdin.on("data", (chunk) => {
@@ -1395,7 +1428,7 @@ async function readStdin() {
1395
1428
  });
1396
1429
  process.stdin.on("end", () => {
1397
1430
  clearTimeout(timeout);
1398
- resolve(data);
1431
+ resolve2(data);
1399
1432
  });
1400
1433
  process.stdin.on("error", (err) => {
1401
1434
  clearTimeout(timeout);
@@ -1439,14 +1472,16 @@ function assertHasSteps(steps) {
1439
1472
  async function resolveAppId(client, appRef, platform) {
1440
1473
  if (appRef.includes(".")) {
1441
1474
  if (!platform) {
1442
- throw new Error(
1475
+ throw new UsageError(
1443
1476
  "When using a package name for --app, you must also specify --platform (ios, android)."
1444
1477
  );
1445
1478
  }
1446
1479
  const apps = await client.listApps();
1447
1480
  const match = apps.find((a) => a.package_name === appRef && a.platform === platform);
1448
1481
  if (!match) {
1449
- throw new Error(`No app found with package name "${appRef}" and platform "${platform}".`);
1482
+ throw new UsageError(
1483
+ `No app found with package name "${appRef}" and platform "${platform}".`
1484
+ );
1450
1485
  }
1451
1486
  return match.public_id;
1452
1487
  }
@@ -1470,7 +1505,8 @@ async function createTest(options) {
1470
1505
  steps,
1471
1506
  context: options.context,
1472
1507
  mode: "ai",
1473
- if_exists: options.ifExists
1508
+ if_exists: options.ifExists,
1509
+ group_ids: options.group?.length ? options.group : void 0
1474
1510
  });
1475
1511
  spinner.succeed(
1476
1512
  result.outcome === "updated" ? `Test updated: ${result.name}` : result.outcome === "unchanged" ? `Test already exists, unchanged: ${result.name}` : `Test created: ${result.name}`
@@ -1483,6 +1519,7 @@ async function createTest(options) {
1483
1519
  name: result.name,
1484
1520
  app_id: appPublicId || null,
1485
1521
  outcome: result.outcome ?? null,
1522
+ groups: result.groups ?? [],
1486
1523
  url: testCaseUrl(baseUrl, result.public_id)
1487
1524
  },
1488
1525
  null,
@@ -1498,6 +1535,9 @@ async function createTest(options) {
1498
1535
  if (result.platform) {
1499
1536
  console.log(` ${chalk10.dim(`Platform: ${result.platform}`)}`);
1500
1537
  }
1538
+ if (result.groups?.length) {
1539
+ console.log(` ${chalk10.dim(`Groups: ${result.groups.map((g) => g.name).join(", ")}`)}`);
1540
+ }
1501
1541
  console.log(` ${chalk10.dim(`Steps: ${steps.length}`)}`);
1502
1542
  console.log(chalk10.dim(` ${testCaseUrl(baseUrl, result.public_id)}
1503
1543
  `));
@@ -1511,6 +1551,153 @@ async function createTest(options) {
1511
1551
  }
1512
1552
  }
1513
1553
 
1554
+ // src/commands/import-maestro.ts
1555
+ import chalk11 from "chalk";
1556
+ import { existsSync as existsSync2, readFileSync as readFileSync2, readdirSync, statSync } from "fs";
1557
+ import { basename, extname, join, relative, resolve, sep } from "path";
1558
+ import { zipSync } from "fflate";
1559
+ var MAX_FOLDER_FLOWS = 200;
1560
+ var MAX_FLOW_BYTES = 256 * 1024;
1561
+ var MAX_TOTAL_BYTES = 5 * 1024 * 1024;
1562
+ var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git"]);
1563
+ function readMaestroUpload(path) {
1564
+ if (!existsSync2(path)) throw new UsageError(`Not found: ${path}`);
1565
+ if (statSync(path).isFile()) {
1566
+ const ext = extname(path).toLowerCase();
1567
+ if (![".yaml", ".yml", ".zip"].includes(ext)) {
1568
+ throw new UsageError(
1569
+ "Pass a Maestro flow (.yaml or .yml), a .zip of your flows folder, or the folder itself."
1570
+ );
1571
+ }
1572
+ const size = statSync(path).size;
1573
+ const cap = ext === ".zip" ? MAX_TOTAL_BYTES : MAX_FLOW_BYTES;
1574
+ if (size > cap) {
1575
+ throw new UsageError(`${path} is larger than ${cap / 1024} KB.`);
1576
+ }
1577
+ return { name: basename(path), bytes: readFileSync2(path) };
1578
+ }
1579
+ const root = resolve(path);
1580
+ const entries = {};
1581
+ let count = 0;
1582
+ let total = 0;
1583
+ const walk = (dir) => {
1584
+ for (const ent of readdirSync(dir, { withFileTypes: true })) {
1585
+ if (ent.isSymbolicLink()) continue;
1586
+ const full = join(dir, ent.name);
1587
+ if (ent.isDirectory()) {
1588
+ if (!SKIP_DIRS.has(ent.name)) walk(full);
1589
+ continue;
1590
+ }
1591
+ if (!ent.isFile() || !/\.ya?ml$/i.test(ent.name)) continue;
1592
+ const rel = relative(root, full).split(sep).join("/");
1593
+ const bytes2 = readFileSync2(full);
1594
+ count += 1;
1595
+ total += bytes2.length;
1596
+ if (count > MAX_FOLDER_FLOWS) {
1597
+ throw new UsageError(`${path} holds more than ${MAX_FOLDER_FLOWS} flow files.`);
1598
+ }
1599
+ if (bytes2.length > MAX_FLOW_BYTES) {
1600
+ throw new UsageError(`${rel} is larger than ${MAX_FLOW_BYTES / 1024} KB.`);
1601
+ }
1602
+ if (total > MAX_TOTAL_BYTES) {
1603
+ throw new UsageError(`The flows in ${path} add up to more than 5 MB.`);
1604
+ }
1605
+ entries[rel] = bytes2;
1606
+ }
1607
+ };
1608
+ walk(root);
1609
+ if (count === 0) throw new UsageError(`No .yaml or .yml flows found in ${path}.`);
1610
+ const bytes = zipSync(entries);
1611
+ if (bytes.length > MAX_TOTAL_BYTES) {
1612
+ throw new UsageError(`The zipped flows in ${path} are larger than 5 MB.`);
1613
+ }
1614
+ return { name: `${basename(root) || "flows"}.zip`, bytes };
1615
+ }
1616
+ var FLAG_LABEL = {
1617
+ rewrite: "needs rewrite",
1618
+ check: "check",
1619
+ dropped: "not imported"
1620
+ };
1621
+ var START_STATE_LABEL = {
1622
+ reinstall: "Fresh install",
1623
+ clear_data: "Clean app data",
1624
+ relaunch: "Restart the app",
1625
+ none: "Where the last test stopped"
1626
+ };
1627
+ function flagLine(f) {
1628
+ const where = [f.file, f.where, f.command].filter(Boolean).join(" \xB7 ");
1629
+ const label = f.severity === "rewrite" ? chalk11.red(FLAG_LABEL.rewrite) : FLAG_LABEL[f.severity];
1630
+ return ` ${label} ${chalk11.dim(where)} ${f.message}`;
1631
+ }
1632
+ function formatImport(result, baseUrl) {
1633
+ const lines = [];
1634
+ const tests = result.tests.filter((t) => t.importable);
1635
+ if (result.created === null) {
1636
+ lines.push(
1637
+ `Preview: ${tests.length} test(s) and ${result.setups.length} setup(s) would be created. Nothing was saved; run again without --dry-run to import.`
1638
+ );
1639
+ } else {
1640
+ lines.push(`Created ${result.created} test(s), setups included.`);
1641
+ }
1642
+ for (const f of result.flags) lines.push(flagLine(f));
1643
+ for (const t of [...result.tests, ...result.setups]) {
1644
+ const meta = [`${t.steps.length} steps`];
1645
+ if (t.setup_keys.length) meta.push(`before this test: ${t.setup_keys.join(", ")}`);
1646
+ if (t.start_state) meta.push(`start: ${START_STATE_LABEL[t.start_state] ?? t.start_state}`);
1647
+ const head = t.importable ? t.name : chalk11.red(`${t.name} (not imported)`);
1648
+ lines.push("", ` ${head} ${chalk11.dim(`${t.key} \xB7 ${meta.join(" \xB7 ")}`)}`);
1649
+ if (t.created_id) lines.push(` ${chalk11.dim(testCaseUrl(baseUrl, t.created_id))}`);
1650
+ for (const f of t.flags) lines.push(flagLine(f));
1651
+ }
1652
+ if (result.test_data.length) {
1653
+ lines.push("", "These tests use test data:");
1654
+ for (const d of result.test_data) {
1655
+ const note = d.status === "needs_value" || d.status === "skipped" || d.suggested_value === null ? "add the value under App notes & data" : d.status === "kept" ? "already set, kept" : d.status === "created" ? "added from the flow" : d.secret ? "stored as a secret" : `flow value: ${d.suggested_value}`;
1656
+ lines.push(` ${d.key} ${chalk11.dim(note)}`);
1657
+ }
1658
+ }
1659
+ return lines;
1660
+ }
1661
+ async function importMaestro(path, options) {
1662
+ let upload;
1663
+ try {
1664
+ if (!options.app) throw new UsageError("Pass the app to import into: --app <app id>.");
1665
+ upload = readMaestroUpload(path);
1666
+ } catch (err) {
1667
+ if (options.output === "json") printJsonError(err);
1668
+ throw err;
1669
+ }
1670
+ const { token, baseUrl } = resolveAuth(options);
1671
+ const client = new ApiClient(baseUrl, token);
1672
+ try {
1673
+ const appId = await resolveAppId(client, options.app, options.platform);
1674
+ const spinner = createSpinner(options.dryRun ? "Reading flows..." : "Importing flows...", {
1675
+ silent: options.output === "json"
1676
+ });
1677
+ spinner.start();
1678
+ let result;
1679
+ try {
1680
+ result = await client.importMaestro(upload, {
1681
+ appId,
1682
+ group: options.group,
1683
+ dryRun: options.dryRun === true
1684
+ });
1685
+ } finally {
1686
+ spinner.stop();
1687
+ }
1688
+ if (options.output === "json") {
1689
+ console.log(JSON.stringify(result, null, 2));
1690
+ return;
1691
+ }
1692
+ for (const line of formatImport(result, baseUrl)) console.log(line);
1693
+ } catch (err) {
1694
+ if (options.output === "json") printJsonError(err);
1695
+ console.error(chalk11.red(err instanceof Error ? err.message : "Import failed"));
1696
+ const rejected = err instanceof UsageError || err instanceof NotFoundError || err instanceof ApiStatusError && err.status >= 400 && err.status < 500 ? EXIT_TEST_FAIL : EXIT_INFRA_ERROR;
1697
+ process.exit(errorExitCode(err, rejected));
1698
+ }
1699
+ }
1700
+
1514
1701
  // src/commands/login.ts
1515
1702
  import { randomUUID } from "crypto";
1516
1703
  import open from "open";
@@ -1588,7 +1775,7 @@ async function logout() {
1588
1775
  }
1589
1776
 
1590
1777
  // src/commands/dev-status.ts
1591
- import chalk11 from "chalk";
1778
+ import chalk12 from "chalk";
1592
1779
  function buildLine(info) {
1593
1780
  if (!info || typeof info.app_build_id !== "number") return null;
1594
1781
  const version = info.app_version ? ` version ${info.app_version}${info.app_build_number ? ` (${info.app_build_number})` : ""}` : "";
@@ -1649,7 +1836,7 @@ async function devStatus(opts) {
1649
1836
  console.error(` url: ${state.page_url}`);
1650
1837
  console.error(` started: ${state.started_at}`);
1651
1838
  }
1652
- if (metro) console.error(chalk11.dim(`Metro: pid ${metro.pid} on :${metro.port} (CLI-owned)`));
1839
+ if (metro) console.error(chalk12.dim(`Metro: pid ${metro.pid} on :${metro.port} (CLI-owned)`));
1653
1840
  process.exit(0);
1654
1841
  }
1655
1842
  var LIVE_STATUSES = /* @__PURE__ */ new Set(["pending", "queued", "starting", "active"]);
@@ -1674,7 +1861,7 @@ async function reportOrphan(api, state, opts) {
1674
1861
  );
1675
1862
  } else if (live) {
1676
1863
  console.error(
1677
- chalk11.yellow(
1864
+ chalk12.yellow(
1678
1865
  `\u26A0 The dev process (pid ${state.pid}) has exited, but session ${state.session_id} is still ${status}.`
1679
1866
  )
1680
1867
  );
@@ -1722,12 +1909,12 @@ async function devStop(opts) {
1722
1909
  }
1723
1910
 
1724
1911
  // src/commands/dev-reload.ts
1725
- import chalk12 from "chalk";
1912
+ import chalk13 from "chalk";
1726
1913
  function fail(opts, code, message, exit) {
1727
1914
  if (opts.json) {
1728
1915
  console.log(JSON.stringify({ reloaded: false, code, message }));
1729
1916
  } else {
1730
- console.error(chalk12.red(`error: ${message}`));
1917
+ console.error(chalk13.red(`error: ${message}`));
1731
1918
  }
1732
1919
  process.exit(exit);
1733
1920
  }
@@ -1780,7 +1967,7 @@ async function devReload(opts) {
1780
1967
  api,
1781
1968
  // Build output and the "reloaded in" line are human output: stderr, so
1782
1969
  // stdout stays reserved for the --json result.
1783
- log: (line) => console.error(chalk12.dim(line))
1970
+ log: (line) => console.error(chalk13.dim(line))
1784
1971
  });
1785
1972
  const started = Date.now();
1786
1973
  try {
@@ -1802,10 +1989,10 @@ async function devReload(opts) {
1802
1989
  }
1803
1990
 
1804
1991
  // src/commands/builds.ts
1805
- import chalk14 from "chalk";
1992
+ import chalk15 from "chalk";
1806
1993
 
1807
1994
  // src/lib/formatters/builds.ts
1808
- import chalk13 from "chalk";
1995
+ import chalk14 from "chalk";
1809
1996
  var HEADERS = ["ID", "PLATFORM", "STATUS", "CREATED", "COST", "APP"];
1810
1997
  var UNBILLED_STATUSES = /* @__PURE__ */ new Set(["queued", "running"]);
1811
1998
  function formatCostCell(costMicro, status) {
@@ -1813,13 +2000,13 @@ function formatCostCell(costMicro, status) {
1813
2000
  return `$${(Math.max(costMicro, 0) / 1e6).toFixed(2)}`;
1814
2001
  }
1815
2002
  var STATUS_COLOR = {
1816
- succeeded: chalk13.green,
1817
- failed: chalk13.red,
1818
- timed_out: chalk13.red,
1819
- infra_failed: chalk13.red,
1820
- canceled: chalk13.dim,
1821
- running: chalk13.yellow,
1822
- queued: chalk13.dim
2003
+ succeeded: chalk14.green,
2004
+ failed: chalk14.red,
2005
+ timed_out: chalk14.red,
2006
+ infra_failed: chalk14.red,
2007
+ canceled: chalk14.dim,
2008
+ running: chalk14.yellow,
2009
+ queued: chalk14.dim
1823
2010
  };
1824
2011
  function formatBuildsHuman(builds2) {
1825
2012
  if (builds2.length === 0) {
@@ -1835,13 +2022,13 @@ function formatBuildsHuman(builds2) {
1835
2022
  ]);
1836
2023
  const widths = HEADERS.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
1837
2024
  const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
1838
- const join4 = (cells) => cells.join(" ").trimEnd();
1839
- const lines = [chalk13.bold(join4(padCells(HEADERS)))];
2025
+ const join5 = (cells) => cells.join(" ").trimEnd();
2026
+ const lines = [chalk14.bold(join5(padCells(HEADERS)))];
1840
2027
  for (const row of rows) {
1841
2028
  const padded = padCells(row);
1842
2029
  const colorFn = STATUS_COLOR[row[2]] ?? ((s) => s);
1843
2030
  padded[2] = colorFn(row[2]) + padded[2].slice(row[2].length);
1844
- lines.push(join4(padded));
2031
+ lines.push(join5(padded));
1845
2032
  }
1846
2033
  return lines.join("\n") + "\n";
1847
2034
  }
@@ -1895,7 +2082,7 @@ async function buildsLogs(id, opts, deps) {
1895
2082
  console.log(JSON.stringify(logs));
1896
2083
  } else {
1897
2084
  if (logs.content) process.stdout.write(logs.content);
1898
- console.error(chalk14.dim(`status: ${logs.status}`));
2085
+ console.error(chalk15.dim(`status: ${logs.status}`));
1899
2086
  }
1900
2087
  process.exitCode = 0;
1901
2088
  return;
@@ -1992,7 +2179,7 @@ async function buildsDownload(id, opts, deps) {
1992
2179
  if (opts.json) {
1993
2180
  console.log(JSON.stringify(result));
1994
2181
  } else {
1995
- console.log(chalk14.green(`\u2713 saved ${result.path}`));
2182
+ console.log(chalk15.green(`\u2713 saved ${result.path}`));
1996
2183
  }
1997
2184
  process.exitCode = 0;
1998
2185
  } catch (err) {
@@ -2009,7 +2196,7 @@ async function buildsCancel(id, opts, deps) {
2009
2196
  if (opts.json) {
2010
2197
  console.log(JSON.stringify(build));
2011
2198
  } else {
2012
- console.log(`${chalk14.cyan(build.id)} ${build.status}`);
2199
+ console.log(`${chalk15.cyan(build.id)} ${build.status}`);
2013
2200
  }
2014
2201
  process.exitCode = 0;
2015
2202
  } catch (err) {
@@ -2026,7 +2213,7 @@ async function buildsDeleteArtifact(id, opts, deps) {
2026
2213
  if (opts.json) {
2027
2214
  console.log(JSON.stringify(result));
2028
2215
  } else {
2029
- console.log(chalk14.green(`\u2713 deleted artifact for ${id}`));
2216
+ console.log(chalk15.green(`\u2713 deleted artifact for ${id}`));
2030
2217
  }
2031
2218
  process.exitCode = 0;
2032
2219
  } catch (err) {
@@ -2037,11 +2224,11 @@ async function buildsDeleteArtifact(id, opts, deps) {
2037
2224
  function printError(opts, err) {
2038
2225
  const message = err instanceof Error ? err.message : String(err);
2039
2226
  if (opts.json) console.log(JSON.stringify({ event: "error", message }));
2040
- console.error(chalk14.red(`error: ${message}`));
2227
+ console.error(chalk15.red(`error: ${message}`));
2041
2228
  }
2042
2229
 
2043
2230
  // src/commands/submit.ts
2044
- import chalk15 from "chalk";
2231
+ import chalk16 from "chalk";
2045
2232
  function defaultDeps2(client) {
2046
2233
  return {
2047
2234
  createSubmission: client.createSubmission.bind(client),
@@ -2065,7 +2252,7 @@ function submitConfigFor(cwd, platform) {
2065
2252
  cfg = loadDevConfig(cwd);
2066
2253
  } catch (err) {
2067
2254
  console.error(
2068
- chalk15.yellow(
2255
+ chalk16.yellow(
2069
2256
  `warning: vibeview.json could not be read \u2014 ignoring submit defaults (${err instanceof Error ? err.message : String(err)})`
2070
2257
  )
2071
2258
  );
@@ -2213,12 +2400,12 @@ async function submitCommand(opts, deps) {
2213
2400
  return;
2214
2401
  }
2215
2402
  if (!opts.json) {
2216
- console.error(` ${chalk15.cyan(sub.id)} ${chalk15.dim(`[${sub.platform}]`)}`);
2403
+ console.error(` ${chalk16.cyan(sub.id)} ${chalk16.dim(`[${sub.platform}]`)}`);
2217
2404
  }
2218
2405
  const final = await followSubmission(sub, opts, d);
2219
2406
  if (!opts.json && final.status === "succeeded" && (final.release_status ?? releaseStatus) === "draft") {
2220
2407
  console.error(
2221
- chalk15.dim("release status: draft \u2014 activate it from the Play Console when ready")
2408
+ chalk16.dim("release status: draft \u2014 activate it from the Play Console when ready")
2222
2409
  );
2223
2410
  }
2224
2411
  process.exitCode = final.status === "succeeded" ? 0 : 1;
@@ -2249,11 +2436,11 @@ async function checkStore(platform, androidFamily, ascKey2, opts, deps) {
2249
2436
  });
2250
2437
  emit(opts.json, { event: "preflight", ...result });
2251
2438
  if (!opts.json) {
2252
- const mark = result.ready ? chalk15.green("\u2713") : chalk15.red("\u2717");
2439
+ const mark = result.ready ? chalk16.green("\u2713") : chalk16.red("\u2717");
2253
2440
  console.error(`${mark} ${result.message}`);
2254
2441
  if (result.ready) {
2255
2442
  console.error(
2256
- chalk15.dim(
2443
+ chalk16.dim(
2257
2444
  ` next: vibeview build --cloud --production --platform ${platform}, then vibeview submit --platform ${platform} --latest`
2258
2445
  )
2259
2446
  );
@@ -2287,7 +2474,7 @@ async function linkedIdentifier(platform, opts, deps) {
2287
2474
  }
2288
2475
  if (!app.package_name) return void 0;
2289
2476
  if (!opts.json) {
2290
- console.error(chalk15.dim(`Checking ${app.package_name} (${app.name}, linked in vibeview.json)`));
2477
+ console.error(chalk16.dim(`Checking ${app.package_name} (${app.name}, linked in vibeview.json)`));
2291
2478
  }
2292
2479
  return app.package_name;
2293
2480
  }
@@ -2328,14 +2515,14 @@ async function followSubmission(created, opts, deps) {
2328
2515
  if (opts.json) {
2329
2516
  emit(opts.json, { event: "status", id: created.id, status: "queued" });
2330
2517
  } else {
2331
- console.error(chalk15.dim("\u23F3 queued \u2014 waiting for a submission slot"));
2518
+ console.error(chalk16.dim("\u23F3 queued \u2014 waiting for a submission slot"));
2332
2519
  }
2333
2520
  } else if (log.status === "running" && !printedRunning) {
2334
2521
  printedRunning = true;
2335
2522
  if (opts.json) {
2336
2523
  emit(opts.json, { event: "status", id: created.id, status: "running" });
2337
2524
  } else {
2338
- console.error(`${chalk15.cyan("\u25B6")} submitting`);
2525
+ console.error(`${chalk16.cyan("\u25B6")} submitting`);
2339
2526
  }
2340
2527
  }
2341
2528
  if (log.content) {
@@ -2377,7 +2564,7 @@ The submission continues server-side \u2014 see its outcome on the Builds page i
2377
2564
  });
2378
2565
  if (!opts.json) {
2379
2566
  const ok = final.status === "succeeded";
2380
- const mark = ok ? chalk15.green("\u2713") : chalk15.red("\u2717");
2567
+ const mark = ok ? chalk16.green("\u2713") : chalk16.red("\u2717");
2381
2568
  const duration = formatDuration(final.started_at, final.finished_at);
2382
2569
  let line = `${mark} ${final.status}`;
2383
2570
  if (duration) line += ` (${duration})`;
@@ -2389,11 +2576,11 @@ The submission continues server-side \u2014 see its outcome on the Builds page i
2389
2576
  function printError2(opts, err) {
2390
2577
  const message = err instanceof Error ? err.message : String(err);
2391
2578
  if (opts.json) console.log(JSON.stringify({ event: "error", message }));
2392
- console.error(chalk15.red(`error: ${message}`));
2579
+ console.error(chalk16.red(`error: ${message}`));
2393
2580
  }
2394
2581
 
2395
2582
  // src/commands/versions.ts
2396
- import chalk16 from "chalk";
2583
+ import chalk17 from "chalk";
2397
2584
  function defaultDeps3(client) {
2398
2585
  return {
2399
2586
  listAppVersions: client.listAppVersions.bind(client),
@@ -2443,7 +2630,7 @@ async function versionsSet(buildNumber, opts, deps) {
2443
2630
  console.log(JSON.stringify(version));
2444
2631
  } else {
2445
2632
  console.log(
2446
- chalk16.green(`\u2713 ${version.platform} build number set to ${version.build_number}`) + chalk16.dim(
2633
+ chalk17.green(`\u2713 ${version.platform} build number set to ${version.build_number}`) + chalk17.dim(
2447
2634
  " \u2014 the next autoIncrement production build gets " + (version.build_number + 1)
2448
2635
  )
2449
2636
  );
@@ -2462,19 +2649,19 @@ function formatVersionsHuman(versions2) {
2462
2649
  const rows = versions2.map((v) => [v.platform, String(v.build_number), v.updated_at ?? ""]);
2463
2650
  const widths = HEADERS2.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
2464
2651
  const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
2465
- const join4 = (cells) => cells.join(" ").trimEnd();
2466
- const lines = [chalk16.bold(join4(padCells(HEADERS2)))];
2467
- for (const row of rows) lines.push(join4(padCells(row)));
2652
+ const join5 = (cells) => cells.join(" ").trimEnd();
2653
+ const lines = [chalk17.bold(join5(padCells(HEADERS2)))];
2654
+ for (const row of rows) lines.push(join5(padCells(row)));
2468
2655
  return lines.join("\n") + "\n";
2469
2656
  }
2470
2657
  function printError3(opts, err) {
2471
2658
  const message = err instanceof Error ? err.message : String(err);
2472
2659
  if (opts.json) console.log(JSON.stringify({ event: "error", message }));
2473
- console.error(chalk16.red(`error: ${message}`));
2660
+ console.error(chalk17.red(`error: ${message}`));
2474
2661
  }
2475
2662
 
2476
2663
  // src/commands/whoami.ts
2477
- import chalk17 from "chalk";
2664
+ import chalk18 from "chalk";
2478
2665
  function defaultDeps4(opts) {
2479
2666
  const { token, baseUrl } = resolveAuth(opts);
2480
2667
  const client = new ApiClient(baseUrl, token);
@@ -2505,33 +2692,33 @@ async function whoAmI(opts, deps) {
2505
2692
  process.exitCode = 0;
2506
2693
  return;
2507
2694
  }
2508
- console.log(`${chalk17.bold("Logged in as")} ${me.email}`);
2695
+ console.log(`${chalk18.bold("Logged in as")} ${me.email}`);
2509
2696
  if (me.memberships.length === 1) {
2510
- console.log(`${chalk17.bold("Organization")} ${me.memberships[0].organization_name}`);
2697
+ console.log(`${chalk18.bold("Organization")} ${me.memberships[0].organization_name}`);
2511
2698
  } else if (me.memberships.length > 1) {
2512
- console.log(chalk17.bold("Organizations"));
2699
+ console.log(chalk18.bold("Organizations"));
2513
2700
  for (const m of me.memberships) {
2514
2701
  console.log(` ${m.organization_name} (${m.role})`);
2515
2702
  }
2516
2703
  }
2517
- console.log(`${chalk17.bold("Member role")} ${me.role ?? "\u2014"}`);
2518
- console.log(`${chalk17.bold("Token role")} ${me.token_role ?? "\u2014 (session auth)"}`);
2519
- console.log(`${chalk17.bold("Effective role")} ${effective ?? "\u2014"}`);
2704
+ console.log(`${chalk18.bold("Member role")} ${me.role ?? "\u2014"}`);
2705
+ console.log(`${chalk18.bold("Token role")} ${me.token_role ?? "\u2014 (session auth)"}`);
2706
+ console.log(`${chalk18.bold("Effective role")} ${effective ?? "\u2014"}`);
2520
2707
  if (me.token_access) {
2521
2708
  console.log(
2522
- `${chalk17.bold("Access")} ${me.token_access === "ci" ? "CI only (upload builds, run tests, read results)" : "Full access"}`
2709
+ `${chalk18.bold("Access")} ${me.token_access === "ci" ? "CI only (upload builds, run tests, read results)" : "Full access"}`
2523
2710
  );
2524
- console.log(`${chalk17.bold("Expires")} ${describeExpiry(me.token_expires_at)}`);
2711
+ console.log(`${chalk18.bold("Expires")} ${describeExpiry(me.token_expires_at)}`);
2525
2712
  }
2526
2713
  if (me.is_superadmin) {
2527
- console.log(chalk17.dim("Superadmin (platform-wide; does not raise the org role above)"));
2714
+ console.log(chalk18.dim("Superadmin (platform-wide; does not raise the org role above)"));
2528
2715
  }
2529
2716
  if (me.role === "admin" && me.token_role && me.token_role !== "admin" && me.token_access !== "ci") {
2530
2717
  console.log(
2531
- chalk17.yellow(
2718
+ chalk18.yellow(
2532
2719
  `
2533
- 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 ${chalk17.bold("vibeview env set")}) will fail with a permissions error.
2534
- Create an admin API token in the dashboard and use ${chalk17.bold("--token")} or ${chalk17.bold("VIBEVIEW_API_TOKEN")}.`
2720
+ 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 ${chalk18.bold("vibeview env set")}) will fail with a permissions error.
2721
+ Create an admin API token in the dashboard and use ${chalk18.bold("--token")} or ${chalk18.bold("VIBEVIEW_API_TOKEN")}.`
2535
2722
  )
2536
2723
  );
2537
2724
  }
@@ -2541,15 +2728,15 @@ Create an admin API token in the dashboard and use ${chalk17.bold("--token")} or
2541
2728
  if (opts.json) {
2542
2729
  console.log(JSON.stringify({ event: "error", message }));
2543
2730
  }
2544
- console.error(chalk17.red(`error: ${message}`));
2731
+ console.error(chalk18.red(`error: ${message}`));
2545
2732
  process.exitCode = errorExitCode(err);
2546
2733
  }
2547
2734
  }
2548
2735
 
2549
2736
  // src/commands/env.ts
2550
2737
  import { chmod, open as open2, readFile as readFileAsync } from "fs/promises";
2551
- import { basename } from "path";
2552
- import chalk18 from "chalk";
2738
+ import { basename as basename2 } from "path";
2739
+ import chalk19 from "chalk";
2553
2740
  import { password } from "@inquirer/prompts";
2554
2741
 
2555
2742
  // src/lib/gitignore.ts
@@ -2605,7 +2792,7 @@ function defaultDeps5(client) {
2605
2792
  isIgnored: (path) => isGitIgnored(path),
2606
2793
  writeFile: writeSecureFile,
2607
2794
  readFile: (path) => readFileAsync(path),
2608
- warn: (message) => console.error(chalk18.yellow(`warning: ${message}`))
2795
+ warn: (message) => console.error(chalk19.yellow(`warning: ${message}`))
2609
2796
  };
2610
2797
  }
2611
2798
  async function envList(opts, deps) {
@@ -2636,9 +2823,9 @@ function formatEnvVarsHuman(variables) {
2636
2823
  ]);
2637
2824
  const widths = HEADERS3.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
2638
2825
  const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
2639
- const join4 = (cells) => cells.join(" ").trimEnd();
2640
- const lines = [chalk18.bold(join4(padCells(HEADERS3)))];
2641
- for (const row of rows) lines.push(join4(padCells(row)));
2826
+ const join5 = (cells) => cells.join(" ").trimEnd();
2827
+ const lines = [chalk19.bold(join5(padCells(HEADERS3)))];
2828
+ for (const row of rows) lines.push(join5(padCells(row)));
2642
2829
  return lines.join("\n") + "\n";
2643
2830
  }
2644
2831
  async function envSet(name, value, opts, deps) {
@@ -2655,7 +2842,7 @@ async function envSet(name, value, opts, deps) {
2655
2842
  const visibility = resolveVisibility(opts);
2656
2843
  if (opts.file) {
2657
2844
  const content = await d.readFile(opts.file);
2658
- const filename = basename(opts.file);
2845
+ const filename = basename2(opts.file);
2659
2846
  const result2 = await d.setFileVar({ name, content, filename, visibility, app: opts.app });
2660
2847
  printSetResult(opts, result2);
2661
2848
  process.exitCode = 0;
@@ -2687,7 +2874,7 @@ function printSetResult(opts, result) {
2687
2874
  if (opts.json) {
2688
2875
  console.log(JSON.stringify(result));
2689
2876
  } else {
2690
- console.log(chalk18.green(`\u2713 set ${result.name} (${result.visibility})`));
2877
+ console.log(chalk19.green(`\u2713 set ${result.name} (${result.visibility})`));
2691
2878
  }
2692
2879
  }
2693
2880
  async function envRm(name, opts, deps) {
@@ -2697,7 +2884,7 @@ async function envRm(name, opts, deps) {
2697
2884
  if (opts.json) {
2698
2885
  console.log(JSON.stringify({ event: "deleted", name }));
2699
2886
  } else {
2700
- console.log(chalk18.green(`\u2713 removed ${name}`));
2887
+ console.log(chalk19.green(`\u2713 removed ${name}`));
2701
2888
  }
2702
2889
  process.exitCode = 0;
2703
2890
  } catch (err) {
@@ -2729,7 +2916,7 @@ async function envPull(opts, deps) {
2729
2916
  if (opts.json) {
2730
2917
  console.log(JSON.stringify({ event: "pulled", path: outPath, count: variables.length }));
2731
2918
  } else {
2732
- console.log(chalk18.green(`\u2713 wrote ${variables.length} variable(s) to ${outPath}`));
2919
+ console.log(chalk19.green(`\u2713 wrote ${variables.length} variable(s) to ${outPath}`));
2733
2920
  }
2734
2921
  process.exitCode = 0;
2735
2922
  } catch (err) {
@@ -2768,7 +2955,7 @@ function escapeEnvValue(value) {
2768
2955
  function printError4(opts, err) {
2769
2956
  const message = err instanceof Error ? err.message : String(err);
2770
2957
  if (opts.json) console.log(JSON.stringify({ event: "error", message }));
2771
- console.error(chalk18.red(`error: ${message}`));
2958
+ console.error(chalk19.red(`error: ${message}`));
2772
2959
  }
2773
2960
 
2774
2961
  // src/commands/credentials.ts
@@ -2779,8 +2966,8 @@ import {
2779
2966
  writeFile as writeFileAsync
2780
2967
  } from "fs/promises";
2781
2968
  import { mkdirSync } from "fs";
2782
- import { basename as basename2, extname, join } from "path";
2783
- import chalk19 from "chalk";
2969
+ import { basename as basename3, extname as extname2, join as join2 } from "path";
2970
+ import chalk20 from "chalk";
2784
2971
  import { input, password as password2 } from "@inquirer/prompts";
2785
2972
  function clientFor2(opts) {
2786
2973
  const { token, baseUrl } = resolveAuth(opts);
@@ -2853,7 +3040,7 @@ function defaultDeps6(client) {
2853
3040
  mkdir: (dir) => mkdirSync(dir, { recursive: true }),
2854
3041
  writeFile: (path, data, mode) => writeFileAsync(path, data, mode !== void 0 ? { mode } : void 0),
2855
3042
  chmod: (path, mode) => chmodAsync(path, mode),
2856
- warn: (message) => console.error(chalk19.yellow(`warning: ${message}`))
3043
+ warn: (message) => console.error(chalk20.yellow(`warning: ${message}`))
2857
3044
  };
2858
3045
  }
2859
3046
  var ANDROID_KIND = "android_keystore";
@@ -2883,10 +3070,10 @@ async function credentialsAdd(opts, deps) {
2883
3070
  "a password is required: run interactively to be prompted (passwords are never accepted as flags)."
2884
3071
  );
2885
3072
  }
2886
- const name = opts.name ?? basename2(opts.file, extname(opts.file));
3073
+ const name = opts.name ?? basename3(opts.file, extname2(opts.file));
2887
3074
  try {
2888
3075
  const file = await d.readFile(opts.file);
2889
- const filename = basename2(opts.file);
3076
+ const filename = basename3(opts.file);
2890
3077
  let body;
2891
3078
  if (platform === "android") {
2892
3079
  const storePassword = await d.prompt({ message: "Keystore (store) password", hidden: true });
@@ -2915,14 +3102,14 @@ async function credentialsAdd(opts, deps) {
2915
3102
  file,
2916
3103
  filename,
2917
3104
  profile,
2918
- profileFilename: basename2(opts.profile)
3105
+ profileFilename: basename3(opts.profile)
2919
3106
  };
2920
3107
  }
2921
3108
  const result = await d.addCredential(body);
2922
3109
  if (opts.json) {
2923
3110
  console.log(JSON.stringify(result));
2924
3111
  } else {
2925
- console.log(chalk19.green(`\u2713 added ${result.name} (${result.platform})`));
3112
+ console.log(chalk20.green(`\u2713 added ${result.name} (${result.platform})`));
2926
3113
  }
2927
3114
  process.exitCode = 0;
2928
3115
  } catch (err) {
@@ -2948,7 +3135,7 @@ async function credentialsGenerate(opts, deps) {
2948
3135
  const outDir = opts.out ?? process.cwd();
2949
3136
  try {
2950
3137
  d.mkdir(outDir);
2951
- const expectedPath = join(outDir, `${opts.name}.keystore`);
3138
+ const expectedPath = join2(outDir, `${opts.name}.keystore`);
2952
3139
  if (await d.fileExists(expectedPath)) {
2953
3140
  throw new Error(
2954
3141
  `${expectedPath} already exists \u2014 refusing to overwrite it. Move it away or pass a different --out/--name.`
@@ -2963,7 +3150,7 @@ async function credentialsGenerate(opts, deps) {
2963
3150
  // charge, so a stale CLI can never accidentally opt out of it.
2964
3151
  ...opts.force ? { overwrite: true } : {}
2965
3152
  });
2966
- const savedTo = join(outDir, basename2(result.filename));
3153
+ const savedTo = join2(outDir, basename3(result.filename));
2967
3154
  if (opts.json) {
2968
3155
  console.log(JSON.stringify({ ...result, saved_to: savedTo }));
2969
3156
  }
@@ -2980,20 +3167,20 @@ async function credentialsGenerate(opts, deps) {
2980
3167
  }
2981
3168
  if (opts.json) {
2982
3169
  } else {
2983
- console.log(chalk19.green(`\u2713 generated ${result.name} (android)`));
3170
+ console.log(chalk20.green(`\u2713 generated ${result.name} (android)`));
2984
3171
  console.log("");
2985
- console.log(` Keystore saved to: ${chalk19.bold(savedTo)}`);
2986
- console.log(` Key alias: ${chalk19.bold(result.key_alias)}`);
2987
- console.log(` Store password: ${chalk19.bold(result.store_password)}`);
2988
- console.log(` Key password: ${chalk19.bold(result.key_password)}`);
3172
+ console.log(` Keystore saved to: ${chalk20.bold(savedTo)}`);
3173
+ console.log(` Key alias: ${chalk20.bold(result.key_alias)}`);
3174
+ console.log(` Store password: ${chalk20.bold(result.store_password)}`);
3175
+ console.log(` Key password: ${chalk20.bold(result.key_password)}`);
2989
3176
  console.log("");
2990
3177
  console.log(
2991
- chalk19.yellow(
3178
+ chalk20.yellow(
2992
3179
  " These are shown ONCE \u2014 VibeView cannot recover the keystore file or its passwords. Back them up somewhere safe."
2993
3180
  )
2994
3181
  );
2995
3182
  console.log(
2996
- chalk19.yellow(
3183
+ chalk20.yellow(
2997
3184
  " The credential is already uploaded and active for android and androidtv production builds."
2998
3185
  )
2999
3186
  );
@@ -3019,19 +3206,19 @@ async function generateAppleCredential(platform, opts, d) {
3019
3206
  if (opts.json) {
3020
3207
  console.log(JSON.stringify(result));
3021
3208
  } else {
3022
- console.log(chalk19.green(`\u2713 generated ${result.name} (${result.platform})`));
3209
+ console.log(chalk20.green(`\u2713 generated ${result.name} (${result.platform})`));
3023
3210
  console.log("");
3024
- console.log(` Scope: ${chalk19.bold(result.app_public_id ?? "org-wide")}`);
3211
+ console.log(` Scope: ${chalk20.bold(result.app_public_id ?? "org-wide")}`);
3025
3212
  const expires = formatExpires(result);
3026
3213
  if (expires) {
3027
- console.log(` Expires: ${chalk19.bold(expires)}`);
3214
+ console.log(` Expires: ${chalk20.bold(expires)}`);
3028
3215
  }
3029
3216
  console.log("");
3030
3217
  console.log(
3031
3218
  ` The certificate and provisioning profile are stored with VibeView and already active for ${result.platform} production builds.`
3032
3219
  );
3033
3220
  console.log(
3034
- ` Run ${chalk19.bold(`vibeview credentials download ${result.name}`)} if you need the files locally.`
3221
+ ` Run ${chalk20.bold(`vibeview credentials download ${result.name}`)} if you need the files locally.`
3035
3222
  );
3036
3223
  }
3037
3224
  process.exitCode = 0;
@@ -3072,8 +3259,8 @@ async function credentialsDownload(name, opts, deps) {
3072
3259
  }
3073
3260
  throw err;
3074
3261
  }
3075
- const savedTo = join(outDir, basename2(material.filename));
3076
- const profileSavedTo = material.profile_base64 != null ? join(outDir, `${basename2(material.filename, extname(material.filename))}.mobileprovision`) : null;
3262
+ const savedTo = join2(outDir, basename3(material.filename));
3263
+ const profileSavedTo = material.profile_base64 != null ? join2(outDir, `${basename3(material.filename, extname2(material.filename))}.mobileprovision`) : null;
3077
3264
  if (opts.json) {
3078
3265
  console.log(
3079
3266
  JSON.stringify({
@@ -3097,17 +3284,17 @@ async function credentialsDownload(name, opts, deps) {
3097
3284
  await d.chmod(path, 384);
3098
3285
  }
3099
3286
  if (!opts.json) {
3100
- console.log(chalk19.green(`\u2713 downloaded ${material.name} (${material.platform})`));
3287
+ console.log(chalk20.green(`\u2713 downloaded ${material.name} (${material.platform})`));
3101
3288
  console.log("");
3102
- console.log(` Saved to: ${chalk19.bold(savedTo)}`);
3289
+ console.log(` Saved to: ${chalk20.bold(savedTo)}`);
3103
3290
  if (profileSavedTo !== null) {
3104
- console.log(` Profile: ${chalk19.bold(profileSavedTo)}`);
3291
+ console.log(` Profile: ${chalk20.bold(profileSavedTo)}`);
3105
3292
  }
3106
3293
  for (const [key, value] of Object.entries(material.passwords)) {
3107
- console.log(` ${key}: ${chalk19.bold(value)}`);
3294
+ console.log(` ${key}: ${chalk20.bold(value)}`);
3108
3295
  }
3109
3296
  console.log("");
3110
- console.log(chalk19.yellow(" This download is recorded in your organization's audit log."));
3297
+ console.log(chalk20.yellow(" This download is recorded in your organization's audit log."));
3111
3298
  }
3112
3299
  process.exitCode = 0;
3113
3300
  } catch (err) {
@@ -3133,18 +3320,18 @@ async function ascKeyAdd(opts, deps) {
3133
3320
  keyId: opts.keyId,
3134
3321
  issuerId: opts.issuerId,
3135
3322
  file,
3136
- filename: basename2(opts.file)
3323
+ filename: basename3(opts.file)
3137
3324
  });
3138
3325
  if (opts.json) {
3139
3326
  console.log(JSON.stringify(result));
3140
3327
  } else {
3141
- console.log(chalk19.green(`\u2713 added App Store Connect key ${result.name}`));
3328
+ console.log(chalk20.green(`\u2713 added App Store Connect key ${result.name}`));
3142
3329
  console.log("");
3143
- console.log(` Key ID: ${chalk19.bold(result.key_id)}`);
3144
- console.log(` Issuer ID: ${chalk19.bold(result.issuer_id)}`);
3330
+ console.log(` Key ID: ${chalk20.bold(result.key_id)}`);
3331
+ console.log(` Issuer ID: ${chalk20.bold(result.issuer_id)}`);
3145
3332
  console.log("");
3146
3333
  console.log(
3147
- chalk19.yellow(
3334
+ chalk20.yellow(
3148
3335
  " The .p8 is stored encrypted and will never be shown again \u2014 keep your own copy somewhere safe."
3149
3336
  )
3150
3337
  );
@@ -3178,9 +3365,9 @@ function formatAscKeysHuman(keys) {
3178
3365
  const rows = keys.map((k) => [k.name, k.key_id, k.issuer_id, formatUtcTimestamp(k.created_at)]);
3179
3366
  const widths = HEADERS3.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
3180
3367
  const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
3181
- const join4 = (cells) => cells.join(" ").trimEnd();
3182
- const lines = [chalk19.bold(join4(padCells(HEADERS3)))];
3183
- for (const row of rows) lines.push(join4(padCells(row)));
3368
+ const join5 = (cells) => cells.join(" ").trimEnd();
3369
+ const lines = [chalk20.bold(join5(padCells(HEADERS3)))];
3370
+ for (const row of rows) lines.push(join5(padCells(row)));
3184
3371
  return lines.join("\n") + "\n";
3185
3372
  }
3186
3373
  async function ascKeyRm(name, opts, deps) {
@@ -3190,7 +3377,7 @@ async function ascKeyRm(name, opts, deps) {
3190
3377
  if (opts.json) {
3191
3378
  console.log(JSON.stringify({ event: "deleted", name }));
3192
3379
  } else {
3193
- console.log(chalk19.green(`\u2713 removed App Store Connect key ${name}`));
3380
+ console.log(chalk20.green(`\u2713 removed App Store Connect key ${name}`));
3194
3381
  }
3195
3382
  process.exitCode = 0;
3196
3383
  } catch (err) {
@@ -3210,17 +3397,17 @@ async function playKeyAdd(opts, deps) {
3210
3397
  const result = await d.addPlayKey({
3211
3398
  ...opts.name !== void 0 ? { name: opts.name } : {},
3212
3399
  file,
3213
- filename: basename2(opts.file)
3400
+ filename: basename3(opts.file)
3214
3401
  });
3215
3402
  if (opts.json) {
3216
3403
  console.log(JSON.stringify(result));
3217
3404
  } else {
3218
- console.log(chalk19.green(`\u2713 added Google Play key ${result.name}`));
3405
+ console.log(chalk20.green(`\u2713 added Google Play key ${result.name}`));
3219
3406
  console.log("");
3220
- console.log(` Service account: ${chalk19.bold(result.client_email)}`);
3407
+ console.log(` Service account: ${chalk20.bold(result.client_email)}`);
3221
3408
  console.log("");
3222
3409
  console.log(
3223
- chalk19.yellow(
3410
+ chalk20.yellow(
3224
3411
  " The JSON key is stored encrypted and will never be shown again \u2014 keep your own copy somewhere safe."
3225
3412
  )
3226
3413
  );
@@ -3254,9 +3441,9 @@ function formatPlayKeysHuman(keys) {
3254
3441
  const rows = keys.map((k) => [k.name, k.client_email, formatUtcTimestamp(k.created_at)]);
3255
3442
  const widths = HEADERS3.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
3256
3443
  const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
3257
- const join4 = (cells) => cells.join(" ").trimEnd();
3258
- const lines = [chalk19.bold(join4(padCells(HEADERS3)))];
3259
- for (const row of rows) lines.push(join4(padCells(row)));
3444
+ const join5 = (cells) => cells.join(" ").trimEnd();
3445
+ const lines = [chalk20.bold(join5(padCells(HEADERS3)))];
3446
+ for (const row of rows) lines.push(join5(padCells(row)));
3260
3447
  return lines.join("\n") + "\n";
3261
3448
  }
3262
3449
  async function playKeyRm(name, opts, deps) {
@@ -3266,7 +3453,7 @@ async function playKeyRm(name, opts, deps) {
3266
3453
  if (opts.json) {
3267
3454
  console.log(JSON.stringify({ event: "deleted", name }));
3268
3455
  } else {
3269
- console.log(chalk19.green(`\u2713 removed Google Play key ${name}`));
3456
+ console.log(chalk20.green(`\u2713 removed Google Play key ${name}`));
3270
3457
  }
3271
3458
  process.exitCode = 0;
3272
3459
  } catch (err) {
@@ -3334,9 +3521,9 @@ function formatCredentialsHuman(credentials2) {
3334
3521
  ]);
3335
3522
  const widths = HEADERS3.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
3336
3523
  const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
3337
- const join4 = (cells) => cells.join(" ").trimEnd();
3338
- const lines = [chalk19.bold(join4(padCells(HEADERS3)))];
3339
- for (const row of rows) lines.push(join4(padCells(row)));
3524
+ const join5 = (cells) => cells.join(" ").trimEnd();
3525
+ const lines = [chalk20.bold(join5(padCells(HEADERS3)))];
3526
+ for (const row of rows) lines.push(join5(padCells(row)));
3340
3527
  return lines.join("\n") + "\n";
3341
3528
  }
3342
3529
  async function credentialsRm(name, opts, deps) {
@@ -3346,7 +3533,7 @@ async function credentialsRm(name, opts, deps) {
3346
3533
  if (opts.json) {
3347
3534
  console.log(JSON.stringify({ event: "deleted", name }));
3348
3535
  } else {
3349
- console.log(chalk19.green(`\u2713 removed ${name}`));
3536
+ console.log(chalk20.green(`\u2713 removed ${name}`));
3350
3537
  }
3351
3538
  process.exitCode = 0;
3352
3539
  } catch (err) {
@@ -3358,16 +3545,16 @@ function printKeystoreWriteFailure(opts, err, result) {
3358
3545
  const message = err instanceof Error ? err.message : String(err);
3359
3546
  if (opts.json) {
3360
3547
  console.log(JSON.stringify({ event: "error", message }));
3361
- console.error(chalk19.red(`error: ${message}`));
3548
+ console.error(chalk20.red(`error: ${message}`));
3362
3549
  return;
3363
3550
  }
3364
- console.error(chalk19.red(`error: ${message}`));
3551
+ console.error(chalk20.red(`error: ${message}`));
3365
3552
  console.error(
3366
- chalk19.red(
3553
+ chalk20.red(
3367
3554
  `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.`
3368
3555
  )
3369
3556
  );
3370
- console.error(chalk19.red("Recover it NOW from the values below \u2014 they will not be shown again:"));
3557
+ console.error(chalk20.red("Recover it NOW from the values below \u2014 they will not be shown again:"));
3371
3558
  console.error("");
3372
3559
  console.error(` Key alias: ${result.key_alias}`);
3373
3560
  console.error(` Store password: ${result.store_password}`);
@@ -3381,11 +3568,11 @@ function printKeystoreWriteFailure(opts, err, result) {
3381
3568
  function printError5(opts, err) {
3382
3569
  const message = err instanceof Error ? err.message : String(err);
3383
3570
  if (opts.json) console.log(JSON.stringify({ event: "error", message }));
3384
- console.error(chalk19.red(`error: ${message}`));
3571
+ console.error(chalk20.red(`error: ${message}`));
3385
3572
  }
3386
3573
 
3387
3574
  // src/commands/webhooks.ts
3388
- import chalk20 from "chalk";
3575
+ import chalk21 from "chalk";
3389
3576
  function clientFor3(opts) {
3390
3577
  const { token, baseUrl } = resolveAuth(opts);
3391
3578
  return new ApiClient(baseUrl, token);
@@ -3416,14 +3603,14 @@ async function webhooksAdd(opts, deps) {
3416
3603
  if (opts.json) {
3417
3604
  console.log(JSON.stringify(result));
3418
3605
  } else {
3419
- console.log(chalk20.green(`\u2713 added ${result.id} ${result.url}`));
3606
+ console.log(chalk21.green(`\u2713 added ${result.id} ${result.url}`));
3420
3607
  console.log("");
3421
- console.log(` Signing secret: ${chalk20.bold(result.secret)}`);
3608
+ console.log(` Signing secret: ${chalk21.bold(result.secret)}`);
3422
3609
  if (opts.secret !== void 0) {
3423
- console.log(chalk20.yellow(" (This is the secret you provided.)"));
3610
+ console.log(chalk21.yellow(" (This is the secret you provided.)"));
3424
3611
  }
3425
3612
  console.log(
3426
- chalk20.yellow(" This secret is shown ONCE and cannot be retrieved again \u2014 store it now.")
3613
+ chalk21.yellow(" This secret is shown ONCE and cannot be retrieved again \u2014 store it now.")
3427
3614
  );
3428
3615
  }
3429
3616
  process.exitCode = 0;
@@ -3453,9 +3640,9 @@ function formatWebhooksHuman(webhooks2) {
3453
3640
  ]);
3454
3641
  const widths = HEADERS3.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
3455
3642
  const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
3456
- const join4 = (cells) => cells.join(" ").trimEnd();
3457
- const lines = [chalk20.bold(join4(padCells(HEADERS3)))];
3458
- for (const row of rows) lines.push(join4(padCells(row)));
3643
+ const join5 = (cells) => cells.join(" ").trimEnd();
3644
+ const lines = [chalk21.bold(join5(padCells(HEADERS3)))];
3645
+ for (const row of rows) lines.push(join5(padCells(row)));
3459
3646
  return lines.join("\n") + "\n";
3460
3647
  }
3461
3648
  async function webhooksList(opts, deps) {
@@ -3480,7 +3667,7 @@ async function webhooksRm(id, opts, deps) {
3480
3667
  if (opts.json) {
3481
3668
  console.log(JSON.stringify({ event: "deleted", id }));
3482
3669
  } else {
3483
- console.log(chalk20.green(`\u2713 removed ${id}`));
3670
+ console.log(chalk21.green(`\u2713 removed ${id}`));
3484
3671
  }
3485
3672
  process.exitCode = 0;
3486
3673
  } catch (err) {
@@ -3513,13 +3700,13 @@ async function webhooksTest(id, opts, deps) {
3513
3700
  if (opts.json) console.log(JSON.stringify(ld));
3514
3701
  if (ld.status === "succeeded") {
3515
3702
  if (!opts.json) {
3516
- console.log(chalk20.green(`\u2713 Delivered (HTTP ${ld.response_code})`));
3703
+ console.log(chalk21.green(`\u2713 Delivered (HTTP ${ld.response_code})`));
3517
3704
  }
3518
3705
  process.exitCode = 0;
3519
3706
  } else {
3520
3707
  if (!opts.json) {
3521
3708
  const code = ld.response_code != null ? `HTTP ${ld.response_code}` : "no response";
3522
- console.error(chalk20.red(`\u2717 Failed (${code}) at ${formatUtcTimestamp(ld.at)}`));
3709
+ console.error(chalk21.red(`\u2717 Failed (${code}) at ${formatUtcTimestamp(ld.at)}`));
3523
3710
  }
3524
3711
  process.exitCode = 1;
3525
3712
  }
@@ -3528,7 +3715,7 @@ async function webhooksTest(id, opts, deps) {
3528
3715
  }
3529
3716
  if (opts.json) console.log(JSON.stringify({ status: "pending" }));
3530
3717
  console.error(
3531
- chalk20.yellow(
3718
+ chalk21.yellow(
3532
3719
  `Delivery still pending after ${TEST_POLL_BUDGET_MS / 1e3}s \u2014 check \`vibeview webhooks list\` later for the result.`
3533
3720
  )
3534
3721
  );
@@ -3541,7 +3728,7 @@ async function webhooksTest(id, opts, deps) {
3541
3728
  function printError6(opts, err) {
3542
3729
  const message = err instanceof Error ? err.message : String(err);
3543
3730
  if (opts.json) console.log(JSON.stringify({ event: "error", message }));
3544
- console.error(chalk20.red(`error: ${message}`));
3731
+ console.error(chalk21.red(`error: ${message}`));
3545
3732
  }
3546
3733
 
3547
3734
  // src/commands/list-devices.ts
@@ -3582,7 +3769,7 @@ async function listDevicesCommand(options) {
3582
3769
  }
3583
3770
 
3584
3771
  // src/lib/metadata.ts
3585
- import { readFileSync as readFileSync2 } from "fs";
3772
+ import { readFileSync as readFileSync3 } from "fs";
3586
3773
  var MAX_METADATA_BYTES = 16384;
3587
3774
  var MAX_METADATA_DEPTH = 4;
3588
3775
  function measureDepth(value, current = 1) {
@@ -3621,7 +3808,7 @@ function parseMetadata(raw) {
3621
3808
  function readMetadataFile(path) {
3622
3809
  let raw;
3623
3810
  try {
3624
- raw = readFileSync2(path, "utf8");
3811
+ raw = readFileSync3(path, "utf8");
3625
3812
  } catch (e) {
3626
3813
  throw new Error(`failed to read --metadata-file ${path}: ${e.message}`);
3627
3814
  }
@@ -3633,27 +3820,27 @@ function readMetadataFile(path) {
3633
3820
  }
3634
3821
 
3635
3822
  // src/commands/agent-setup.ts
3636
- import { copyFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
3823
+ import { copyFileSync, existsSync as existsSync3, mkdirSync as mkdirSync2 } from "fs";
3637
3824
  import { homedir } from "os";
3638
- import { dirname, join as join2 } from "path";
3825
+ import { dirname, join as join3 } from "path";
3639
3826
  import { fileURLToPath } from "url";
3640
3827
  import { spawnSync } from "child_process";
3641
3828
  import { confirm } from "@inquirer/prompts";
3642
- import chalk21 from "chalk";
3829
+ import chalk22 from "chalk";
3643
3830
  function resolveShippedSkillPath(fromDir) {
3644
- for (const rel of ["..", join2("..", "..")]) {
3645
- const candidate = join2(fromDir, rel, "skills", "vibeview-agent", "SKILL.md");
3646
- if (existsSync2(candidate)) return candidate;
3831
+ for (const rel of ["..", join3("..", "..")]) {
3832
+ const candidate = join3(fromDir, rel, "skills", "vibeview-agent", "SKILL.md");
3833
+ if (existsSync3(candidate)) return candidate;
3647
3834
  }
3648
3835
  return null;
3649
3836
  }
3650
3837
  function skillInstallDir(project, cwd, home) {
3651
3838
  const base = project ? cwd : home;
3652
- return join2(base, ".claude", "skills", "vibeview-agent");
3839
+ return join3(base, ".claude", "skills", "vibeview-agent");
3653
3840
  }
3654
3841
  function installSkill(shippedPath, targetDir) {
3655
- const target = join2(targetDir, "SKILL.md");
3656
- const existed = existsSync2(target);
3842
+ const target = join3(targetDir, "SKILL.md");
3843
+ const existed = existsSync3(target);
3657
3844
  mkdirSync2(targetDir, { recursive: true });
3658
3845
  copyFileSync(shippedPath, target);
3659
3846
  return existed ? "updated" : "installed";
@@ -3669,23 +3856,23 @@ function registerMcp() {
3669
3856
  encoding: "utf8"
3670
3857
  });
3671
3858
  if (res.status === 0) {
3672
- console.log(chalk21.green("\u2713 MCP server registered with Claude Code (claude mcp add vibeview)"));
3859
+ console.log(chalk22.green("\u2713 MCP server registered with Claude Code (claude mcp add vibeview)"));
3673
3860
  return;
3674
3861
  }
3675
3862
  const out = `${res.stderr ?? ""}${res.stdout ?? ""}`;
3676
3863
  if (out.includes("already exists")) {
3677
- console.log(chalk21.dim("MCP server already registered with Claude Code \u2014 nothing to do"));
3864
+ console.log(chalk22.dim("MCP server already registered with Claude Code \u2014 nothing to do"));
3678
3865
  return;
3679
3866
  }
3680
- console.error(chalk21.yellow(`\u26A0 could not register the MCP server: ${out.trim() || res.error}`));
3681
- console.error(chalk21.yellow(` register it manually: ${MCP_ADD_HINT}`));
3867
+ console.error(chalk22.yellow(`\u26A0 could not register the MCP server: ${out.trim() || res.error}`));
3868
+ console.error(chalk22.yellow(` register it manually: ${MCP_ADD_HINT}`));
3682
3869
  }
3683
3870
  async function agentSetup(opts) {
3684
3871
  const moduleDir = dirname(fileURLToPath(import.meta.url));
3685
3872
  const shipped = resolveShippedSkillPath(moduleDir);
3686
3873
  if (!shipped) {
3687
3874
  console.error(
3688
- chalk21.red(
3875
+ chalk22.red(
3689
3876
  "error: could not find the packaged skill (skills/vibeview-agent/SKILL.md) \u2014 is this a complete install of the vibeview package?"
3690
3877
  )
3691
3878
  );
@@ -3694,9 +3881,9 @@ async function agentSetup(opts) {
3694
3881
  }
3695
3882
  const targetDir = skillInstallDir(opts.project ?? false, process.cwd(), homedir());
3696
3883
  const result = installSkill(shipped, targetDir);
3697
- console.log(chalk21.green(`\u2713 vibeview-agent skill ${result}: ${join2(targetDir, "SKILL.md")}`));
3884
+ console.log(chalk22.green(`\u2713 vibeview-agent skill ${result}: ${join3(targetDir, "SKILL.md")}`));
3698
3885
  console.log(
3699
- chalk21.dim(
3886
+ chalk22.dim(
3700
3887
  opts.project ? " Claude Code discovers it automatically for anyone working in this project." : " Claude Code discovers it automatically in every project on this machine."
3701
3888
  )
3702
3889
  );
@@ -3704,7 +3891,7 @@ async function agentSetup(opts) {
3704
3891
  if (!claudeCliAvailable()) {
3705
3892
  if (opts.mcp === true) {
3706
3893
  console.error(
3707
- chalk21.yellow(`\u26A0 claude CLI not found \u2014 install Claude Code, then run: ${MCP_ADD_HINT}`)
3894
+ chalk22.yellow(`\u26A0 claude CLI not found \u2014 install Claude Code, then run: ${MCP_ADD_HINT}`)
3708
3895
  );
3709
3896
  }
3710
3897
  } else if (opts.mcp === true || process.stdin.isTTY && await confirm({
@@ -3716,7 +3903,7 @@ async function agentSetup(opts) {
3716
3903
  }
3717
3904
  console.log(
3718
3905
  `
3719
- Suggested line for your project's ${chalk21.bold("CLAUDE.md")} (or ${chalk21.bold(
3906
+ Suggested line for your project's ${chalk22.bold("CLAUDE.md")} (or ${chalk22.bold(
3720
3907
  "AGENTS.md"
3721
3908
  )} for other agents):
3722
3909
  `
@@ -3724,17 +3911,17 @@ Suggested line for your project's ${chalk21.bold("CLAUDE.md")} (or ${chalk21.bol
3724
3911
  console.log(` ${CLAUDE_MD_SUGGESTION}
3725
3912
  `);
3726
3913
  console.log(
3727
- chalk21.dim(
3914
+ chalk22.dim(
3728
3915
  "Other agents (Cursor, Copilot, Codex, ...): install the same skill from GitHub with\n npx skills add vibeview/skills\nor point them at the skill file above, or add the MCP server per client \u2014\nsee https://vibeview.io/docs/agent-control"
3729
3916
  )
3730
3917
  );
3731
3918
  }
3732
3919
 
3733
3920
  // src/lib/update-check.ts
3734
- import { mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync } from "fs";
3921
+ import { mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync } from "fs";
3735
3922
  import { homedir as homedir2 } from "os";
3736
- import { dirname as dirname2, join as join3 } from "path";
3737
- var CACHE_FILE = join3(homedir2(), ".vibeview", "update-check.json");
3923
+ import { dirname as dirname2, join as join4 } from "path";
3924
+ var CACHE_FILE = join4(homedir2(), ".vibeview", "update-check.json");
3738
3925
  var REGISTRY_URL = "https://registry.npmjs.org/vibeview/latest";
3739
3926
  var ONE_DAY_MS = 24 * 60 * 60 * 1e3;
3740
3927
  function isNewerVersion(a, b) {
@@ -3765,7 +3952,7 @@ function updateNotice(current, latest) {
3765
3952
  }
3766
3953
  function readCache(file) {
3767
3954
  try {
3768
- const parsed = JSON.parse(readFileSync3(file, "utf8"));
3955
+ const parsed = JSON.parse(readFileSync4(file, "utf8"));
3769
3956
  if (typeof parsed?.checked_at === "number" && typeof parsed?.latest === "string") {
3770
3957
  return parsed;
3771
3958
  }
@@ -3848,11 +4035,14 @@ program.command("run-suite <suite-id>").description("Run all test cases in a sui
3848
4035
  "Pin run to a specific device (use `vibeview list-devices` to find one). Conflicts with --device-category."
3849
4036
  ).option(
3850
4037
  "--platform <platform>",
3851
- "Run the suite on another platform (ios, android, tvos, androidtv): runs your app there with the same bundle id / package"
4038
+ "Run the suite on another platform (ios, android, tvos, androidtv): runs your linked app on that platform, else the app with the same bundle id / package"
3852
4039
  ).option(
3853
4040
  "--app <app-id>",
3854
4041
  "Run the suite against this app instead of its own (e.g. another platform build with a different bundle id)"
3855
- ).option("--build <id>", "Specific AppBuild id to run against (defaults to latest)").option("--metadata <json>", "JSON object passed through to webhooks (e.g. PR context)").option("--metadata-file <path>", "Read JSON object from a file (use for nested JSON in CI)").action((suiteId, opts) => {
4042
+ ).option("--build <id>", "Specific AppBuild id to run against (defaults to latest)").option("--metadata <json>", "JSON object passed through to webhooks (e.g. PR context)").option("--metadata-file <path>", "Read JSON object from a file (use for nested JSON in CI)").option(
4043
+ "--no-retry",
4044
+ "Do not retry a failed test once (CI retries a failed test once by default)"
4045
+ ).action((suiteId, opts) => {
3856
4046
  if (opts.device && opts.deviceCategory) {
3857
4047
  process.stderr.write("error: --device cannot be combined with --device-category\n");
3858
4048
  process.exit(2);
@@ -3880,7 +4070,7 @@ program.command("run-suites").description("Run all suites matching a tag filter"
3880
4070
  []
3881
4071
  ).option("--match <mode>", "any (default) or all", "any").option(
3882
4072
  "--platform <platform>",
3883
- "Filter matched suites to one platform: ios, android, tvos, androidtv (default: no filter)"
4073
+ "Filter matched suites to one platform: ios, android, tvos, androidtv, roku (default: no filter)"
3884
4074
  ).option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--output <format>", "Output format: human, json, junit", "human").option("--verbose", "Show per-suite progress", false).option("--timeout <seconds>", "Per-suite timeout, capped at 2 hours total", "1800").option("--commit-sha <sha>", "Git commit SHA propagated to every SuiteRun").option("--model <model>", "AI model").option("--provider <provider>", "AI provider").option("--mode <mode>", "Execution mode: hybrid, ai, replay").option(
3885
4075
  "--device-category <category>",
3886
4076
  "Device category: phone, tablet, tv (default: tv for a TV platform, else phone)",
@@ -3888,13 +4078,16 @@ program.command("run-suites").description("Run all suites matching a tag filter"
3888
4078
  ).option(
3889
4079
  "--device <device_id>",
3890
4080
  "Pin run to a specific device (use `vibeview list-devices` to find one). Conflicts with --device-category."
3891
- ).option("--build <id>", "AppBuild id (validated against every matched suite)").option("--metadata <json>", "JSON object passed through to webhooks (e.g. PR context)").option("--metadata-file <path>", "Read JSON object from a file (use for nested JSON in CI)").action((opts) => {
4081
+ ).option("--build <id>", "AppBuild id (validated against every matched suite)").option("--metadata <json>", "JSON object passed through to webhooks (e.g. PR context)").option("--metadata-file <path>", "Read JSON object from a file (use for nested JSON in CI)").option(
4082
+ "--no-retry",
4083
+ "Do not retry a failed test once (CI retries a failed test once by default)"
4084
+ ).action((opts) => {
3892
4085
  if (opts.device && opts.deviceCategory) {
3893
4086
  process.stderr.write("error: --device cannot be combined with --device-category\n");
3894
4087
  process.exit(2);
3895
4088
  }
3896
4089
  try {
3897
- parseRunPlatform(opts.platform);
4090
+ parseSuitePlatformFilter(opts.platform);
3898
4091
  } catch (err) {
3899
4092
  process.stderr.write(`error: ${err.message}
3900
4093
  `);
@@ -3917,11 +4110,14 @@ program.command("run-test <test-id>").description("Run a single test case").opti
3917
4110
  "Pin run to a specific device (use `vibeview list-devices` to find one). Conflicts with --device-category."
3918
4111
  ).option(
3919
4112
  "--platform <platform>",
3920
- "Run the test on another platform (ios, android, tvos, androidtv): runs your app there with the same bundle id / package"
4113
+ "Run the test on another platform (ios, android, tvos, androidtv): runs your linked app on that platform, else the app with the same bundle id / package"
3921
4114
  ).option(
3922
4115
  "--app <app-id>",
3923
4116
  "Run the test against this app instead of its own (e.g. another platform build with a different bundle id)"
3924
- ).option("--build <id>", "Specific AppBuild id to run against (defaults to latest)").option("--metadata <json>", "JSON object passed through to webhooks (e.g. PR context)").option("--metadata-file <path>", "Read JSON object from a file (use for nested JSON in CI)").action((testId, opts) => {
4117
+ ).option("--build <id>", "Specific AppBuild id to run against (defaults to latest)").option("--metadata <json>", "JSON object passed through to webhooks (e.g. PR context)").option("--metadata-file <path>", "Read JSON object from a file (use for nested JSON in CI)").option(
4118
+ "--no-retry",
4119
+ "Do not retry a failed test once (CI retries a failed test once by default)"
4120
+ ).action((testId, opts) => {
3925
4121
  if (opts.device && opts.deviceCategory) {
3926
4122
  process.stderr.write("error: --device cannot be combined with --device-category\n");
3927
4123
  process.exit(2);
@@ -3969,12 +4165,18 @@ program.command("list-devices").description("List devices registered with the pl
3969
4165
  program.command("upload-app <file>").description(
3970
4166
  "Upload an app build (zipped simulator .app for iOS/tvOS, .apk for Android/Android TV, or a Roku channel .zip)"
3971
4167
  ).option("--token <token>", "API token").option("--output <format>", "Output format: human, json", "human").option("--name <name>", "App name (auto-detected if not provided)").option("--note <text>", "Note stored on the uploaded build (shown in the dashboard)").action((file, opts) => uploadApp(file, opts));
3972
- 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)").addOption(
4168
+ program.command("create-test [name]").description("Create an AI test case, optionally in one or more groups").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)").option(
4169
+ "--group <id...>",
4170
+ "Group (suite) ID to add the test to; repeat or list several. Same app as --app"
4171
+ ).addOption(
3973
4172
  new Option(
3974
4173
  "--if-exists <mode>",
3975
- "When a standalone test with this name already exists for the app: fail, update it, or create a new one alongside it. Default: reuse an identical test, refuse a different one"
4174
+ "When a test with this name already exists for the app (outside any group, or in one of the --group groups): fail, update it, or create a new one alongside it. Default: reuse an identical test, refuse a different one"
3976
4175
  ).choices(["fail", "update", "new"])
3977
4176
  ).action((name, opts) => createTest({ ...opts, testName: name || opts.testName || "CLI Test" }));
4177
+ program.command("import-maestro <path>").description(
4178
+ "Import Maestro flows as tests: a flow file, a .zip of your flows folder, or the folder itself"
4179
+ ).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("--group <group>", "Put the imported tests in this group (name or id)").option("--dry-run", "Show what would be imported and every flagged command; save nothing", false).action((path, opts) => importMaestro(path, opts));
3978
4180
  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));
3979
4181
  program.command("logout").description("Revoke the saved login token and remove it from ~/.vibeview/config.json").action(() => logout());
3980
4182
  program.command("dev").description(
@@ -4147,7 +4349,7 @@ program.command("dev-reload").description(
4147
4349
  program.command("mcp").description(
4148
4350
  "Run an MCP stdio server exposing the agent verbs (tap, swipe, type, ...) plus dev_start/dev_stop/dev_reload as tools"
4149
4351
  ).option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").action(async (opts) => {
4150
- const { runMcpServer } = await import("./server-QRIH34XN.js");
4352
+ const { runMcpServer } = await import("./server-FMU3HQWF.js");
4151
4353
  await runMcpServer({ ...opts, version: pkg.version });
4152
4354
  });
4153
4355
  program.command("agent-setup").description(