vibeview 0.13.0 → 0.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -38,7 +38,7 @@ function resolveAuth(options) {
38
38
 
39
39
  // src/lib/api-client.ts
40
40
  import { openAsBlob, readFileSync as readFileSync2 } from "fs";
41
- import { createWriteStream, renameSync, rmSync } from "fs";
41
+ import { createWriteStream, mkdirSync as mkdirSync2, renameSync, rmSync } from "fs";
42
42
  import { basename, join as join2, resolve, sep } from "path";
43
43
  import { pipeline } from "stream/promises";
44
44
  import { Readable } from "stream";
@@ -297,6 +297,13 @@ var ApiClient = class {
297
297
  async cancelCloudBuild(id) {
298
298
  return this.requestRaw("POST", `/builds/${id}/cancel`);
299
299
  }
300
+ /** DELETE /api/v1/builds/{id}/artifact — deletes a production build's
301
+ * stored artifact file. The build record, its logs, and its cost stay
302
+ * intact; this only removes the downloadable file. 409 if the build has
303
+ * no artifact (never had one, or already deleted). */
304
+ async deleteCloudBuildArtifact(id) {
305
+ return this.requestRaw("DELETE", `/builds/${id}/artifact`);
306
+ }
300
307
  /**
301
308
  * Download a finished build's artifact (GET /api/v1/builds/{id}/artifact),
302
309
  * streaming the response body straight to a file in destDir. The filename
@@ -325,6 +332,7 @@ var ApiClient = class {
325
332
  if (resolvedPath !== join2(resolvedDestDir, filename) || !resolvedPath.startsWith(resolvedDestDir + sep)) {
326
333
  throw new Error(`Refusing to write artifact outside destination directory: ${filename}`);
327
334
  }
335
+ mkdirSync2(resolvedDestDir, { recursive: true });
328
336
  if (!resp.body) {
329
337
  createWriteStream(path).end();
330
338
  return { filename, path };
@@ -363,6 +371,10 @@ var ApiClient = class {
363
371
  formData.append("file", blob, body.filename);
364
372
  return this.requestFormData("POST", "/build-env/", formData);
365
373
  }
374
+ /** GET /api/v1/users/me — identity + roles for `vibeview whoami`. */
375
+ async whoAmI() {
376
+ return this.requestRaw("GET", "/users/me");
377
+ }
366
378
  /** DELETE /api/v1/build-env/{id}. Requires ADMIN role. */
367
379
  async deleteBuildEnvVar(id) {
368
380
  await this.requestRaw("DELETE", `/build-env/${id}`);
@@ -374,6 +386,44 @@ var ApiClient = class {
374
386
  const qs = app ? `?app=${encodeURIComponent(app)}` : "";
375
387
  return this.requestRaw("GET", `/build-env/pull${qs}`);
376
388
  }
389
+ /** GET /api/v1/signing-credentials/[?app=]. Requires VIEWER role. Never
390
+ * returns file bytes, profile bytes, or a password — see
391
+ * routes/signing.py's module docstring. */
392
+ async listSigningCredentials(app) {
393
+ const qs = app ? `?app=${encodeURIComponent(app)}` : "";
394
+ return this.requestRaw("GET", `/signing-credentials/${qs}`);
395
+ }
396
+ /**
397
+ * POST /api/v1/signing-credentials/ (multipart). Requires ADMIN role.
398
+ * `passwords` carries the route's own snake_case form field names
399
+ * (store_password, key_password, key_alias, p12_password) — the CLI's own
400
+ * camelCase objects are converted to this shape ONLY at this boundary
401
+ * (see credentials.ts), never anywhere else, so there is exactly one place
402
+ * the two naming conventions meet.
403
+ */
404
+ async addSigningCredential(body) {
405
+ const formData = new FormData();
406
+ formData.append("platform", body.platform);
407
+ formData.append("kind", body.kind);
408
+ formData.append("name", body.name);
409
+ if (body.app) formData.append("app", body.app);
410
+ for (const [key, value] of Object.entries(body.passwords)) {
411
+ formData.append(key, value);
412
+ }
413
+ formData.append("file", new Blob([new Uint8Array(body.file)]), body.filename);
414
+ if (body.profile) {
415
+ formData.append(
416
+ "profile",
417
+ new Blob([new Uint8Array(body.profile)]),
418
+ body.profileFilename ?? "profile"
419
+ );
420
+ }
421
+ return this.requestFormData("POST", "/signing-credentials/", formData);
422
+ }
423
+ /** DELETE /api/v1/signing-credentials/{id}. Requires ADMIN role. */
424
+ async deleteSigningCredential(id) {
425
+ await this.requestRaw("DELETE", `/signing-credentials/${id}`);
426
+ }
377
427
  };
378
428
  function parseContentDispositionFilename(header) {
379
429
  if (!header) return void 0;
@@ -410,6 +460,12 @@ function createSpinner(text) {
410
460
  var EXIT_PASS = 0;
411
461
  var EXIT_TEST_FAIL = 1;
412
462
  var EXIT_INFRA_ERROR = 2;
463
+ var UsageError = class extends Error {
464
+ constructor(message) {
465
+ super(message);
466
+ this.name = "UsageError";
467
+ }
468
+ };
413
469
 
414
470
  // src/commands/upload-app.ts
415
471
  async function uploadAppBuild(filePath, options) {
@@ -480,6 +536,7 @@ export {
480
536
  EXIT_PASS,
481
537
  EXIT_TEST_FAIL,
482
538
  EXIT_INFRA_ERROR,
539
+ UsageError,
483
540
  uploadAppBuild,
484
541
  uploadApp
485
542
  };
package/dist/cli.js CHANGED
@@ -13,19 +13,20 @@ import {
13
13
  readDevState,
14
14
  readMetroState,
15
15
  runAgentVerb
16
- } from "./chunk-YFXMXO5T.js";
16
+ } from "./chunk-2VNBRBST.js";
17
17
  import {
18
18
  ApiClient,
19
19
  EXIT_INFRA_ERROR,
20
20
  EXIT_PASS,
21
21
  EXIT_TEST_FAIL,
22
22
  NotFoundError,
23
+ UsageError,
23
24
  createSpinner,
24
25
  loadConfig,
25
26
  resolveAuth,
26
27
  saveConfig,
27
28
  uploadApp
28
- } from "./chunk-J7V3GYOI.js";
29
+ } from "./chunk-2WTLJ66O.js";
29
30
 
30
31
  // src/cli.ts
31
32
  import { createRequire } from "module";
@@ -909,9 +910,9 @@ async function resolveSteps(options) {
909
910
  return JSON.parse(readFileSync(options.stepsFile, "utf-8"));
910
911
  }
911
912
  if (!process.stdin.isTTY) {
912
- const input = await readStdin();
913
- if (input.trim()) {
914
- return JSON.parse(input.trim());
913
+ const input2 = await readStdin();
914
+ if (input2.trim()) {
915
+ return JSON.parse(input2.trim());
915
916
  }
916
917
  }
917
918
  throw new Error("No steps provided. Use --steps, --steps-file, or pipe JSON to stdin.");
@@ -1190,7 +1191,8 @@ function defaultDeps(client) {
1190
1191
  getCloudBuild: client.getCloudBuild.bind(client),
1191
1192
  getCloudBuildLogs: client.getCloudBuildLogs.bind(client),
1192
1193
  cancelCloudBuild: client.cancelCloudBuild.bind(client),
1193
- downloadCloudBuildArtifact: client.downloadCloudBuildArtifact.bind(client)
1194
+ downloadCloudBuildArtifact: client.downloadCloudBuildArtifact.bind(client),
1195
+ deleteCloudBuildArtifact: client.deleteCloudBuildArtifact.bind(client)
1194
1196
  };
1195
1197
  }
1196
1198
  var POLL_INTERVAL_MS = 2e3;
@@ -1347,16 +1349,91 @@ async function buildsCancel(id, opts, deps) {
1347
1349
  process.exitCode = 1;
1348
1350
  }
1349
1351
  }
1352
+ async function buildsDeleteArtifact(id, opts, deps) {
1353
+ const { token, baseUrl } = resolveAuth(opts);
1354
+ const client = new ApiClient(baseUrl, token);
1355
+ const d = deps ?? defaultDeps(client);
1356
+ try {
1357
+ const result = await d.deleteCloudBuildArtifact(id);
1358
+ if (opts.json) {
1359
+ console.log(JSON.stringify(result));
1360
+ } else {
1361
+ console.log(chalk11.green(`\u2713 deleted artifact for ${id}`));
1362
+ }
1363
+ process.exitCode = 0;
1364
+ } catch (err) {
1365
+ printError(opts, err);
1366
+ process.exitCode = 1;
1367
+ }
1368
+ }
1350
1369
  function printError(opts, err) {
1351
1370
  const message = err instanceof Error ? err.message : String(err);
1352
1371
  if (opts.json) console.log(JSON.stringify({ event: "error", message }));
1353
1372
  console.error(chalk11.red(`error: ${message}`));
1354
1373
  }
1355
1374
 
1375
+ // src/commands/whoami.ts
1376
+ import chalk12 from "chalk";
1377
+ function defaultDeps2(opts) {
1378
+ const { token, baseUrl } = resolveAuth(opts);
1379
+ const client = new ApiClient(baseUrl, token);
1380
+ return { fetchMe: () => client.whoAmI() };
1381
+ }
1382
+ function effectiveRole(role, tokenRole) {
1383
+ const RANK = { admin: 3, developer: 2, viewer: 1 };
1384
+ if (!role) return tokenRole;
1385
+ if (!tokenRole) return role;
1386
+ return (RANK[tokenRole] ?? 3) < (RANK[role] ?? 3) ? tokenRole : role;
1387
+ }
1388
+ async function whoAmI(opts, deps) {
1389
+ const d = deps ?? defaultDeps2(opts);
1390
+ try {
1391
+ const me = await d.fetchMe();
1392
+ const effective = effectiveRole(me.role, me.token_role);
1393
+ if (opts.json) {
1394
+ console.log(JSON.stringify({ ...me, effective_role: effective }));
1395
+ process.exitCode = 0;
1396
+ return;
1397
+ }
1398
+ console.log(`${chalk12.bold("Logged in as")} ${me.email}`);
1399
+ if (me.memberships.length === 1) {
1400
+ console.log(`${chalk12.bold("Organization")} ${me.memberships[0].organization_name}`);
1401
+ } else if (me.memberships.length > 1) {
1402
+ console.log(chalk12.bold("Organizations"));
1403
+ for (const m of me.memberships) {
1404
+ console.log(` ${m.organization_name} (${m.role})`);
1405
+ }
1406
+ }
1407
+ console.log(`${chalk12.bold("Member role")} ${me.role ?? "\u2014"}`);
1408
+ console.log(`${chalk12.bold("Token role")} ${me.token_role ?? "\u2014 (session auth)"}`);
1409
+ console.log(`${chalk12.bold("Effective role")} ${effective ?? "\u2014"}`);
1410
+ if (me.is_superadmin) {
1411
+ console.log(chalk12.dim("Superadmin (platform-wide; does not raise the org role above)"));
1412
+ }
1413
+ if (me.role === "admin" && me.token_role && me.token_role !== "admin") {
1414
+ console.log(
1415
+ chalk12.yellow(
1416
+ `
1417
+ 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.
1418
+ Create an admin API token in the dashboard and use ${chalk12.bold("--token")} or ${chalk12.bold("VIBEVIEW_API_TOKEN")}.`
1419
+ )
1420
+ );
1421
+ }
1422
+ process.exitCode = 0;
1423
+ } catch (err) {
1424
+ const message = err instanceof Error ? err.message : String(err);
1425
+ if (opts.json) {
1426
+ console.log(JSON.stringify({ event: "error", message }));
1427
+ }
1428
+ console.error(chalk12.red(`error: ${message}`));
1429
+ process.exitCode = 1;
1430
+ }
1431
+ }
1432
+
1356
1433
  // src/commands/env.ts
1357
1434
  import { chmod, open as open2, readFile as readFileAsync } from "fs/promises";
1358
1435
  import { basename } from "path";
1359
- import chalk12 from "chalk";
1436
+ import chalk13 from "chalk";
1360
1437
  import { password } from "@inquirer/prompts";
1361
1438
 
1362
1439
  // src/lib/gitignore.ts
@@ -1383,7 +1460,7 @@ async function writeSecureFile(path, content) {
1383
1460
  }
1384
1461
  await chmod(path, 384);
1385
1462
  }
1386
- function defaultDeps2(client) {
1463
+ function defaultDeps3(client) {
1387
1464
  return {
1388
1465
  listVars: (app) => client.listBuildEnvVars(app),
1389
1466
  setVar: (body) => client.setBuildEnvVar(body),
@@ -1412,11 +1489,11 @@ function defaultDeps2(client) {
1412
1489
  isIgnored: (path) => isGitIgnored(path),
1413
1490
  writeFile: writeSecureFile,
1414
1491
  readFile: (path) => readFileAsync(path),
1415
- warn: (message) => console.error(chalk12.yellow(`warning: ${message}`))
1492
+ warn: (message) => console.error(chalk13.yellow(`warning: ${message}`))
1416
1493
  };
1417
1494
  }
1418
1495
  async function envList(opts, deps) {
1419
- const d = deps ?? defaultDeps2(clientFor(opts));
1496
+ const d = deps ?? defaultDeps3(clientFor(opts));
1420
1497
  try {
1421
1498
  const { variables } = await d.listVars(opts.app);
1422
1499
  if (opts.json) {
@@ -1444,12 +1521,12 @@ function formatEnvVarsHuman(variables) {
1444
1521
  const widths = HEADERS3.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
1445
1522
  const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
1446
1523
  const join2 = (cells) => cells.join(" ").trimEnd();
1447
- const lines = [chalk12.bold(join2(padCells(HEADERS3)))];
1524
+ const lines = [chalk13.bold(join2(padCells(HEADERS3)))];
1448
1525
  for (const row of rows) lines.push(join2(padCells(row)));
1449
1526
  return lines.join("\n") + "\n";
1450
1527
  }
1451
1528
  async function envSet(name, value, opts, deps) {
1452
- const d = deps ?? defaultDeps2(clientFor(opts));
1529
+ const d = deps ?? defaultDeps3(clientFor(opts));
1453
1530
  let resolvedValue = value;
1454
1531
  if (resolvedValue === void 0 && !opts.file) {
1455
1532
  if (opts.json || !d.isTTY) {
@@ -1494,17 +1571,17 @@ function printSetResult(opts, result) {
1494
1571
  if (opts.json) {
1495
1572
  console.log(JSON.stringify(result));
1496
1573
  } else {
1497
- console.log(chalk12.green(`\u2713 set ${result.name} (${result.visibility})`));
1574
+ console.log(chalk13.green(`\u2713 set ${result.name} (${result.visibility})`));
1498
1575
  }
1499
1576
  }
1500
1577
  async function envRm(name, opts, deps) {
1501
- const d = deps ?? defaultDeps2(clientFor(opts));
1578
+ const d = deps ?? defaultDeps3(clientFor(opts));
1502
1579
  try {
1503
1580
  await d.rmVar({ name, app: opts.app });
1504
1581
  if (opts.json) {
1505
1582
  console.log(JSON.stringify({ event: "deleted", name }));
1506
1583
  } else {
1507
- console.log(chalk12.green(`\u2713 removed ${name}`));
1584
+ console.log(chalk13.green(`\u2713 removed ${name}`));
1508
1585
  }
1509
1586
  process.exitCode = 0;
1510
1587
  } catch (err) {
@@ -1513,7 +1590,7 @@ async function envRm(name, opts, deps) {
1513
1590
  }
1514
1591
  }
1515
1592
  async function envPull(opts, deps) {
1516
- const d = deps ?? defaultDeps2(clientFor(opts));
1593
+ const d = deps ?? defaultDeps3(clientFor(opts));
1517
1594
  const outPath = opts.out ?? ".env";
1518
1595
  try {
1519
1596
  const { variables } = await d.pullVars(opts.app);
@@ -1536,7 +1613,7 @@ async function envPull(opts, deps) {
1536
1613
  if (opts.json) {
1537
1614
  console.log(JSON.stringify({ event: "pulled", path: outPath, count: variables.length }));
1538
1615
  } else {
1539
- console.log(chalk12.green(`\u2713 wrote ${variables.length} variable(s) to ${outPath}`));
1616
+ console.log(chalk13.green(`\u2713 wrote ${variables.length} variable(s) to ${outPath}`));
1540
1617
  }
1541
1618
  process.exitCode = 0;
1542
1619
  } catch (err) {
@@ -1575,11 +1652,195 @@ function escapeEnvValue(value) {
1575
1652
  function printError2(opts, err) {
1576
1653
  const message = err instanceof Error ? err.message : String(err);
1577
1654
  if (opts.json) console.log(JSON.stringify({ event: "error", message }));
1578
- console.error(chalk12.red(`error: ${message}`));
1655
+ console.error(chalk13.red(`error: ${message}`));
1656
+ }
1657
+
1658
+ // src/commands/credentials.ts
1659
+ import { readFile as readFileAsync2 } from "fs/promises";
1660
+ import { basename as basename2, extname } from "path";
1661
+ import chalk14 from "chalk";
1662
+ import { input, password as password2 } from "@inquirer/prompts";
1663
+ function clientFor2(opts) {
1664
+ const { token, baseUrl } = resolveAuth(opts);
1665
+ return new ApiClient(baseUrl, token);
1666
+ }
1667
+ function defaultDeps4(client) {
1668
+ return {
1669
+ addCredential: (body) => client.addSigningCredential({
1670
+ platform: body.platform,
1671
+ kind: body.kind,
1672
+ name: body.name,
1673
+ app: body.app,
1674
+ // Convert to the route's snake_case form fields HERE, at the client
1675
+ // boundary, and nowhere else — see api-client.ts's
1676
+ // addSigningCredential doc comment.
1677
+ passwords: {
1678
+ ...body.storePassword !== void 0 ? { store_password: body.storePassword } : {},
1679
+ ...body.keyPassword !== void 0 ? { key_password: body.keyPassword } : {},
1680
+ ...body.keyAlias !== void 0 ? { key_alias: body.keyAlias } : {},
1681
+ ...body.p12Password !== void 0 ? { p12_password: body.p12Password } : {}
1682
+ },
1683
+ file: body.file,
1684
+ filename: body.filename,
1685
+ profile: body.profile,
1686
+ profileFilename: body.profileFilename
1687
+ }),
1688
+ listCredentials: (app) => client.listSigningCredentials(app),
1689
+ deleteCredential: async ({ name, app }) => {
1690
+ const { credentials: credentials2 } = await client.listSigningCredentials(app);
1691
+ const wantApp = app ?? null;
1692
+ const match = credentials2.find(
1693
+ (c) => c.name === name && (c.app_public_id ?? null) === wantApp
1694
+ );
1695
+ if (!match) {
1696
+ throw new Error(`No credential named "${name}" found${app ? ` for app "${app}"` : ""}.`);
1697
+ }
1698
+ await client.deleteSigningCredential(match.id);
1699
+ },
1700
+ prompt: ({ message, hidden }) => hidden ? password2({ message }) : input({ message }),
1701
+ isTTY: Boolean(process.stdin.isTTY),
1702
+ readFile: (path) => readFileAsync2(path),
1703
+ warn: (message) => console.error(chalk14.yellow(`warning: ${message}`))
1704
+ };
1705
+ }
1706
+ var ANDROID_KIND = "android_keystore";
1707
+ var IOS_KIND = "ios_distribution";
1708
+ async function credentialsAdd(opts, deps) {
1709
+ const d = deps ?? defaultDeps4(clientFor2(opts));
1710
+ if (!opts.platform || opts.platform !== "ios" && opts.platform !== "android") {
1711
+ throw new UsageError('--platform is required and must be "ios" or "android"');
1712
+ }
1713
+ if (!opts.file) {
1714
+ throw new UsageError("--file is required");
1715
+ }
1716
+ if (opts.platform === "ios" && !opts.profile) {
1717
+ throw new UsageError("--profile is required for ios credentials (a .mobileprovision file)");
1718
+ }
1719
+ const canPrompt = !opts.json && d.isTTY;
1720
+ if (!canPrompt) {
1721
+ throw new UsageError(
1722
+ "a password is required: run interactively to be prompted (passwords are never accepted as flags)."
1723
+ );
1724
+ }
1725
+ const name = opts.name ?? basename2(opts.file, extname(opts.file));
1726
+ try {
1727
+ const file = await d.readFile(opts.file);
1728
+ const filename = basename2(opts.file);
1729
+ let body;
1730
+ if (opts.platform === "android") {
1731
+ const storePassword = await d.prompt({ message: "Keystore (store) password", hidden: true });
1732
+ const keyPassword = await d.prompt({ message: "Key password", hidden: true });
1733
+ const keyAlias = await d.prompt({ message: "Key alias", hidden: false });
1734
+ body = {
1735
+ platform: "android",
1736
+ kind: ANDROID_KIND,
1737
+ name,
1738
+ app: opts.app,
1739
+ storePassword,
1740
+ keyPassword,
1741
+ keyAlias,
1742
+ file,
1743
+ filename
1744
+ };
1745
+ } else {
1746
+ const p12Password = await d.prompt({ message: ".p12 password", hidden: true });
1747
+ const profile = await d.readFile(opts.profile);
1748
+ body = {
1749
+ platform: "ios",
1750
+ kind: IOS_KIND,
1751
+ name,
1752
+ app: opts.app,
1753
+ p12Password,
1754
+ file,
1755
+ filename,
1756
+ profile,
1757
+ profileFilename: basename2(opts.profile)
1758
+ };
1759
+ }
1760
+ const result = await d.addCredential(body);
1761
+ if (opts.json) {
1762
+ console.log(JSON.stringify(result));
1763
+ } else {
1764
+ console.log(chalk14.green(`\u2713 added ${result.name} (${result.platform})`));
1765
+ }
1766
+ process.exitCode = 0;
1767
+ } catch (err) {
1768
+ printError3(opts, err);
1769
+ process.exitCode = 1;
1770
+ }
1771
+ }
1772
+ async function credentialsList(opts, deps) {
1773
+ const d = deps ?? defaultDeps4(clientFor2(opts));
1774
+ try {
1775
+ const { credentials: credentials2 } = await d.listCredentials(opts.app);
1776
+ for (const c of credentials2) {
1777
+ if (c.expires_soon) {
1778
+ d.warn(
1779
+ `${c.name} (${c.platform}) is expiring soon${c.expires_at ? ` \u2014 ${c.expires_at}` : ""}.`
1780
+ );
1781
+ }
1782
+ }
1783
+ if (opts.json) {
1784
+ console.log(JSON.stringify(credentials2, null, 2));
1785
+ } else {
1786
+ process.stdout.write(formatCredentialsHuman(credentials2));
1787
+ }
1788
+ process.exitCode = 0;
1789
+ } catch (err) {
1790
+ printError3(opts, err);
1791
+ process.exitCode = 1;
1792
+ }
1793
+ }
1794
+ function formatIdentity(c) {
1795
+ if (!c.fingerprint) return "";
1796
+ const [tag, ...rest] = c.fingerprint.split(":");
1797
+ const value = rest.join(":");
1798
+ if (tag === "cert-sha256") return `cert sha256:${value}`;
1799
+ if (tag === "file-sha256") return `keystore-file sha256:${value}`;
1800
+ return c.platform === "android" ? `keystore-file sha256:${c.fingerprint}` : `cert sha1:${c.fingerprint}`;
1801
+ }
1802
+ function formatCredentialsHuman(credentials2) {
1803
+ if (credentials2.length === 0) {
1804
+ return "No signing credentials found.\n";
1805
+ }
1806
+ const HEADERS3 = ["NAME", "PLATFORM", "SCOPE", "IDENTITY", "EXPIRES"];
1807
+ const rows = credentials2.map((c) => [
1808
+ c.name,
1809
+ c.platform,
1810
+ c.app_public_id ?? "org-wide",
1811
+ formatIdentity(c),
1812
+ c.expires_at ? c.expires_soon ? `${c.expires_at} (soon)` : c.expires_at : ""
1813
+ ]);
1814
+ const widths = HEADERS3.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
1815
+ const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
1816
+ const join2 = (cells) => cells.join(" ").trimEnd();
1817
+ const lines = [chalk14.bold(join2(padCells(HEADERS3)))];
1818
+ for (const row of rows) lines.push(join2(padCells(row)));
1819
+ return lines.join("\n") + "\n";
1820
+ }
1821
+ async function credentialsRm(name, opts, deps) {
1822
+ const d = deps ?? defaultDeps4(clientFor2(opts));
1823
+ try {
1824
+ await d.deleteCredential({ name, app: opts.app });
1825
+ if (opts.json) {
1826
+ console.log(JSON.stringify({ event: "deleted", name }));
1827
+ } else {
1828
+ console.log(chalk14.green(`\u2713 removed ${name}`));
1829
+ }
1830
+ process.exitCode = 0;
1831
+ } catch (err) {
1832
+ printError3(opts, err);
1833
+ process.exitCode = 1;
1834
+ }
1835
+ }
1836
+ function printError3(opts, err) {
1837
+ const message = err instanceof Error ? err.message : String(err);
1838
+ if (opts.json) console.log(JSON.stringify({ event: "error", message }));
1839
+ console.error(chalk14.red(`error: ${message}`));
1579
1840
  }
1580
1841
 
1581
1842
  // src/lib/formatters/devices.ts
1582
- import chalk13 from "chalk";
1843
+ import chalk15 from "chalk";
1583
1844
  var HEADERS2 = ["DEVICE_ID", "NAME", "TYPE", "CATEGORY", "OS", "STATUS"];
1584
1845
  function formatDevicesHuman(devices) {
1585
1846
  if (devices.length === 0) {
@@ -1595,9 +1856,9 @@ function formatDevicesHuman(devices) {
1595
1856
  ]);
1596
1857
  const widths = HEADERS2.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
1597
1858
  const fmt = (cells) => cells.map((c, i) => c.padEnd(widths[i])).join(" ").trimEnd();
1598
- const lines = [chalk13.bold(fmt(HEADERS2))];
1859
+ const lines = [chalk15.bold(fmt(HEADERS2))];
1599
1860
  for (const row of rows.sort(rowSort)) {
1600
- const statusColored = row[5] === "available" ? chalk13.green(row[5]) : row[5] === "busy" ? chalk13.yellow(row[5]) : chalk13.dim(row[5]);
1861
+ const statusColored = row[5] === "available" ? chalk15.green(row[5]) : row[5] === "busy" ? chalk15.yellow(row[5]) : chalk15.dim(row[5]);
1601
1862
  lines.push(fmt([row[0], row[1], row[2], row[3], row[4], statusColored]));
1602
1863
  }
1603
1864
  return lines.join("\n") + "\n";
@@ -1687,7 +1948,7 @@ import { dirname, join } from "path";
1687
1948
  import { fileURLToPath } from "url";
1688
1949
  import { spawnSync } from "child_process";
1689
1950
  import { confirm } from "@inquirer/prompts";
1690
- import chalk14 from "chalk";
1951
+ import chalk16 from "chalk";
1691
1952
  function resolveShippedSkillPath(fromDir) {
1692
1953
  for (const rel of ["..", join("..", "..")]) {
1693
1954
  const candidate = join(fromDir, rel, "skills", "vibeview-agent", "SKILL.md");
@@ -1715,23 +1976,23 @@ function registerMcp() {
1715
1976
  encoding: "utf8"
1716
1977
  });
1717
1978
  if (res.status === 0) {
1718
- console.log(chalk14.green("\u2713 MCP server registered with Claude Code (claude mcp add vibeview)"));
1979
+ console.log(chalk16.green("\u2713 MCP server registered with Claude Code (claude mcp add vibeview)"));
1719
1980
  return;
1720
1981
  }
1721
1982
  const out = `${res.stderr ?? ""}${res.stdout ?? ""}`;
1722
1983
  if (out.includes("already exists")) {
1723
- console.log(chalk14.dim("MCP server already registered with Claude Code \u2014 nothing to do"));
1984
+ console.log(chalk16.dim("MCP server already registered with Claude Code \u2014 nothing to do"));
1724
1985
  return;
1725
1986
  }
1726
- console.error(chalk14.yellow(`\u26A0 could not register the MCP server: ${out.trim() || res.error}`));
1727
- console.error(chalk14.yellow(" register it manually: claude mcp add vibeview -- vibeview mcp"));
1987
+ console.error(chalk16.yellow(`\u26A0 could not register the MCP server: ${out.trim() || res.error}`));
1988
+ console.error(chalk16.yellow(" register it manually: claude mcp add vibeview -- vibeview mcp"));
1728
1989
  }
1729
1990
  async function agentSetup(opts) {
1730
1991
  const moduleDir = dirname(fileURLToPath(import.meta.url));
1731
1992
  const shipped = resolveShippedSkillPath(moduleDir);
1732
1993
  if (!shipped) {
1733
1994
  console.error(
1734
- chalk14.red(
1995
+ chalk16.red(
1735
1996
  "error: could not find the packaged skill (skills/vibeview-agent/SKILL.md) \u2014 is this a complete install of the vibeview package?"
1736
1997
  )
1737
1998
  );
@@ -1740,9 +2001,9 @@ async function agentSetup(opts) {
1740
2001
  }
1741
2002
  const targetDir = skillInstallDir(opts.project ?? false, process.cwd(), homedir());
1742
2003
  const result = installSkill(shipped, targetDir);
1743
- console.log(chalk14.green(`\u2713 vibeview-agent skill ${result}: ${join(targetDir, "SKILL.md")}`));
2004
+ console.log(chalk16.green(`\u2713 vibeview-agent skill ${result}: ${join(targetDir, "SKILL.md")}`));
1744
2005
  console.log(
1745
- chalk14.dim(
2006
+ chalk16.dim(
1746
2007
  opts.project ? " Claude Code discovers it automatically for anyone working in this project." : " Claude Code discovers it automatically in every project on this machine."
1747
2008
  )
1748
2009
  );
@@ -1750,7 +2011,7 @@ async function agentSetup(opts) {
1750
2011
  if (!claudeCliAvailable()) {
1751
2012
  if (opts.mcp === true) {
1752
2013
  console.error(
1753
- chalk14.yellow(
2014
+ chalk16.yellow(
1754
2015
  "\u26A0 claude CLI not found \u2014 install Claude Code, then run: claude mcp add vibeview -- vibeview mcp"
1755
2016
  )
1756
2017
  );
@@ -1764,7 +2025,7 @@ async function agentSetup(opts) {
1764
2025
  }
1765
2026
  console.log(
1766
2027
  `
1767
- Suggested line for your project's ${chalk14.bold("CLAUDE.md")} (or ${chalk14.bold(
2028
+ Suggested line for your project's ${chalk16.bold("CLAUDE.md")} (or ${chalk16.bold(
1768
2029
  "AGENTS.md"
1769
2030
  )} for other agents):
1770
2031
  `
@@ -1772,7 +2033,7 @@ Suggested line for your project's ${chalk14.bold("CLAUDE.md")} (or ${chalk14.bol
1772
2033
  console.log(` ${CLAUDE_MD_SUGGESTION}
1773
2034
  `);
1774
2035
  console.log(
1775
- chalk14.dim(
2036
+ chalk16.dim(
1776
2037
  "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"
1777
2038
  )
1778
2039
  );
@@ -1931,12 +2192,20 @@ program.command("build").description("Run your configured build command and uplo
1931
2192
  "--download",
1932
2193
  "--cloud only: download the artifact of each succeeded build (implies wait)",
1933
2194
  false
2195
+ ).option(
2196
+ "--production",
2197
+ "--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",
2198
+ false
1934
2199
  ).option("--json", "Print the result as one JSON line (NDJSON events with --cloud)", false).action((opts) => buildCommand(opts));
1935
2200
  var builds = program.command("builds").description("Manage cloud builds (see `vibeview build --cloud`)");
1936
2201
  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));
1937
2202
  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));
1938
2203
  builds.command("download <id>").description("Download a succeeded cloud build's artifact").option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--json", "Print the saved filename/path as one JSON line", false).option("--out <dir>", "Destination directory (default: current directory)").action((id, opts) => buildsDownload(id, opts));
1939
2204
  builds.command("cancel <id>").description("Cancel a queued or running cloud build").option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--json", "Print the resulting build summary as one JSON line", false).action((id, opts) => buildsCancel(id, opts));
2205
+ builds.command("delete-artifact <id>").description(
2206
+ "Delete a production build's stored artifact file (irreversible; the build record, logs, and cost stay intact \u2014 only the downloadable file is removed)"
2207
+ ).option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--json", "Print the result as one JSON line", false).action((id, opts) => buildsDeleteArtifact(id, opts));
2208
+ program.command("whoami").description("Show which account and role this CLI is authenticated as").option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--json", "Print the identity as one JSON line", false).action((opts) => whoAmI(opts));
1940
2209
  var env = program.command("env").description("Manage cloud build environment variables and secrets");
1941
2210
  env.command("list").description("List build variables and secrets (never prints a value)").option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--app <id>", "Scope to a single app (default: org-wide variables only)").option("--json", "Print the raw variable list as a JSON array", false).action((opts) => envList(opts));
1942
2211
  env.command("set <name> [value]").description(
@@ -1949,12 +2218,18 @@ env.command("rm <name>").description("Remove a build variable or secret").option
1949
2218
  env.command("pull").description(
1950
2219
  "Write plain/sensitive variable values to a .env file (secrets and files are never included)"
1951
2220
  ).option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--app <id>", "Scope to a single app (default: org-wide variables only)").option("--out <path>", "Output file path (default: .env)").option("--json", "Print the result as one JSON line", false).action((opts) => envPull(opts));
2221
+ var credentials = program.command("credentials").description("Manage app-signing credentials for production cloud builds");
2222
+ credentials.command("add").description(
2223
+ "Add a signing credential. Prompts for passwords with hidden input \u2014 they are never accepted as flags (a flag lands in shell history and in `ps`)."
2224
+ ).option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--platform <platform>", "ios or android").option("--file <path>", "The keystore (.jks) or certificate (.p12) file").option("--profile <path>", "ios only: the .mobileprovision file (required for ios)").option("--app <id>", "Scope to a single app (default: org-wide)").option("--name <name>", "Credential name (default: the file basename without extension)").action((opts) => credentialsAdd(opts));
2225
+ 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));
2226
+ 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));
1952
2227
  program.command("dev-status").description("Show the status of a `vibeview dev --detach` session in this project").option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--json", "Print as one JSON line", false).action((opts) => devStatus(opts));
1953
2228
  program.command("dev-stop").description("Stop a `vibeview dev --detach` session in this project").option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--json", "Print as one JSON line", false).action((opts) => devStop(opts));
1954
2229
  program.command("mcp").description(
1955
2230
  "Run an MCP stdio server exposing the agent verbs (tap, swipe, type, ...) plus dev_start/dev_stop as tools"
1956
2231
  ).option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").action(async (opts) => {
1957
- const { runMcpServer } = await import("./server-JA5A5PY6.js");
2232
+ const { runMcpServer } = await import("./server-KG7CEBEY.js");
1958
2233
  await runMcpServer(opts);
1959
2234
  });
1960
2235
  program.command("agent-setup").description(
@@ -1983,5 +2258,5 @@ program.parseAsync().catch((e) => {
1983
2258
  if (process.env.VIBEVIEW_DEBUG) throw e;
1984
2259
  process.stderr.write(`error: ${e instanceof Error ? e.message : String(e)}
1985
2260
  `);
1986
- process.exit(EXIT_INFRA_ERROR);
2261
+ process.exit(e instanceof UsageError ? EXIT_TEST_FAIL : EXIT_INFRA_ERROR);
1987
2262
  });
@@ -11,11 +11,11 @@ import {
11
11
  readDevState,
12
12
  renderEnvelope,
13
13
  startDevSession
14
- } from "./chunk-YFXMXO5T.js";
14
+ } from "./chunk-2VNBRBST.js";
15
15
  import {
16
16
  ApiClient,
17
17
  resolveAuth
18
- } from "./chunk-J7V3GYOI.js";
18
+ } from "./chunk-2WTLJ66O.js";
19
19
 
20
20
  // src/mcp/server.ts
21
21
  import { writeFileSync } from "fs";
@@ -239,7 +239,7 @@ async function handleUploadApp(args, deps) {
239
239
  }
240
240
  const name = typeof args.name === "string" && args.name ? args.name : void 0;
241
241
  const upload = deps.uploadApp ?? (async (f, n) => {
242
- const { uploadAppBuild } = await import("./upload-app-ZAWCZFQE.js");
242
+ const { uploadAppBuild } = await import("./upload-app-NVPQ7KRO.js");
243
243
  const { result: result2 } = await uploadAppBuild(f, { ...deps.auth, name: n });
244
244
  return result2;
245
245
  });