vibeview 0.18.0 → 0.19.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
@@ -6,6 +6,7 @@ import {
6
6
  clearDevState,
7
7
  dev,
8
8
  fetchFinalSummary,
9
+ formatDuration,
9
10
  killOwnedMetro,
10
11
  newTransitionState,
11
12
  printOutcomeLine,
@@ -13,7 +14,7 @@ import {
13
14
  readDevState,
14
15
  readMetroState,
15
16
  runAgentVerb
16
- } from "./chunk-MGP3UR7P.js";
17
+ } from "./chunk-T6FS4ELD.js";
17
18
  import {
18
19
  ApiClient,
19
20
  EXIT_INFRA_ERROR,
@@ -26,7 +27,7 @@ import {
26
27
  resolveAuth,
27
28
  saveConfig,
28
29
  uploadApp
29
- } from "./chunk-BJEDH672.js";
30
+ } from "./chunk-FG2J2BGS.js";
30
31
 
31
32
  // src/cli.ts
32
33
  import { createRequire } from "module";
@@ -1210,7 +1211,7 @@ async function buildsList(opts, deps) {
1210
1211
  }
1211
1212
  }
1212
1213
  try {
1213
- const { builds: builds2 } = await d.listCloudBuilds(limit);
1214
+ const { builds: builds2 } = await d.listCloudBuilds(limit !== void 0 ? { limit } : void 0);
1214
1215
  if (opts.json) {
1215
1216
  console.log(JSON.stringify(builds2, null, 2));
1216
1217
  } else {
@@ -1378,9 +1379,192 @@ function printError(opts, err) {
1378
1379
  console.error(chalk11.red(`error: ${message}`));
1379
1380
  }
1380
1381
 
1381
- // src/commands/whoami.ts
1382
+ // src/commands/submit.ts
1382
1383
  import chalk12 from "chalk";
1383
- function defaultDeps2(opts) {
1384
+ function defaultDeps2(client) {
1385
+ return {
1386
+ createSubmission: client.createSubmission.bind(client),
1387
+ getSubmission: client.getSubmission.bind(client),
1388
+ getSubmissionLog: client.getSubmissionLog.bind(client),
1389
+ listCloudBuilds: client.listCloudBuilds.bind(client)
1390
+ };
1391
+ }
1392
+ var POLL_INTERVAL_MS2 = 2e3;
1393
+ var SUBMIT_PLATFORMS = ["ios", "tvos", "android", "androidtv"];
1394
+ function isSubmitPlatform(p) {
1395
+ return SUBMIT_PLATFORMS.includes(p);
1396
+ }
1397
+ var PLAY_TRACKS = ["internal", "alpha", "beta", "production"];
1398
+ var SUBMISSION_TERMINAL_STATUSES = /* @__PURE__ */ new Set(["succeeded", "failed"]);
1399
+ var MAX_CONSECUTIVE_POLL_FAILURES2 = 5;
1400
+ function emit(json, evt) {
1401
+ if (json) console.log(JSON.stringify(evt));
1402
+ }
1403
+ async function submitCommand(opts, deps) {
1404
+ if (!opts.platform || !isSubmitPlatform(opts.platform)) {
1405
+ throw new UsageError(
1406
+ `--platform is required and must be one of ${SUBMIT_PLATFORMS.join(", ")}`
1407
+ );
1408
+ }
1409
+ const platform = opts.platform;
1410
+ const androidFamily = platform === "android" || platform === "androidtv";
1411
+ if (opts.build && opts.latest) {
1412
+ throw new UsageError("pass either --build <id> or --latest, not both");
1413
+ }
1414
+ if (!opts.build && !opts.latest) {
1415
+ throw new UsageError(
1416
+ "pass --build <id> (see `vibeview builds list`) or --latest (the newest succeeded production build for this platform)"
1417
+ );
1418
+ }
1419
+ if (!androidFamily && opts.track !== void 0) {
1420
+ throw new UsageError(`--track only applies to android/androidtv (Play) \u2014 not ${platform}`);
1421
+ }
1422
+ if (!androidFamily && opts.package !== void 0) {
1423
+ throw new UsageError(`--package only applies to android/androidtv (Play) \u2014 not ${platform}`);
1424
+ }
1425
+ if (androidFamily && opts.ascKey !== void 0) {
1426
+ throw new UsageError(`--asc-key only applies to ios/tvos (TestFlight) \u2014 not ${platform}`);
1427
+ }
1428
+ if (opts.track !== void 0 && !PLAY_TRACKS.includes(opts.track)) {
1429
+ throw new UsageError(`--track must be one of ${PLAY_TRACKS.join(", ")} \u2014 got "${opts.track}"`);
1430
+ }
1431
+ const { token, baseUrl } = resolveAuth(opts);
1432
+ const client = new ApiClient(baseUrl, token);
1433
+ const d = deps ?? defaultDeps2(client);
1434
+ try {
1435
+ const buildId = opts.build ?? await resolveLatestBuild(platform, d);
1436
+ const sub = await d.createSubmission({
1437
+ build: buildId,
1438
+ platform,
1439
+ ...opts.track !== void 0 ? { track: opts.track } : {},
1440
+ ...opts.package !== void 0 ? { package: opts.package } : {},
1441
+ ...opts.ascKey !== void 0 ? { asc_key: opts.ascKey } : {}
1442
+ });
1443
+ emit(opts.json, {
1444
+ event: "submission_created",
1445
+ id: sub.id,
1446
+ build_id: sub.build_id,
1447
+ platform: sub.platform
1448
+ });
1449
+ if (opts.wait === false) {
1450
+ if (!opts.json) console.log(sub.id);
1451
+ process.exitCode = 0;
1452
+ return;
1453
+ }
1454
+ if (!opts.json) {
1455
+ console.error(` ${chalk12.cyan(sub.id)} ${chalk12.dim(`[${sub.platform}]`)}`);
1456
+ }
1457
+ const final = await followSubmission(sub, opts, d);
1458
+ process.exitCode = final.status === "succeeded" ? 0 : 1;
1459
+ } catch (err) {
1460
+ printError2(opts, err);
1461
+ process.exitCode = 1;
1462
+ }
1463
+ }
1464
+ async function resolveLatestBuild(platform, deps) {
1465
+ const { builds: builds2 } = await deps.listCloudBuilds({
1466
+ platform,
1467
+ status: "succeeded",
1468
+ distribution: "production",
1469
+ limit: 200
1470
+ });
1471
+ const match = builds2.find(
1472
+ (b) => b.platform === platform && b.distribution === "production" && b.status === "succeeded" && b.has_artifact
1473
+ );
1474
+ if (!match) {
1475
+ throw new Error(
1476
+ `no submittable ${platform} build found \u2014 --latest needs a succeeded production build whose artifact is still stored. Run \`vibeview build --cloud --production --platform ${platform}\` first, or pass --build <id> (see \`vibeview builds list\`).`
1477
+ );
1478
+ }
1479
+ if (match.platform !== platform) {
1480
+ throw new Error(
1481
+ `--latest resolved build ${match.id}, which is ${match.platform}, not ${platform} \u2014 refusing to submit it.`
1482
+ );
1483
+ }
1484
+ return match.id;
1485
+ }
1486
+ async function followSubmission(created, opts, deps) {
1487
+ const pollIntervalMs = deps.pollIntervalMs ?? POLL_INTERVAL_MS2;
1488
+ let offset = 0;
1489
+ let status = created.status;
1490
+ let consecutiveFailures = 0;
1491
+ let printedQueued = false;
1492
+ let printedRunning = false;
1493
+ while (!SUBMISSION_TERMINAL_STATUSES.has(status)) {
1494
+ try {
1495
+ const log = await deps.getSubmissionLog(created.id, offset);
1496
+ if (log.status === "queued" && !printedQueued) {
1497
+ printedQueued = true;
1498
+ if (opts.json) {
1499
+ emit(opts.json, { event: "status", id: created.id, status: "queued" });
1500
+ } else {
1501
+ console.error(chalk12.dim("\u23F3 queued \u2014 waiting for a submission slot"));
1502
+ }
1503
+ } else if (log.status === "running" && !printedRunning) {
1504
+ printedRunning = true;
1505
+ if (opts.json) {
1506
+ emit(opts.json, { event: "status", id: created.id, status: "running" });
1507
+ } else {
1508
+ console.error(`${chalk12.cyan("\u25B6")} submitting`);
1509
+ }
1510
+ }
1511
+ if (log.content) {
1512
+ if (opts.json) {
1513
+ emit(opts.json, { event: "submission_log", id: created.id, content: log.content });
1514
+ } else {
1515
+ process.stdout.write(log.content);
1516
+ }
1517
+ }
1518
+ offset = log.offset;
1519
+ status = log.status;
1520
+ consecutiveFailures = 0;
1521
+ } catch (err) {
1522
+ if (err instanceof NotFoundError) throw err;
1523
+ consecutiveFailures++;
1524
+ if (consecutiveFailures >= MAX_CONSECUTIVE_POLL_FAILURES2) {
1525
+ const message = err instanceof Error ? err.message : String(err);
1526
+ throw new Error(
1527
+ `submission poll failed ${consecutiveFailures} times in a row, giving up: ${message}
1528
+ The submission continues server-side \u2014 see its outcome on the Builds page in the dashboard (expand build ${created.build_id}'s row \u2192 Submissions).`
1529
+ );
1530
+ }
1531
+ }
1532
+ if (!SUBMISSION_TERMINAL_STATUSES.has(status)) {
1533
+ await new Promise((r) => setTimeout(r, pollIntervalMs));
1534
+ }
1535
+ }
1536
+ let final;
1537
+ try {
1538
+ final = await deps.getSubmission(created.id);
1539
+ } catch {
1540
+ final = { ...created, status };
1541
+ }
1542
+ emit(opts.json, {
1543
+ event: "submission_finished",
1544
+ id: final.id,
1545
+ status: final.status,
1546
+ error_summary: final.error_summary
1547
+ });
1548
+ if (!opts.json) {
1549
+ const ok = final.status === "succeeded";
1550
+ const mark = ok ? chalk12.green("\u2713") : chalk12.red("\u2717");
1551
+ const duration = formatDuration(final.started_at, final.finished_at);
1552
+ let line = `${mark} ${final.status}`;
1553
+ if (duration) line += ` (${duration})`;
1554
+ if (final.error_summary) line += ` \u2014 ${final.error_summary}`;
1555
+ console.error(line);
1556
+ }
1557
+ return final;
1558
+ }
1559
+ function printError2(opts, err) {
1560
+ const message = err instanceof Error ? err.message : String(err);
1561
+ if (opts.json) console.log(JSON.stringify({ event: "error", message }));
1562
+ console.error(chalk12.red(`error: ${message}`));
1563
+ }
1564
+
1565
+ // src/commands/whoami.ts
1566
+ import chalk13 from "chalk";
1567
+ function defaultDeps3(opts) {
1384
1568
  const { token, baseUrl } = resolveAuth(opts);
1385
1569
  const client = new ApiClient(baseUrl, token);
1386
1570
  return { fetchMe: () => client.whoAmI() };
@@ -1392,7 +1576,7 @@ function effectiveRole(role, tokenRole) {
1392
1576
  return (RANK[tokenRole] ?? 3) < (RANK[role] ?? 3) ? tokenRole : role;
1393
1577
  }
1394
1578
  async function whoAmI(opts, deps) {
1395
- const d = deps ?? defaultDeps2(opts);
1579
+ const d = deps ?? defaultDeps3(opts);
1396
1580
  try {
1397
1581
  const me = await d.fetchMe();
1398
1582
  const effective = effectiveRole(me.role, me.token_role);
@@ -1401,27 +1585,27 @@ async function whoAmI(opts, deps) {
1401
1585
  process.exitCode = 0;
1402
1586
  return;
1403
1587
  }
1404
- console.log(`${chalk12.bold("Logged in as")} ${me.email}`);
1588
+ console.log(`${chalk13.bold("Logged in as")} ${me.email}`);
1405
1589
  if (me.memberships.length === 1) {
1406
- console.log(`${chalk12.bold("Organization")} ${me.memberships[0].organization_name}`);
1590
+ console.log(`${chalk13.bold("Organization")} ${me.memberships[0].organization_name}`);
1407
1591
  } else if (me.memberships.length > 1) {
1408
- console.log(chalk12.bold("Organizations"));
1592
+ console.log(chalk13.bold("Organizations"));
1409
1593
  for (const m of me.memberships) {
1410
1594
  console.log(` ${m.organization_name} (${m.role})`);
1411
1595
  }
1412
1596
  }
1413
- console.log(`${chalk12.bold("Member role")} ${me.role ?? "\u2014"}`);
1414
- console.log(`${chalk12.bold("Token role")} ${me.token_role ?? "\u2014 (session auth)"}`);
1415
- console.log(`${chalk12.bold("Effective role")} ${effective ?? "\u2014"}`);
1597
+ console.log(`${chalk13.bold("Member role")} ${me.role ?? "\u2014"}`);
1598
+ console.log(`${chalk13.bold("Token role")} ${me.token_role ?? "\u2014 (session auth)"}`);
1599
+ console.log(`${chalk13.bold("Effective role")} ${effective ?? "\u2014"}`);
1416
1600
  if (me.is_superadmin) {
1417
- console.log(chalk12.dim("Superadmin (platform-wide; does not raise the org role above)"));
1601
+ console.log(chalk13.dim("Superadmin (platform-wide; does not raise the org role above)"));
1418
1602
  }
1419
1603
  if (me.role === "admin" && me.token_role && me.token_role !== "admin") {
1420
1604
  console.log(
1421
- chalk12.yellow(
1605
+ chalk13.yellow(
1422
1606
  `
1423
- 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 ${chalk12.bold("vibeview env set")}) will fail with a permissions error.
1424
- Create an admin API token in the dashboard and use ${chalk12.bold("--token")} or ${chalk12.bold("VIBEVIEW_API_TOKEN")}.`
1607
+ 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 ${chalk13.bold("vibeview env set")}) will fail with a permissions error.
1608
+ Create an admin API token in the dashboard and use ${chalk13.bold("--token")} or ${chalk13.bold("VIBEVIEW_API_TOKEN")}.`
1425
1609
  )
1426
1610
  );
1427
1611
  }
@@ -1431,7 +1615,7 @@ Create an admin API token in the dashboard and use ${chalk12.bold("--token")} or
1431
1615
  if (opts.json) {
1432
1616
  console.log(JSON.stringify({ event: "error", message }));
1433
1617
  }
1434
- console.error(chalk12.red(`error: ${message}`));
1618
+ console.error(chalk13.red(`error: ${message}`));
1435
1619
  process.exitCode = 1;
1436
1620
  }
1437
1621
  }
@@ -1439,7 +1623,7 @@ Create an admin API token in the dashboard and use ${chalk12.bold("--token")} or
1439
1623
  // src/commands/env.ts
1440
1624
  import { chmod, open as open2, readFile as readFileAsync } from "fs/promises";
1441
1625
  import { basename } from "path";
1442
- import chalk13 from "chalk";
1626
+ import chalk14 from "chalk";
1443
1627
  import { password } from "@inquirer/prompts";
1444
1628
 
1445
1629
  // src/lib/gitignore.ts
@@ -1466,7 +1650,7 @@ async function writeSecureFile(path, content) {
1466
1650
  }
1467
1651
  await chmod(path, 384);
1468
1652
  }
1469
- function defaultDeps3(client) {
1653
+ function defaultDeps4(client) {
1470
1654
  return {
1471
1655
  listVars: (app) => client.listBuildEnvVars(app),
1472
1656
  setVar: (body) => client.setBuildEnvVar(body),
@@ -1495,11 +1679,11 @@ function defaultDeps3(client) {
1495
1679
  isIgnored: (path) => isGitIgnored(path),
1496
1680
  writeFile: writeSecureFile,
1497
1681
  readFile: (path) => readFileAsync(path),
1498
- warn: (message) => console.error(chalk13.yellow(`warning: ${message}`))
1682
+ warn: (message) => console.error(chalk14.yellow(`warning: ${message}`))
1499
1683
  };
1500
1684
  }
1501
1685
  async function envList(opts, deps) {
1502
- const d = deps ?? defaultDeps3(clientFor(opts));
1686
+ const d = deps ?? defaultDeps4(clientFor(opts));
1503
1687
  try {
1504
1688
  const { variables } = await d.listVars(opts.app);
1505
1689
  if (opts.json) {
@@ -1509,7 +1693,7 @@ async function envList(opts, deps) {
1509
1693
  }
1510
1694
  process.exitCode = 0;
1511
1695
  } catch (err) {
1512
- printError2(opts, err);
1696
+ printError3(opts, err);
1513
1697
  process.exitCode = 1;
1514
1698
  }
1515
1699
  }
@@ -1527,12 +1711,12 @@ function formatEnvVarsHuman(variables) {
1527
1711
  const widths = HEADERS3.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
1528
1712
  const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
1529
1713
  const join3 = (cells) => cells.join(" ").trimEnd();
1530
- const lines = [chalk13.bold(join3(padCells(HEADERS3)))];
1714
+ const lines = [chalk14.bold(join3(padCells(HEADERS3)))];
1531
1715
  for (const row of rows) lines.push(join3(padCells(row)));
1532
1716
  return lines.join("\n") + "\n";
1533
1717
  }
1534
1718
  async function envSet(name, value, opts, deps) {
1535
- const d = deps ?? defaultDeps3(clientFor(opts));
1719
+ const d = deps ?? defaultDeps4(clientFor(opts));
1536
1720
  let resolvedValue = value;
1537
1721
  if (resolvedValue === void 0 && !opts.file) {
1538
1722
  if (opts.json || !d.isTTY) {
@@ -1558,7 +1742,7 @@ async function envSet(name, value, opts, deps) {
1558
1742
  printSetResult(opts, result);
1559
1743
  process.exitCode = 0;
1560
1744
  } catch (err) {
1561
- printError2(opts, err);
1745
+ printError3(opts, err);
1562
1746
  process.exitCode = 1;
1563
1747
  }
1564
1748
  }
@@ -1577,26 +1761,26 @@ function printSetResult(opts, result) {
1577
1761
  if (opts.json) {
1578
1762
  console.log(JSON.stringify(result));
1579
1763
  } else {
1580
- console.log(chalk13.green(`\u2713 set ${result.name} (${result.visibility})`));
1764
+ console.log(chalk14.green(`\u2713 set ${result.name} (${result.visibility})`));
1581
1765
  }
1582
1766
  }
1583
1767
  async function envRm(name, opts, deps) {
1584
- const d = deps ?? defaultDeps3(clientFor(opts));
1768
+ const d = deps ?? defaultDeps4(clientFor(opts));
1585
1769
  try {
1586
1770
  await d.rmVar({ name, app: opts.app });
1587
1771
  if (opts.json) {
1588
1772
  console.log(JSON.stringify({ event: "deleted", name }));
1589
1773
  } else {
1590
- console.log(chalk13.green(`\u2713 removed ${name}`));
1774
+ console.log(chalk14.green(`\u2713 removed ${name}`));
1591
1775
  }
1592
1776
  process.exitCode = 0;
1593
1777
  } catch (err) {
1594
- printError2(opts, err);
1778
+ printError3(opts, err);
1595
1779
  process.exitCode = 1;
1596
1780
  }
1597
1781
  }
1598
1782
  async function envPull(opts, deps) {
1599
- const d = deps ?? defaultDeps3(clientFor(opts));
1783
+ const d = deps ?? defaultDeps4(clientFor(opts));
1600
1784
  const outPath = opts.out ?? ".env";
1601
1785
  try {
1602
1786
  const { variables } = await d.pullVars(opts.app);
@@ -1619,11 +1803,11 @@ async function envPull(opts, deps) {
1619
1803
  if (opts.json) {
1620
1804
  console.log(JSON.stringify({ event: "pulled", path: outPath, count: variables.length }));
1621
1805
  } else {
1622
- console.log(chalk13.green(`\u2713 wrote ${variables.length} variable(s) to ${outPath}`));
1806
+ console.log(chalk14.green(`\u2713 wrote ${variables.length} variable(s) to ${outPath}`));
1623
1807
  }
1624
1808
  process.exitCode = 0;
1625
1809
  } catch (err) {
1626
- printError2(opts, err);
1810
+ printError3(opts, err);
1627
1811
  process.exitCode = 1;
1628
1812
  }
1629
1813
  }
@@ -1655,10 +1839,10 @@ function escapeEnvValue(value) {
1655
1839
  }
1656
1840
  return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
1657
1841
  }
1658
- function printError2(opts, err) {
1842
+ function printError3(opts, err) {
1659
1843
  const message = err instanceof Error ? err.message : String(err);
1660
1844
  if (opts.json) console.log(JSON.stringify({ event: "error", message }));
1661
- console.error(chalk13.red(`error: ${message}`));
1845
+ console.error(chalk14.red(`error: ${message}`));
1662
1846
  }
1663
1847
 
1664
1848
  // src/commands/credentials.ts
@@ -1670,13 +1854,13 @@ import {
1670
1854
  } from "fs/promises";
1671
1855
  import { mkdirSync } from "fs";
1672
1856
  import { basename as basename2, extname, join } from "path";
1673
- import chalk14 from "chalk";
1857
+ import chalk15 from "chalk";
1674
1858
  import { input, password as password2 } from "@inquirer/prompts";
1675
1859
  function clientFor2(opts) {
1676
1860
  const { token, baseUrl } = resolveAuth(opts);
1677
1861
  return new ApiClient(baseUrl, token);
1678
1862
  }
1679
- function defaultDeps4(client) {
1863
+ function defaultDeps5(client) {
1680
1864
  return {
1681
1865
  addCredential: (body) => client.addSigningCredential({
1682
1866
  platform: body.platform,
@@ -1721,6 +1905,16 @@ function defaultDeps4(client) {
1721
1905
  }
1722
1906
  await client.deleteAscKey(match.id);
1723
1907
  },
1908
+ addPlayKey: (body) => client.addPlayKey(body),
1909
+ listPlayKeys: () => client.listPlayKeys(),
1910
+ deletePlayKey: async (name) => {
1911
+ const { keys } = await client.listPlayKeys();
1912
+ const match = keys.find((k) => k.name === name);
1913
+ if (!match) {
1914
+ throw new Error(`No Google Play key named "${name}" found.`);
1915
+ }
1916
+ await client.deletePlayKey(match.id);
1917
+ },
1724
1918
  prompt: ({ message, hidden }) => hidden ? password2({ message }) : input({ message }),
1725
1919
  isTTY: Boolean(process.stdin.isTTY),
1726
1920
  readFile: (path) => readFileAsync2(path),
@@ -1733,7 +1927,7 @@ function defaultDeps4(client) {
1733
1927
  mkdir: (dir) => mkdirSync(dir, { recursive: true }),
1734
1928
  writeFile: (path, data, mode) => writeFileAsync(path, data, mode !== void 0 ? { mode } : void 0),
1735
1929
  chmod: (path, mode) => chmodAsync(path, mode),
1736
- warn: (message) => console.error(chalk14.yellow(`warning: ${message}`))
1930
+ warn: (message) => console.error(chalk15.yellow(`warning: ${message}`))
1737
1931
  };
1738
1932
  }
1739
1933
  var ANDROID_KIND = "android_keystore";
@@ -1743,7 +1937,7 @@ function isCredentialPlatform(p) {
1743
1937
  return CREDENTIAL_PLATFORMS.includes(p);
1744
1938
  }
1745
1939
  async function credentialsAdd(opts, deps) {
1746
- const d = deps ?? defaultDeps4(clientFor2(opts));
1940
+ const d = deps ?? defaultDeps5(clientFor2(opts));
1747
1941
  if (!opts.platform || !isCredentialPlatform(opts.platform)) {
1748
1942
  throw new UsageError('--platform is required and must be "ios", "android", or "tvos"');
1749
1943
  }
@@ -1802,16 +1996,16 @@ async function credentialsAdd(opts, deps) {
1802
1996
  if (opts.json) {
1803
1997
  console.log(JSON.stringify(result));
1804
1998
  } else {
1805
- console.log(chalk14.green(`\u2713 added ${result.name} (${result.platform})`));
1999
+ console.log(chalk15.green(`\u2713 added ${result.name} (${result.platform})`));
1806
2000
  }
1807
2001
  process.exitCode = 0;
1808
2002
  } catch (err) {
1809
- printError3(opts, err);
2003
+ printError4(opts, err);
1810
2004
  process.exitCode = 1;
1811
2005
  }
1812
2006
  }
1813
2007
  async function credentialsGenerate(opts, deps) {
1814
- const d = deps ?? defaultDeps4(clientFor2(opts));
2008
+ const d = deps ?? defaultDeps5(clientFor2(opts));
1815
2009
  if (!opts.platform) {
1816
2010
  throw new UsageError('--platform is required and must be "android", "ios", or "tvos"');
1817
2011
  }
@@ -1860,27 +2054,27 @@ async function credentialsGenerate(opts, deps) {
1860
2054
  }
1861
2055
  if (opts.json) {
1862
2056
  } else {
1863
- console.log(chalk14.green(`\u2713 generated ${result.name} (android)`));
2057
+ console.log(chalk15.green(`\u2713 generated ${result.name} (android)`));
1864
2058
  console.log("");
1865
- console.log(` Keystore saved to: ${chalk14.bold(savedTo)}`);
1866
- console.log(` Key alias: ${chalk14.bold(result.key_alias)}`);
1867
- console.log(` Store password: ${chalk14.bold(result.store_password)}`);
1868
- console.log(` Key password: ${chalk14.bold(result.key_password)}`);
2059
+ console.log(` Keystore saved to: ${chalk15.bold(savedTo)}`);
2060
+ console.log(` Key alias: ${chalk15.bold(result.key_alias)}`);
2061
+ console.log(` Store password: ${chalk15.bold(result.store_password)}`);
2062
+ console.log(` Key password: ${chalk15.bold(result.key_password)}`);
1869
2063
  console.log("");
1870
2064
  console.log(
1871
- chalk14.yellow(
2065
+ chalk15.yellow(
1872
2066
  " These are shown ONCE \u2014 VibeView cannot recover the keystore file or its passwords. Back them up somewhere safe."
1873
2067
  )
1874
2068
  );
1875
2069
  console.log(
1876
- chalk14.yellow(
2070
+ chalk15.yellow(
1877
2071
  " The credential is already uploaded and active for android and androidtv production builds."
1878
2072
  )
1879
2073
  );
1880
2074
  }
1881
2075
  process.exitCode = 0;
1882
2076
  } catch (err) {
1883
- printError3(opts, err);
2077
+ printError4(opts, err);
1884
2078
  process.exitCode = 1;
1885
2079
  }
1886
2080
  }
@@ -1899,29 +2093,29 @@ async function generateAppleCredential(platform, opts, d) {
1899
2093
  if (opts.json) {
1900
2094
  console.log(JSON.stringify(result));
1901
2095
  } else {
1902
- console.log(chalk14.green(`\u2713 generated ${result.name} (${result.platform})`));
2096
+ console.log(chalk15.green(`\u2713 generated ${result.name} (${result.platform})`));
1903
2097
  console.log("");
1904
- console.log(` Scope: ${chalk14.bold(result.app_public_id ?? "org-wide")}`);
2098
+ console.log(` Scope: ${chalk15.bold(result.app_public_id ?? "org-wide")}`);
1905
2099
  const expires = formatExpires(result);
1906
2100
  if (expires) {
1907
- console.log(` Expires: ${chalk14.bold(expires)}`);
2101
+ console.log(` Expires: ${chalk15.bold(expires)}`);
1908
2102
  }
1909
2103
  console.log("");
1910
2104
  console.log(
1911
2105
  ` The certificate and provisioning profile are stored with VibeView and already active for ${result.platform} production builds.`
1912
2106
  );
1913
2107
  console.log(
1914
- ` Run ${chalk14.bold(`vibeview credentials download ${result.name}`)} if you need the files locally.`
2108
+ ` Run ${chalk15.bold(`vibeview credentials download ${result.name}`)} if you need the files locally.`
1915
2109
  );
1916
2110
  }
1917
2111
  process.exitCode = 0;
1918
2112
  } catch (err) {
1919
- printError3(opts, err);
2113
+ printError4(opts, err);
1920
2114
  process.exitCode = 1;
1921
2115
  }
1922
2116
  }
1923
2117
  async function credentialsDownload(name, opts, deps) {
1924
- const d = deps ?? defaultDeps4(clientFor2(opts));
2118
+ const d = deps ?? defaultDeps5(clientFor2(opts));
1925
2119
  const outDir = opts.out ?? process.cwd();
1926
2120
  try {
1927
2121
  const { credentials: credentials2 } = await d.listCredentials(opts.app);
@@ -1977,26 +2171,26 @@ async function credentialsDownload(name, opts, deps) {
1977
2171
  await d.chmod(path, 384);
1978
2172
  }
1979
2173
  if (!opts.json) {
1980
- console.log(chalk14.green(`\u2713 downloaded ${material.name} (${material.platform})`));
2174
+ console.log(chalk15.green(`\u2713 downloaded ${material.name} (${material.platform})`));
1981
2175
  console.log("");
1982
- console.log(` Saved to: ${chalk14.bold(savedTo)}`);
2176
+ console.log(` Saved to: ${chalk15.bold(savedTo)}`);
1983
2177
  if (profileSavedTo !== null) {
1984
- console.log(` Profile: ${chalk14.bold(profileSavedTo)}`);
2178
+ console.log(` Profile: ${chalk15.bold(profileSavedTo)}`);
1985
2179
  }
1986
2180
  for (const [key, value] of Object.entries(material.passwords)) {
1987
- console.log(` ${key}: ${chalk14.bold(value)}`);
2181
+ console.log(` ${key}: ${chalk15.bold(value)}`);
1988
2182
  }
1989
2183
  console.log("");
1990
- console.log(chalk14.yellow(" This download is recorded in your organization's audit log."));
2184
+ console.log(chalk15.yellow(" This download is recorded in your organization's audit log."));
1991
2185
  }
1992
2186
  process.exitCode = 0;
1993
2187
  } catch (err) {
1994
- printError3(opts, err);
2188
+ printError4(opts, err);
1995
2189
  process.exitCode = 1;
1996
2190
  }
1997
2191
  }
1998
2192
  async function ascKeyAdd(opts, deps) {
1999
- const d = deps ?? defaultDeps4(clientFor2(opts));
2193
+ const d = deps ?? defaultDeps5(clientFor2(opts));
2000
2194
  if (!opts.keyId) {
2001
2195
  throw new UsageError("--key-id is required (the 10-character App Store Connect key id)");
2002
2196
  }
@@ -2018,25 +2212,25 @@ async function ascKeyAdd(opts, deps) {
2018
2212
  if (opts.json) {
2019
2213
  console.log(JSON.stringify(result));
2020
2214
  } else {
2021
- console.log(chalk14.green(`\u2713 added App Store Connect key ${result.name}`));
2215
+ console.log(chalk15.green(`\u2713 added App Store Connect key ${result.name}`));
2022
2216
  console.log("");
2023
- console.log(` Key ID: ${chalk14.bold(result.key_id)}`);
2024
- console.log(` Issuer ID: ${chalk14.bold(result.issuer_id)}`);
2217
+ console.log(` Key ID: ${chalk15.bold(result.key_id)}`);
2218
+ console.log(` Issuer ID: ${chalk15.bold(result.issuer_id)}`);
2025
2219
  console.log("");
2026
2220
  console.log(
2027
- chalk14.yellow(
2221
+ chalk15.yellow(
2028
2222
  " The .p8 is stored encrypted and will never be shown again \u2014 keep your own copy somewhere safe."
2029
2223
  )
2030
2224
  );
2031
2225
  }
2032
2226
  process.exitCode = 0;
2033
2227
  } catch (err) {
2034
- printError3(opts, err);
2228
+ printError4(opts, err);
2035
2229
  process.exitCode = 1;
2036
2230
  }
2037
2231
  }
2038
2232
  async function ascKeyList(opts, deps) {
2039
- const d = deps ?? defaultDeps4(clientFor2(opts));
2233
+ const d = deps ?? defaultDeps5(clientFor2(opts));
2040
2234
  try {
2041
2235
  const { keys } = await d.listAscKeys();
2042
2236
  if (opts.json) {
@@ -2046,7 +2240,7 @@ async function ascKeyList(opts, deps) {
2046
2240
  }
2047
2241
  process.exitCode = 0;
2048
2242
  } catch (err) {
2049
- printError3(opts, err);
2243
+ printError4(opts, err);
2050
2244
  process.exitCode = 1;
2051
2245
  }
2052
2246
  }
@@ -2059,27 +2253,103 @@ function formatAscKeysHuman(keys) {
2059
2253
  const widths = HEADERS3.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
2060
2254
  const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
2061
2255
  const join3 = (cells) => cells.join(" ").trimEnd();
2062
- const lines = [chalk14.bold(join3(padCells(HEADERS3)))];
2256
+ const lines = [chalk15.bold(join3(padCells(HEADERS3)))];
2063
2257
  for (const row of rows) lines.push(join3(padCells(row)));
2064
2258
  return lines.join("\n") + "\n";
2065
2259
  }
2066
2260
  async function ascKeyRm(name, opts, deps) {
2067
- const d = deps ?? defaultDeps4(clientFor2(opts));
2261
+ const d = deps ?? defaultDeps5(clientFor2(opts));
2068
2262
  try {
2069
2263
  await d.deleteAscKey(name);
2070
2264
  if (opts.json) {
2071
2265
  console.log(JSON.stringify({ event: "deleted", name }));
2072
2266
  } else {
2073
- console.log(chalk14.green(`\u2713 removed App Store Connect key ${name}`));
2267
+ console.log(chalk15.green(`\u2713 removed App Store Connect key ${name}`));
2074
2268
  }
2075
2269
  process.exitCode = 0;
2076
2270
  } catch (err) {
2077
- printError3(opts, err);
2271
+ printError4(opts, err);
2272
+ process.exitCode = 1;
2273
+ }
2274
+ }
2275
+ async function playKeyAdd(opts, deps) {
2276
+ const d = deps ?? defaultDeps5(clientFor2(opts));
2277
+ if (!opts.file) {
2278
+ throw new UsageError(
2279
+ "--file is required (the service-account JSON from Google Play Console \u2192 API access)"
2280
+ );
2281
+ }
2282
+ try {
2283
+ const file = await d.readFile(opts.file);
2284
+ const result = await d.addPlayKey({
2285
+ ...opts.name !== void 0 ? { name: opts.name } : {},
2286
+ file,
2287
+ filename: basename2(opts.file)
2288
+ });
2289
+ if (opts.json) {
2290
+ console.log(JSON.stringify(result));
2291
+ } else {
2292
+ console.log(chalk15.green(`\u2713 added Google Play key ${result.name}`));
2293
+ console.log("");
2294
+ console.log(` Service account: ${chalk15.bold(result.client_email)}`);
2295
+ console.log("");
2296
+ console.log(
2297
+ chalk15.yellow(
2298
+ " The JSON key is stored encrypted and will never be shown again \u2014 keep your own copy somewhere safe."
2299
+ )
2300
+ );
2301
+ }
2302
+ process.exitCode = 0;
2303
+ } catch (err) {
2304
+ printError4(opts, err);
2305
+ process.exitCode = 1;
2306
+ }
2307
+ }
2308
+ async function playKeyList(opts, deps) {
2309
+ const d = deps ?? defaultDeps5(clientFor2(opts));
2310
+ try {
2311
+ const { keys } = await d.listPlayKeys();
2312
+ if (opts.json) {
2313
+ console.log(JSON.stringify(keys, null, 2));
2314
+ } else {
2315
+ process.stdout.write(formatPlayKeysHuman(keys));
2316
+ }
2317
+ process.exitCode = 0;
2318
+ } catch (err) {
2319
+ printError4(opts, err);
2320
+ process.exitCode = 1;
2321
+ }
2322
+ }
2323
+ function formatPlayKeysHuman(keys) {
2324
+ if (keys.length === 0) {
2325
+ return "No Google Play keys found.\n";
2326
+ }
2327
+ const HEADERS3 = ["NAME", "CLIENT EMAIL", "CREATED"];
2328
+ const rows = keys.map((k) => [k.name, k.client_email, k.created_at ?? ""]);
2329
+ const widths = HEADERS3.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
2330
+ const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
2331
+ const join3 = (cells) => cells.join(" ").trimEnd();
2332
+ const lines = [chalk15.bold(join3(padCells(HEADERS3)))];
2333
+ for (const row of rows) lines.push(join3(padCells(row)));
2334
+ return lines.join("\n") + "\n";
2335
+ }
2336
+ async function playKeyRm(name, opts, deps) {
2337
+ const d = deps ?? defaultDeps5(clientFor2(opts));
2338
+ try {
2339
+ await d.deletePlayKey(name);
2340
+ if (opts.json) {
2341
+ console.log(JSON.stringify({ event: "deleted", name }));
2342
+ } else {
2343
+ console.log(chalk15.green(`\u2713 removed Google Play key ${name}`));
2344
+ }
2345
+ process.exitCode = 0;
2346
+ } catch (err) {
2347
+ printError4(opts, err);
2078
2348
  process.exitCode = 1;
2079
2349
  }
2080
2350
  }
2081
2351
  async function credentialsList(opts, deps) {
2082
- const d = deps ?? defaultDeps4(clientFor2(opts));
2352
+ const d = deps ?? defaultDeps5(clientFor2(opts));
2083
2353
  try {
2084
2354
  const { credentials: credentials2 } = await d.listCredentials(opts.app);
2085
2355
  for (const c of credentials2) {
@@ -2095,7 +2365,7 @@ async function credentialsList(opts, deps) {
2095
2365
  }
2096
2366
  process.exitCode = 0;
2097
2367
  } catch (err) {
2098
- printError3(opts, err);
2368
+ printError4(opts, err);
2099
2369
  process.exitCode = 1;
2100
2370
  }
2101
2371
  }
@@ -2139,22 +2409,22 @@ function formatCredentialsHuman(credentials2) {
2139
2409
  const widths = HEADERS3.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
2140
2410
  const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
2141
2411
  const join3 = (cells) => cells.join(" ").trimEnd();
2142
- const lines = [chalk14.bold(join3(padCells(HEADERS3)))];
2412
+ const lines = [chalk15.bold(join3(padCells(HEADERS3)))];
2143
2413
  for (const row of rows) lines.push(join3(padCells(row)));
2144
2414
  return lines.join("\n") + "\n";
2145
2415
  }
2146
2416
  async function credentialsRm(name, opts, deps) {
2147
- const d = deps ?? defaultDeps4(clientFor2(opts));
2417
+ const d = deps ?? defaultDeps5(clientFor2(opts));
2148
2418
  try {
2149
2419
  await d.deleteCredential({ name, app: opts.app });
2150
2420
  if (opts.json) {
2151
2421
  console.log(JSON.stringify({ event: "deleted", name }));
2152
2422
  } else {
2153
- console.log(chalk14.green(`\u2713 removed ${name}`));
2423
+ console.log(chalk15.green(`\u2713 removed ${name}`));
2154
2424
  }
2155
2425
  process.exitCode = 0;
2156
2426
  } catch (err) {
2157
- printError3(opts, err);
2427
+ printError4(opts, err);
2158
2428
  process.exitCode = 1;
2159
2429
  }
2160
2430
  }
@@ -2162,16 +2432,16 @@ function printKeystoreWriteFailure(opts, err, result) {
2162
2432
  const message = err instanceof Error ? err.message : String(err);
2163
2433
  if (opts.json) {
2164
2434
  console.log(JSON.stringify({ event: "error", message }));
2165
- console.error(chalk14.red(`error: ${message}`));
2435
+ console.error(chalk15.red(`error: ${message}`));
2166
2436
  return;
2167
2437
  }
2168
- console.error(chalk14.red(`error: ${message}`));
2438
+ console.error(chalk15.red(`error: ${message}`));
2169
2439
  console.error(
2170
- chalk14.red(
2440
+ chalk15.red(
2171
2441
  `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.`
2172
2442
  )
2173
2443
  );
2174
- console.error(chalk14.red("Recover it NOW from the values below \u2014 they will not be shown again:"));
2444
+ console.error(chalk15.red("Recover it NOW from the values below \u2014 they will not be shown again:"));
2175
2445
  console.error("");
2176
2446
  console.error(` Key alias: ${result.key_alias}`);
2177
2447
  console.error(` Store password: ${result.store_password}`);
@@ -2182,19 +2452,19 @@ function printKeystoreWriteFailure(opts, err, result) {
2182
2452
  ` Restore the file with: echo '${result.keystore_base64}' | base64 -d > ${result.name}.keystore`
2183
2453
  );
2184
2454
  }
2185
- function printError3(opts, err) {
2455
+ function printError4(opts, err) {
2186
2456
  const message = err instanceof Error ? err.message : String(err);
2187
2457
  if (opts.json) console.log(JSON.stringify({ event: "error", message }));
2188
- console.error(chalk14.red(`error: ${message}`));
2458
+ console.error(chalk15.red(`error: ${message}`));
2189
2459
  }
2190
2460
 
2191
2461
  // src/commands/webhooks.ts
2192
- import chalk15 from "chalk";
2462
+ import chalk16 from "chalk";
2193
2463
  function clientFor3(opts) {
2194
2464
  const { token, baseUrl } = resolveAuth(opts);
2195
2465
  return new ApiClient(baseUrl, token);
2196
2466
  }
2197
- function defaultDeps5(client) {
2467
+ function defaultDeps6(client) {
2198
2468
  return {
2199
2469
  addWebhook: (body) => client.addWebhook(body),
2200
2470
  listWebhooks: () => client.listWebhooks(),
@@ -2211,7 +2481,7 @@ async function webhooksAdd(opts, deps) {
2211
2481
  if (!opts.url.startsWith("https://")) {
2212
2482
  throw new UsageError(`--url must start with https:// (got "${opts.url}")`);
2213
2483
  }
2214
- const d = deps ?? defaultDeps5(clientFor3(opts));
2484
+ const d = deps ?? defaultDeps6(clientFor3(opts));
2215
2485
  try {
2216
2486
  const result = await d.addWebhook({
2217
2487
  url: opts.url,
@@ -2220,19 +2490,19 @@ async function webhooksAdd(opts, deps) {
2220
2490
  if (opts.json) {
2221
2491
  console.log(JSON.stringify(result));
2222
2492
  } else {
2223
- console.log(chalk15.green(`\u2713 added ${result.id} ${result.url}`));
2493
+ console.log(chalk16.green(`\u2713 added ${result.id} ${result.url}`));
2224
2494
  console.log("");
2225
- console.log(` Signing secret: ${chalk15.bold(result.secret)}`);
2495
+ console.log(` Signing secret: ${chalk16.bold(result.secret)}`);
2226
2496
  if (opts.secret !== void 0) {
2227
- console.log(chalk15.yellow(" (This is the secret you provided.)"));
2497
+ console.log(chalk16.yellow(" (This is the secret you provided.)"));
2228
2498
  }
2229
2499
  console.log(
2230
- chalk15.yellow(" This secret is shown ONCE and cannot be retrieved again \u2014 store it now.")
2500
+ chalk16.yellow(" This secret is shown ONCE and cannot be retrieved again \u2014 store it now.")
2231
2501
  );
2232
2502
  }
2233
2503
  process.exitCode = 0;
2234
2504
  } catch (err) {
2235
- printError4(opts, err);
2505
+ printError5(opts, err);
2236
2506
  process.exitCode = 1;
2237
2507
  }
2238
2508
  }
@@ -2258,12 +2528,12 @@ function formatWebhooksHuman(webhooks2) {
2258
2528
  const widths = HEADERS3.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
2259
2529
  const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
2260
2530
  const join3 = (cells) => cells.join(" ").trimEnd();
2261
- const lines = [chalk15.bold(join3(padCells(HEADERS3)))];
2531
+ const lines = [chalk16.bold(join3(padCells(HEADERS3)))];
2262
2532
  for (const row of rows) lines.push(join3(padCells(row)));
2263
2533
  return lines.join("\n") + "\n";
2264
2534
  }
2265
2535
  async function webhooksList(opts, deps) {
2266
- const d = deps ?? defaultDeps5(clientFor3(opts));
2536
+ const d = deps ?? defaultDeps6(clientFor3(opts));
2267
2537
  try {
2268
2538
  const { webhooks: webhooks2 } = await d.listWebhooks();
2269
2539
  if (opts.json) {
@@ -2273,22 +2543,22 @@ async function webhooksList(opts, deps) {
2273
2543
  }
2274
2544
  process.exitCode = 0;
2275
2545
  } catch (err) {
2276
- printError4(opts, err);
2546
+ printError5(opts, err);
2277
2547
  process.exitCode = 1;
2278
2548
  }
2279
2549
  }
2280
2550
  async function webhooksRm(id, opts, deps) {
2281
- const d = deps ?? defaultDeps5(clientFor3(opts));
2551
+ const d = deps ?? defaultDeps6(clientFor3(opts));
2282
2552
  try {
2283
2553
  await d.deleteWebhook(id);
2284
2554
  if (opts.json) {
2285
2555
  console.log(JSON.stringify({ event: "deleted", id }));
2286
2556
  } else {
2287
- console.log(chalk15.green(`\u2713 removed ${id}`));
2557
+ console.log(chalk16.green(`\u2713 removed ${id}`));
2288
2558
  }
2289
2559
  process.exitCode = 0;
2290
2560
  } catch (err) {
2291
- printError4(opts, err);
2561
+ printError5(opts, err);
2292
2562
  process.exitCode = 1;
2293
2563
  }
2294
2564
  }
@@ -2305,7 +2575,7 @@ function parseUtcTimestamp(iso) {
2305
2575
  var TEST_POLL_INTERVAL_MS = 3e3;
2306
2576
  var TEST_POLL_BUDGET_MS = 6e4;
2307
2577
  async function webhooksTest(id, opts, deps) {
2308
- const d = deps ?? defaultDeps5(clientFor3(opts));
2578
+ const d = deps ?? defaultDeps6(clientFor3(opts));
2309
2579
  try {
2310
2580
  const startedAt = d.now();
2311
2581
  await d.testWebhook(id);
@@ -2317,13 +2587,13 @@ async function webhooksTest(id, opts, deps) {
2317
2587
  if (opts.json) console.log(JSON.stringify(ld));
2318
2588
  if (ld.status === "succeeded") {
2319
2589
  if (!opts.json) {
2320
- console.log(chalk15.green(`\u2713 Delivered (HTTP ${ld.response_code})`));
2590
+ console.log(chalk16.green(`\u2713 Delivered (HTTP ${ld.response_code})`));
2321
2591
  }
2322
2592
  process.exitCode = 0;
2323
2593
  } else {
2324
2594
  if (!opts.json) {
2325
2595
  const code = ld.response_code != null ? `HTTP ${ld.response_code}` : "no response";
2326
- console.error(chalk15.red(`\u2717 Failed (${code}) at ${ld.at}`));
2596
+ console.error(chalk16.red(`\u2717 Failed (${code}) at ${ld.at}`));
2327
2597
  }
2328
2598
  process.exitCode = 1;
2329
2599
  }
@@ -2332,24 +2602,24 @@ async function webhooksTest(id, opts, deps) {
2332
2602
  }
2333
2603
  if (opts.json) console.log(JSON.stringify({ status: "pending" }));
2334
2604
  console.error(
2335
- chalk15.yellow(
2605
+ chalk16.yellow(
2336
2606
  `Delivery still pending after ${TEST_POLL_BUDGET_MS / 1e3}s \u2014 check \`vibeview webhooks list\` later for the result.`
2337
2607
  )
2338
2608
  );
2339
2609
  process.exitCode = 1;
2340
2610
  } catch (err) {
2341
- printError4(opts, err);
2611
+ printError5(opts, err);
2342
2612
  process.exitCode = 1;
2343
2613
  }
2344
2614
  }
2345
- function printError4(opts, err) {
2615
+ function printError5(opts, err) {
2346
2616
  const message = err instanceof Error ? err.message : String(err);
2347
2617
  if (opts.json) console.log(JSON.stringify({ event: "error", message }));
2348
- console.error(chalk15.red(`error: ${message}`));
2618
+ console.error(chalk16.red(`error: ${message}`));
2349
2619
  }
2350
2620
 
2351
2621
  // src/lib/formatters/devices.ts
2352
- import chalk16 from "chalk";
2622
+ import chalk17 from "chalk";
2353
2623
  var HEADERS2 = ["DEVICE_ID", "NAME", "TYPE", "CATEGORY", "OS", "STATUS"];
2354
2624
  function formatDevicesHuman(devices) {
2355
2625
  if (devices.length === 0) {
@@ -2365,9 +2635,9 @@ function formatDevicesHuman(devices) {
2365
2635
  ]);
2366
2636
  const widths = HEADERS2.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
2367
2637
  const fmt = (cells) => cells.map((c, i) => c.padEnd(widths[i])).join(" ").trimEnd();
2368
- const lines = [chalk16.bold(fmt(HEADERS2))];
2638
+ const lines = [chalk17.bold(fmt(HEADERS2))];
2369
2639
  for (const row of rows.sort(rowSort)) {
2370
- const statusColored = row[5] === "available" ? chalk16.green(row[5]) : row[5] === "busy" ? chalk16.yellow(row[5]) : chalk16.dim(row[5]);
2640
+ const statusColored = row[5] === "available" ? chalk17.green(row[5]) : row[5] === "busy" ? chalk17.yellow(row[5]) : chalk17.dim(row[5]);
2371
2641
  lines.push(fmt([row[0], row[1], row[2], row[3], row[4], statusColored]));
2372
2642
  }
2373
2643
  return lines.join("\n") + "\n";
@@ -2457,7 +2727,7 @@ import { dirname, join as join2 } from "path";
2457
2727
  import { fileURLToPath } from "url";
2458
2728
  import { spawnSync } from "child_process";
2459
2729
  import { confirm } from "@inquirer/prompts";
2460
- import chalk17 from "chalk";
2730
+ import chalk18 from "chalk";
2461
2731
  function resolveShippedSkillPath(fromDir) {
2462
2732
  for (const rel of ["..", join2("..", "..")]) {
2463
2733
  const candidate = join2(fromDir, rel, "skills", "vibeview-agent", "SKILL.md");
@@ -2485,23 +2755,23 @@ function registerMcp() {
2485
2755
  encoding: "utf8"
2486
2756
  });
2487
2757
  if (res.status === 0) {
2488
- console.log(chalk17.green("\u2713 MCP server registered with Claude Code (claude mcp add vibeview)"));
2758
+ console.log(chalk18.green("\u2713 MCP server registered with Claude Code (claude mcp add vibeview)"));
2489
2759
  return;
2490
2760
  }
2491
2761
  const out = `${res.stderr ?? ""}${res.stdout ?? ""}`;
2492
2762
  if (out.includes("already exists")) {
2493
- console.log(chalk17.dim("MCP server already registered with Claude Code \u2014 nothing to do"));
2763
+ console.log(chalk18.dim("MCP server already registered with Claude Code \u2014 nothing to do"));
2494
2764
  return;
2495
2765
  }
2496
- console.error(chalk17.yellow(`\u26A0 could not register the MCP server: ${out.trim() || res.error}`));
2497
- console.error(chalk17.yellow(" register it manually: claude mcp add vibeview -- vibeview mcp"));
2766
+ console.error(chalk18.yellow(`\u26A0 could not register the MCP server: ${out.trim() || res.error}`));
2767
+ console.error(chalk18.yellow(" register it manually: claude mcp add vibeview -- vibeview mcp"));
2498
2768
  }
2499
2769
  async function agentSetup(opts) {
2500
2770
  const moduleDir = dirname(fileURLToPath(import.meta.url));
2501
2771
  const shipped = resolveShippedSkillPath(moduleDir);
2502
2772
  if (!shipped) {
2503
2773
  console.error(
2504
- chalk17.red(
2774
+ chalk18.red(
2505
2775
  "error: could not find the packaged skill (skills/vibeview-agent/SKILL.md) \u2014 is this a complete install of the vibeview package?"
2506
2776
  )
2507
2777
  );
@@ -2510,9 +2780,9 @@ async function agentSetup(opts) {
2510
2780
  }
2511
2781
  const targetDir = skillInstallDir(opts.project ?? false, process.cwd(), homedir());
2512
2782
  const result = installSkill(shipped, targetDir);
2513
- console.log(chalk17.green(`\u2713 vibeview-agent skill ${result}: ${join2(targetDir, "SKILL.md")}`));
2783
+ console.log(chalk18.green(`\u2713 vibeview-agent skill ${result}: ${join2(targetDir, "SKILL.md")}`));
2514
2784
  console.log(
2515
- chalk17.dim(
2785
+ chalk18.dim(
2516
2786
  opts.project ? " Claude Code discovers it automatically for anyone working in this project." : " Claude Code discovers it automatically in every project on this machine."
2517
2787
  )
2518
2788
  );
@@ -2520,7 +2790,7 @@ async function agentSetup(opts) {
2520
2790
  if (!claudeCliAvailable()) {
2521
2791
  if (opts.mcp === true) {
2522
2792
  console.error(
2523
- chalk17.yellow(
2793
+ chalk18.yellow(
2524
2794
  "\u26A0 claude CLI not found \u2014 install Claude Code, then run: claude mcp add vibeview -- vibeview mcp"
2525
2795
  )
2526
2796
  );
@@ -2534,7 +2804,7 @@ async function agentSetup(opts) {
2534
2804
  }
2535
2805
  console.log(
2536
2806
  `
2537
- Suggested line for your project's ${chalk17.bold("CLAUDE.md")} (or ${chalk17.bold(
2807
+ Suggested line for your project's ${chalk18.bold("CLAUDE.md")} (or ${chalk18.bold(
2538
2808
  "AGENTS.md"
2539
2809
  )} for other agents):
2540
2810
  `
@@ -2542,7 +2812,7 @@ Suggested line for your project's ${chalk17.bold("CLAUDE.md")} (or ${chalk17.bol
2542
2812
  console.log(` ${CLAUDE_MD_SUGGESTION}
2543
2813
  `);
2544
2814
  console.log(
2545
- chalk17.dim(
2815
+ chalk18.dim(
2546
2816
  "Other agents (Cursor, Copilot, Codex, ...): point them at the skill file above, or\nadd the MCP server per client \u2014 see https://vibeview.io/docs/agent-control"
2547
2817
  )
2548
2818
  );
@@ -2706,6 +2976,22 @@ program.command("build").description("Run your configured build command and uplo
2706
2976
  "--cloud only: build a signed, installable production artifact (.aab/.ipa) using uploaded signing credentials (see `vibeview credentials add`), instead of the default unsigned simulator build",
2707
2977
  false
2708
2978
  ).option("--json", "Print the result as one JSON line (NDJSON events with --cloud)", false).action((opts) => buildCommand(opts));
2979
+ program.command("submit").description(
2980
+ "Submit a succeeded production cloud build's artifact to TestFlight (ios/tvos) or a Google Play track (android/androidtv) \u2014 binary upload only; store listing and review stay in the store consoles"
2981
+ ).option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--platform <platform>", "ios, tvos, android, or androidtv").option("--build <id>", "Cloud build id to submit (see `vibeview builds list`)").option(
2982
+ "--latest",
2983
+ "Submit the newest succeeded production build for --platform (pass exactly one of --build/--latest)",
2984
+ false
2985
+ ).option(
2986
+ "--track <track>",
2987
+ "android/androidtv only: Play track \u2014 internal (default), alpha, beta, or production"
2988
+ ).option(
2989
+ "--package <name>",
2990
+ "android/androidtv only: package name, for a build not linked to an app"
2991
+ ).option(
2992
+ "--asc-key <name>",
2993
+ "ios/tvos only: which stored App Store Connect key to use (default when exactly one \u2014 see `vibeview credentials asc-key add`)"
2994
+ ).option("--no-wait", "Print the submission id and exit without waiting for completion").option("--json", "Emit line-delimited JSON events on stdout instead of human output", false).action((opts) => submitCommand(opts));
2709
2995
  var builds = program.command("builds").description("Manage cloud builds (see `vibeview build --cloud`)");
2710
2996
  builds.command("list").description("List cloud builds in your organization").option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--json", "Print the raw build summaries as a JSON array", false).option("--limit <n>", "Max builds to return (default 50, max 200)").action((opts) => buildsList(opts));
2711
2997
  builds.command("logs <id>").description("Print a cloud build's log output").option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--json", "Print NDJSON log/outcome events instead of raw text", false).option("--follow", "Keep polling until the build reaches a terminal status", false).action((id, opts) => buildsLogs(id, opts));
@@ -2765,6 +3051,17 @@ ascKey.command("add").description(
2765
3051
  ).option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--key-id <id>", "The 10-character key id (shown next to the key in App Store Connect)").option("--issuer-id <id>", "The issuer id (UUID shown above the keys table)").option("--file <path>", "The AuthKey_<KEYID>.p8 file Apple let you download once").option("--name <name>", "Key name (default: server-chosen)").option("--json", "Print the raw response as one JSON line", false).action((opts) => ascKeyAdd(opts));
2766
3052
  ascKey.command("list").description("List stored App Store Connect keys (metadata only \u2014 never the .p8)").option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--json", "Print the raw key list as a JSON array", false).action((opts) => ascKeyList(opts));
2767
3053
  ascKey.command("rm <name>").description("Remove a stored App Store Connect key").option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--json", "Print the result as one JSON line", false).action((name, opts) => ascKeyRm(name, opts));
3054
+ var playKey = credentials.command("play-key").description(
3055
+ "Manage Google Play service-account keys (create one in Google Play Console \u2192 API access, or Google Cloud \u2192 IAM \u2192 service accounts) \u2014 they power `vibeview submit --platform android|androidtv`"
3056
+ );
3057
+ playKey.command("add").description(
3058
+ "Store a Google Play service-account JSON key. The key is stored encrypted and never shown again; uploading the same name again rotates the key."
3059
+ ).option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option(
3060
+ "--file <path>",
3061
+ "The service-account JSON key file (Google Play Console \u2192 API access / Google Cloud service accounts)"
3062
+ ).option("--name <name>", "Key name (default: server-chosen)").option("--json", "Print the raw response as one JSON line", false).action((opts) => playKeyAdd(opts));
3063
+ playKey.command("list").description("List stored Google Play keys (metadata only \u2014 never the JSON key)").option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--json", "Print the raw key list as a JSON array", false).action((opts) => playKeyList(opts));
3064
+ playKey.command("rm <name>").description("Remove a stored Google Play key").option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--json", "Print the result as one JSON line", false).action((name, opts) => playKeyRm(name, opts));
2768
3065
  credentials.command("list").description("List signing credentials (never prints key material)").option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--app <id>", "Scope to a single app (default: org-wide credentials only)").option("--json", "Print the raw credential list as a JSON array", false).action((opts) => credentialsList(opts));
2769
3066
  credentials.command("rm <name>").description("Remove a signing credential (replace by rm then add)").option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--app <id>", "Scope to a single app (default: org-wide)").option("--json", "Print the result as one JSON line", false).action((name, opts) => credentialsRm(name, opts));
2770
3067
  var webhooks = program.command("webhooks").description("Manage build-completion webhook endpoints");
@@ -2779,7 +3076,7 @@ program.command("dev-stop").description("Stop a `vibeview dev --detach` session
2779
3076
  program.command("mcp").description(
2780
3077
  "Run an MCP stdio server exposing the agent verbs (tap, swipe, type, ...) plus dev_start/dev_stop as tools"
2781
3078
  ).option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").action(async (opts) => {
2782
- const { runMcpServer } = await import("./server-TJQKNXDR.js");
3079
+ const { runMcpServer } = await import("./server-GMC6F545.js");
2783
3080
  await runMcpServer(opts);
2784
3081
  });
2785
3082
  program.command("agent-setup").description(