vibeview 0.17.1 → 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/README.md +40 -28
- package/dist/{chunk-SYFMOPJW.js → chunk-FG2J2BGS.js} +99 -8
- package/dist/{chunk-3IMJ7FNN.js → chunk-T6FS4ELD.js} +56 -12
- package/dist/cli.js +629 -108
- package/dist/{server-AZLB25BY.js → server-GMC6F545.js} +3 -3
- package/dist/{upload-app-XRB7CKA3.js → upload-app-4PE5BEP7.js} +1 -1
- package/package.json +1 -1
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-
|
|
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-
|
|
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/
|
|
1382
|
+
// src/commands/submit.ts
|
|
1382
1383
|
import chalk12 from "chalk";
|
|
1383
|
-
function defaultDeps2(
|
|
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 ??
|
|
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(`${
|
|
1588
|
+
console.log(`${chalk13.bold("Logged in as")} ${me.email}`);
|
|
1405
1589
|
if (me.memberships.length === 1) {
|
|
1406
|
-
console.log(`${
|
|
1590
|
+
console.log(`${chalk13.bold("Organization")} ${me.memberships[0].organization_name}`);
|
|
1407
1591
|
} else if (me.memberships.length > 1) {
|
|
1408
|
-
console.log(
|
|
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(`${
|
|
1414
|
-
console.log(`${
|
|
1415
|
-
console.log(`${
|
|
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(
|
|
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
|
-
|
|
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 ${
|
|
1424
|
-
Create an admin API token in the dashboard and use ${
|
|
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(
|
|
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
|
|
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
|
|
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(
|
|
1682
|
+
warn: (message) => console.error(chalk14.yellow(`warning: ${message}`))
|
|
1499
1683
|
};
|
|
1500
1684
|
}
|
|
1501
1685
|
async function envList(opts, deps) {
|
|
1502
|
-
const d = deps ??
|
|
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
|
-
|
|
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 = [
|
|
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 ??
|
|
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
|
-
|
|
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(
|
|
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 ??
|
|
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(
|
|
1774
|
+
console.log(chalk14.green(`\u2713 removed ${name}`));
|
|
1591
1775
|
}
|
|
1592
1776
|
process.exitCode = 0;
|
|
1593
1777
|
} catch (err) {
|
|
1594
|
-
|
|
1778
|
+
printError3(opts, err);
|
|
1595
1779
|
process.exitCode = 1;
|
|
1596
1780
|
}
|
|
1597
1781
|
}
|
|
1598
1782
|
async function envPull(opts, deps) {
|
|
1599
|
-
const d = deps ??
|
|
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(
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
1863
|
+
function defaultDeps5(client) {
|
|
1680
1864
|
return {
|
|
1681
1865
|
addCredential: (body) => client.addSigningCredential({
|
|
1682
1866
|
platform: body.platform,
|
|
@@ -1710,6 +1894,27 @@ function defaultDeps4(client) {
|
|
|
1710
1894
|
await client.deleteSigningCredential(match.id);
|
|
1711
1895
|
},
|
|
1712
1896
|
generateCredential: (body) => client.generateSigningCredential(body),
|
|
1897
|
+
getMaterial: (id) => client.getSigningCredentialMaterial(id),
|
|
1898
|
+
addAscKey: (body) => client.addAscKey(body),
|
|
1899
|
+
listAscKeys: () => client.listAscKeys(),
|
|
1900
|
+
deleteAscKey: async (name) => {
|
|
1901
|
+
const { keys } = await client.listAscKeys();
|
|
1902
|
+
const match = keys.find((k) => k.name === name);
|
|
1903
|
+
if (!match) {
|
|
1904
|
+
throw new Error(`No App Store Connect key named "${name}" found.`);
|
|
1905
|
+
}
|
|
1906
|
+
await client.deleteAscKey(match.id);
|
|
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
|
+
},
|
|
1713
1918
|
prompt: ({ message, hidden }) => hidden ? password2({ message }) : input({ message }),
|
|
1714
1919
|
isTTY: Boolean(process.stdin.isTTY),
|
|
1715
1920
|
readFile: (path) => readFileAsync2(path),
|
|
@@ -1722,7 +1927,7 @@ function defaultDeps4(client) {
|
|
|
1722
1927
|
mkdir: (dir) => mkdirSync(dir, { recursive: true }),
|
|
1723
1928
|
writeFile: (path, data, mode) => writeFileAsync(path, data, mode !== void 0 ? { mode } : void 0),
|
|
1724
1929
|
chmod: (path, mode) => chmodAsync(path, mode),
|
|
1725
|
-
warn: (message) => console.error(
|
|
1930
|
+
warn: (message) => console.error(chalk15.yellow(`warning: ${message}`))
|
|
1726
1931
|
};
|
|
1727
1932
|
}
|
|
1728
1933
|
var ANDROID_KIND = "android_keystore";
|
|
@@ -1732,7 +1937,7 @@ function isCredentialPlatform(p) {
|
|
|
1732
1937
|
return CREDENTIAL_PLATFORMS.includes(p);
|
|
1733
1938
|
}
|
|
1734
1939
|
async function credentialsAdd(opts, deps) {
|
|
1735
|
-
const d = deps ??
|
|
1940
|
+
const d = deps ?? defaultDeps5(clientFor2(opts));
|
|
1736
1941
|
if (!opts.platform || !isCredentialPlatform(opts.platform)) {
|
|
1737
1942
|
throw new UsageError('--platform is required and must be "ios", "android", or "tvos"');
|
|
1738
1943
|
}
|
|
@@ -1791,27 +1996,29 @@ async function credentialsAdd(opts, deps) {
|
|
|
1791
1996
|
if (opts.json) {
|
|
1792
1997
|
console.log(JSON.stringify(result));
|
|
1793
1998
|
} else {
|
|
1794
|
-
console.log(
|
|
1999
|
+
console.log(chalk15.green(`\u2713 added ${result.name} (${result.platform})`));
|
|
1795
2000
|
}
|
|
1796
2001
|
process.exitCode = 0;
|
|
1797
2002
|
} catch (err) {
|
|
1798
|
-
|
|
2003
|
+
printError4(opts, err);
|
|
1799
2004
|
process.exitCode = 1;
|
|
1800
2005
|
}
|
|
1801
2006
|
}
|
|
1802
2007
|
async function credentialsGenerate(opts, deps) {
|
|
1803
|
-
const d = deps ??
|
|
2008
|
+
const d = deps ?? defaultDeps5(clientFor2(opts));
|
|
1804
2009
|
if (!opts.platform) {
|
|
1805
|
-
throw new UsageError('--platform is required
|
|
2010
|
+
throw new UsageError('--platform is required and must be "android", "ios", or "tvos"');
|
|
1806
2011
|
}
|
|
1807
|
-
if (opts.platform
|
|
1808
|
-
throw new UsageError(
|
|
1809
|
-
"iOS/tvOS generation isn't available yet because it requires an Apple Developer account; upload an exported certificate with `vibeview credentials add` instead"
|
|
1810
|
-
);
|
|
2012
|
+
if (!isCredentialPlatform(opts.platform)) {
|
|
2013
|
+
throw new UsageError(`--platform must be "android", "ios", or "tvos" \u2014 got "${opts.platform}"`);
|
|
1811
2014
|
}
|
|
1812
2015
|
if (!opts.name) {
|
|
1813
2016
|
throw new UsageError("--name is required");
|
|
1814
2017
|
}
|
|
2018
|
+
if (opts.platform !== "android") {
|
|
2019
|
+
await generateAppleCredential(opts.platform, opts, d);
|
|
2020
|
+
return;
|
|
2021
|
+
}
|
|
1815
2022
|
const outDir = opts.out ?? process.cwd();
|
|
1816
2023
|
try {
|
|
1817
2024
|
d.mkdir(outDir);
|
|
@@ -1847,32 +2054,302 @@ async function credentialsGenerate(opts, deps) {
|
|
|
1847
2054
|
}
|
|
1848
2055
|
if (opts.json) {
|
|
1849
2056
|
} else {
|
|
1850
|
-
console.log(
|
|
2057
|
+
console.log(chalk15.green(`\u2713 generated ${result.name} (android)`));
|
|
1851
2058
|
console.log("");
|
|
1852
|
-
console.log(` Keystore saved to: ${
|
|
1853
|
-
console.log(` Key alias: ${
|
|
1854
|
-
console.log(` Store password: ${
|
|
1855
|
-
console.log(` 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)}`);
|
|
1856
2063
|
console.log("");
|
|
1857
2064
|
console.log(
|
|
1858
|
-
|
|
2065
|
+
chalk15.yellow(
|
|
1859
2066
|
" These are shown ONCE \u2014 VibeView cannot recover the keystore file or its passwords. Back them up somewhere safe."
|
|
1860
2067
|
)
|
|
1861
2068
|
);
|
|
1862
2069
|
console.log(
|
|
1863
|
-
|
|
2070
|
+
chalk15.yellow(
|
|
1864
2071
|
" The credential is already uploaded and active for android and androidtv production builds."
|
|
1865
2072
|
)
|
|
1866
2073
|
);
|
|
1867
2074
|
}
|
|
1868
2075
|
process.exitCode = 0;
|
|
1869
2076
|
} catch (err) {
|
|
1870
|
-
|
|
2077
|
+
printError4(opts, err);
|
|
2078
|
+
process.exitCode = 1;
|
|
2079
|
+
}
|
|
2080
|
+
}
|
|
2081
|
+
async function generateAppleCredential(platform, opts, d) {
|
|
2082
|
+
try {
|
|
2083
|
+
const result = await d.generateCredential({
|
|
2084
|
+
name: opts.name,
|
|
2085
|
+
platform,
|
|
2086
|
+
...opts.app !== void 0 ? { app: opts.app } : {},
|
|
2087
|
+
...opts.bundleId !== void 0 ? { bundleId: opts.bundleId } : {},
|
|
2088
|
+
...opts.ascKey !== void 0 ? { ascKey: opts.ascKey } : {},
|
|
2089
|
+
// Same rule as the android path: only ever `overwrite: true`, otherwise
|
|
2090
|
+
// the field is omitted and the server's 409-on-collision stays in charge.
|
|
2091
|
+
...opts.force ? { overwrite: true } : {}
|
|
2092
|
+
});
|
|
2093
|
+
if (opts.json) {
|
|
2094
|
+
console.log(JSON.stringify(result));
|
|
2095
|
+
} else {
|
|
2096
|
+
console.log(chalk15.green(`\u2713 generated ${result.name} (${result.platform})`));
|
|
2097
|
+
console.log("");
|
|
2098
|
+
console.log(` Scope: ${chalk15.bold(result.app_public_id ?? "org-wide")}`);
|
|
2099
|
+
const expires = formatExpires(result);
|
|
2100
|
+
if (expires) {
|
|
2101
|
+
console.log(` Expires: ${chalk15.bold(expires)}`);
|
|
2102
|
+
}
|
|
2103
|
+
console.log("");
|
|
2104
|
+
console.log(
|
|
2105
|
+
` The certificate and provisioning profile are stored with VibeView and already active for ${result.platform} production builds.`
|
|
2106
|
+
);
|
|
2107
|
+
console.log(
|
|
2108
|
+
` Run ${chalk15.bold(`vibeview credentials download ${result.name}`)} if you need the files locally.`
|
|
2109
|
+
);
|
|
2110
|
+
}
|
|
2111
|
+
process.exitCode = 0;
|
|
2112
|
+
} catch (err) {
|
|
2113
|
+
printError4(opts, err);
|
|
2114
|
+
process.exitCode = 1;
|
|
2115
|
+
}
|
|
2116
|
+
}
|
|
2117
|
+
async function credentialsDownload(name, opts, deps) {
|
|
2118
|
+
const d = deps ?? defaultDeps5(clientFor2(opts));
|
|
2119
|
+
const outDir = opts.out ?? process.cwd();
|
|
2120
|
+
try {
|
|
2121
|
+
const { credentials: credentials2 } = await d.listCredentials(opts.app);
|
|
2122
|
+
const wantApp = opts.app ?? null;
|
|
2123
|
+
const matches = credentials2.filter(
|
|
2124
|
+
(c) => c.name === name && (c.app_public_id ?? null) === wantApp
|
|
2125
|
+
);
|
|
2126
|
+
if (matches.length === 0) {
|
|
2127
|
+
throw new Error(
|
|
2128
|
+
`No credential named "${name}" found${opts.app ? ` for app "${opts.app}"` : ""}.`
|
|
2129
|
+
);
|
|
2130
|
+
}
|
|
2131
|
+
if (matches.length > 1) {
|
|
2132
|
+
throw new Error(
|
|
2133
|
+
`Multiple credentials named "${name}" match (platforms: ${matches.map((c) => c.platform).join(", ")}) \u2014 narrow the lookup with --app.`
|
|
2134
|
+
);
|
|
2135
|
+
}
|
|
2136
|
+
const cred = matches[0];
|
|
2137
|
+
d.mkdir(outDir);
|
|
2138
|
+
let material;
|
|
2139
|
+
try {
|
|
2140
|
+
material = await d.getMaterial(cred.id);
|
|
2141
|
+
} catch (err) {
|
|
2142
|
+
if (err instanceof NotFoundError) {
|
|
2143
|
+
throw new Error(
|
|
2144
|
+
`"${name}" was uploaded, not generated by VibeView \u2014 uploaded credential material is never readable back. Only credentials created by \`vibeview credentials generate\` can be downloaded.`
|
|
2145
|
+
);
|
|
2146
|
+
}
|
|
2147
|
+
throw err;
|
|
2148
|
+
}
|
|
2149
|
+
const savedTo = join(outDir, basename2(material.filename));
|
|
2150
|
+
const profileSavedTo = material.profile_base64 != null ? join(outDir, `${basename2(material.filename, extname(material.filename))}.mobileprovision`) : null;
|
|
2151
|
+
if (opts.json) {
|
|
2152
|
+
console.log(
|
|
2153
|
+
JSON.stringify({
|
|
2154
|
+
...material,
|
|
2155
|
+
saved_to: savedTo,
|
|
2156
|
+
...profileSavedTo !== null ? { profile_saved_to: profileSavedTo } : {}
|
|
2157
|
+
})
|
|
2158
|
+
);
|
|
2159
|
+
}
|
|
2160
|
+
const targets = [[savedTo, material.file_base64]];
|
|
2161
|
+
if (profileSavedTo !== null) targets.push([profileSavedTo, material.profile_base64]);
|
|
2162
|
+
for (const [path] of targets) {
|
|
2163
|
+
if (await d.fileExists(path)) {
|
|
2164
|
+
throw new Error(
|
|
2165
|
+
`${path} already exists \u2014 refusing to overwrite it. Move it away or pass a different --out.`
|
|
2166
|
+
);
|
|
2167
|
+
}
|
|
2168
|
+
}
|
|
2169
|
+
for (const [path, base64] of targets) {
|
|
2170
|
+
await d.writeFile(path, Buffer.from(base64, "base64"), 384);
|
|
2171
|
+
await d.chmod(path, 384);
|
|
2172
|
+
}
|
|
2173
|
+
if (!opts.json) {
|
|
2174
|
+
console.log(chalk15.green(`\u2713 downloaded ${material.name} (${material.platform})`));
|
|
2175
|
+
console.log("");
|
|
2176
|
+
console.log(` Saved to: ${chalk15.bold(savedTo)}`);
|
|
2177
|
+
if (profileSavedTo !== null) {
|
|
2178
|
+
console.log(` Profile: ${chalk15.bold(profileSavedTo)}`);
|
|
2179
|
+
}
|
|
2180
|
+
for (const [key, value] of Object.entries(material.passwords)) {
|
|
2181
|
+
console.log(` ${key}: ${chalk15.bold(value)}`);
|
|
2182
|
+
}
|
|
2183
|
+
console.log("");
|
|
2184
|
+
console.log(chalk15.yellow(" This download is recorded in your organization's audit log."));
|
|
2185
|
+
}
|
|
2186
|
+
process.exitCode = 0;
|
|
2187
|
+
} catch (err) {
|
|
2188
|
+
printError4(opts, err);
|
|
2189
|
+
process.exitCode = 1;
|
|
2190
|
+
}
|
|
2191
|
+
}
|
|
2192
|
+
async function ascKeyAdd(opts, deps) {
|
|
2193
|
+
const d = deps ?? defaultDeps5(clientFor2(opts));
|
|
2194
|
+
if (!opts.keyId) {
|
|
2195
|
+
throw new UsageError("--key-id is required (the 10-character App Store Connect key id)");
|
|
2196
|
+
}
|
|
2197
|
+
if (!opts.issuerId) {
|
|
2198
|
+
throw new UsageError("--issuer-id is required (the UUID shown above the keys table)");
|
|
2199
|
+
}
|
|
2200
|
+
if (!opts.file) {
|
|
2201
|
+
throw new UsageError("--file is required (the AuthKey_<KEYID>.p8 file Apple let you download)");
|
|
2202
|
+
}
|
|
2203
|
+
try {
|
|
2204
|
+
const file = await d.readFile(opts.file);
|
|
2205
|
+
const result = await d.addAscKey({
|
|
2206
|
+
...opts.name !== void 0 ? { name: opts.name } : {},
|
|
2207
|
+
keyId: opts.keyId,
|
|
2208
|
+
issuerId: opts.issuerId,
|
|
2209
|
+
file,
|
|
2210
|
+
filename: basename2(opts.file)
|
|
2211
|
+
});
|
|
2212
|
+
if (opts.json) {
|
|
2213
|
+
console.log(JSON.stringify(result));
|
|
2214
|
+
} else {
|
|
2215
|
+
console.log(chalk15.green(`\u2713 added App Store Connect key ${result.name}`));
|
|
2216
|
+
console.log("");
|
|
2217
|
+
console.log(` Key ID: ${chalk15.bold(result.key_id)}`);
|
|
2218
|
+
console.log(` Issuer ID: ${chalk15.bold(result.issuer_id)}`);
|
|
2219
|
+
console.log("");
|
|
2220
|
+
console.log(
|
|
2221
|
+
chalk15.yellow(
|
|
2222
|
+
" The .p8 is stored encrypted and will never be shown again \u2014 keep your own copy somewhere safe."
|
|
2223
|
+
)
|
|
2224
|
+
);
|
|
2225
|
+
}
|
|
2226
|
+
process.exitCode = 0;
|
|
2227
|
+
} catch (err) {
|
|
2228
|
+
printError4(opts, err);
|
|
2229
|
+
process.exitCode = 1;
|
|
2230
|
+
}
|
|
2231
|
+
}
|
|
2232
|
+
async function ascKeyList(opts, deps) {
|
|
2233
|
+
const d = deps ?? defaultDeps5(clientFor2(opts));
|
|
2234
|
+
try {
|
|
2235
|
+
const { keys } = await d.listAscKeys();
|
|
2236
|
+
if (opts.json) {
|
|
2237
|
+
console.log(JSON.stringify(keys, null, 2));
|
|
2238
|
+
} else {
|
|
2239
|
+
process.stdout.write(formatAscKeysHuman(keys));
|
|
2240
|
+
}
|
|
2241
|
+
process.exitCode = 0;
|
|
2242
|
+
} catch (err) {
|
|
2243
|
+
printError4(opts, err);
|
|
2244
|
+
process.exitCode = 1;
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
function formatAscKeysHuman(keys) {
|
|
2248
|
+
if (keys.length === 0) {
|
|
2249
|
+
return "No App Store Connect keys found.\n";
|
|
2250
|
+
}
|
|
2251
|
+
const HEADERS3 = ["NAME", "KEY ID", "ISSUER ID", "CREATED"];
|
|
2252
|
+
const rows = keys.map((k) => [k.name, k.key_id, k.issuer_id, k.created_at ?? ""]);
|
|
2253
|
+
const widths = HEADERS3.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
2254
|
+
const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
|
|
2255
|
+
const join3 = (cells) => cells.join(" ").trimEnd();
|
|
2256
|
+
const lines = [chalk15.bold(join3(padCells(HEADERS3)))];
|
|
2257
|
+
for (const row of rows) lines.push(join3(padCells(row)));
|
|
2258
|
+
return lines.join("\n") + "\n";
|
|
2259
|
+
}
|
|
2260
|
+
async function ascKeyRm(name, opts, deps) {
|
|
2261
|
+
const d = deps ?? defaultDeps5(clientFor2(opts));
|
|
2262
|
+
try {
|
|
2263
|
+
await d.deleteAscKey(name);
|
|
2264
|
+
if (opts.json) {
|
|
2265
|
+
console.log(JSON.stringify({ event: "deleted", name }));
|
|
2266
|
+
} else {
|
|
2267
|
+
console.log(chalk15.green(`\u2713 removed App Store Connect key ${name}`));
|
|
2268
|
+
}
|
|
2269
|
+
process.exitCode = 0;
|
|
2270
|
+
} catch (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);
|
|
1871
2348
|
process.exitCode = 1;
|
|
1872
2349
|
}
|
|
1873
2350
|
}
|
|
1874
2351
|
async function credentialsList(opts, deps) {
|
|
1875
|
-
const d = deps ??
|
|
2352
|
+
const d = deps ?? defaultDeps5(clientFor2(opts));
|
|
1876
2353
|
try {
|
|
1877
2354
|
const { credentials: credentials2 } = await d.listCredentials(opts.app);
|
|
1878
2355
|
for (const c of credentials2) {
|
|
@@ -1888,7 +2365,7 @@ async function credentialsList(opts, deps) {
|
|
|
1888
2365
|
}
|
|
1889
2366
|
process.exitCode = 0;
|
|
1890
2367
|
} catch (err) {
|
|
1891
|
-
|
|
2368
|
+
printError4(opts, err);
|
|
1892
2369
|
process.exitCode = 1;
|
|
1893
2370
|
}
|
|
1894
2371
|
}
|
|
@@ -1932,22 +2409,22 @@ function formatCredentialsHuman(credentials2) {
|
|
|
1932
2409
|
const widths = HEADERS3.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
1933
2410
|
const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
|
|
1934
2411
|
const join3 = (cells) => cells.join(" ").trimEnd();
|
|
1935
|
-
const lines = [
|
|
2412
|
+
const lines = [chalk15.bold(join3(padCells(HEADERS3)))];
|
|
1936
2413
|
for (const row of rows) lines.push(join3(padCells(row)));
|
|
1937
2414
|
return lines.join("\n") + "\n";
|
|
1938
2415
|
}
|
|
1939
2416
|
async function credentialsRm(name, opts, deps) {
|
|
1940
|
-
const d = deps ??
|
|
2417
|
+
const d = deps ?? defaultDeps5(clientFor2(opts));
|
|
1941
2418
|
try {
|
|
1942
2419
|
await d.deleteCredential({ name, app: opts.app });
|
|
1943
2420
|
if (opts.json) {
|
|
1944
2421
|
console.log(JSON.stringify({ event: "deleted", name }));
|
|
1945
2422
|
} else {
|
|
1946
|
-
console.log(
|
|
2423
|
+
console.log(chalk15.green(`\u2713 removed ${name}`));
|
|
1947
2424
|
}
|
|
1948
2425
|
process.exitCode = 0;
|
|
1949
2426
|
} catch (err) {
|
|
1950
|
-
|
|
2427
|
+
printError4(opts, err);
|
|
1951
2428
|
process.exitCode = 1;
|
|
1952
2429
|
}
|
|
1953
2430
|
}
|
|
@@ -1955,16 +2432,16 @@ function printKeystoreWriteFailure(opts, err, result) {
|
|
|
1955
2432
|
const message = err instanceof Error ? err.message : String(err);
|
|
1956
2433
|
if (opts.json) {
|
|
1957
2434
|
console.log(JSON.stringify({ event: "error", message }));
|
|
1958
|
-
console.error(
|
|
2435
|
+
console.error(chalk15.red(`error: ${message}`));
|
|
1959
2436
|
return;
|
|
1960
2437
|
}
|
|
1961
|
-
console.error(
|
|
2438
|
+
console.error(chalk15.red(`error: ${message}`));
|
|
1962
2439
|
console.error(
|
|
1963
|
-
|
|
2440
|
+
chalk15.red(
|
|
1964
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.`
|
|
1965
2442
|
)
|
|
1966
2443
|
);
|
|
1967
|
-
console.error(
|
|
2444
|
+
console.error(chalk15.red("Recover it NOW from the values below \u2014 they will not be shown again:"));
|
|
1968
2445
|
console.error("");
|
|
1969
2446
|
console.error(` Key alias: ${result.key_alias}`);
|
|
1970
2447
|
console.error(` Store password: ${result.store_password}`);
|
|
@@ -1975,19 +2452,19 @@ function printKeystoreWriteFailure(opts, err, result) {
|
|
|
1975
2452
|
` Restore the file with: echo '${result.keystore_base64}' | base64 -d > ${result.name}.keystore`
|
|
1976
2453
|
);
|
|
1977
2454
|
}
|
|
1978
|
-
function
|
|
2455
|
+
function printError4(opts, err) {
|
|
1979
2456
|
const message = err instanceof Error ? err.message : String(err);
|
|
1980
2457
|
if (opts.json) console.log(JSON.stringify({ event: "error", message }));
|
|
1981
|
-
console.error(
|
|
2458
|
+
console.error(chalk15.red(`error: ${message}`));
|
|
1982
2459
|
}
|
|
1983
2460
|
|
|
1984
2461
|
// src/commands/webhooks.ts
|
|
1985
|
-
import
|
|
2462
|
+
import chalk16 from "chalk";
|
|
1986
2463
|
function clientFor3(opts) {
|
|
1987
2464
|
const { token, baseUrl } = resolveAuth(opts);
|
|
1988
2465
|
return new ApiClient(baseUrl, token);
|
|
1989
2466
|
}
|
|
1990
|
-
function
|
|
2467
|
+
function defaultDeps6(client) {
|
|
1991
2468
|
return {
|
|
1992
2469
|
addWebhook: (body) => client.addWebhook(body),
|
|
1993
2470
|
listWebhooks: () => client.listWebhooks(),
|
|
@@ -2004,7 +2481,7 @@ async function webhooksAdd(opts, deps) {
|
|
|
2004
2481
|
if (!opts.url.startsWith("https://")) {
|
|
2005
2482
|
throw new UsageError(`--url must start with https:// (got "${opts.url}")`);
|
|
2006
2483
|
}
|
|
2007
|
-
const d = deps ??
|
|
2484
|
+
const d = deps ?? defaultDeps6(clientFor3(opts));
|
|
2008
2485
|
try {
|
|
2009
2486
|
const result = await d.addWebhook({
|
|
2010
2487
|
url: opts.url,
|
|
@@ -2013,19 +2490,19 @@ async function webhooksAdd(opts, deps) {
|
|
|
2013
2490
|
if (opts.json) {
|
|
2014
2491
|
console.log(JSON.stringify(result));
|
|
2015
2492
|
} else {
|
|
2016
|
-
console.log(
|
|
2493
|
+
console.log(chalk16.green(`\u2713 added ${result.id} ${result.url}`));
|
|
2017
2494
|
console.log("");
|
|
2018
|
-
console.log(` Signing secret: ${
|
|
2495
|
+
console.log(` Signing secret: ${chalk16.bold(result.secret)}`);
|
|
2019
2496
|
if (opts.secret !== void 0) {
|
|
2020
|
-
console.log(
|
|
2497
|
+
console.log(chalk16.yellow(" (This is the secret you provided.)"));
|
|
2021
2498
|
}
|
|
2022
2499
|
console.log(
|
|
2023
|
-
|
|
2500
|
+
chalk16.yellow(" This secret is shown ONCE and cannot be retrieved again \u2014 store it now.")
|
|
2024
2501
|
);
|
|
2025
2502
|
}
|
|
2026
2503
|
process.exitCode = 0;
|
|
2027
2504
|
} catch (err) {
|
|
2028
|
-
|
|
2505
|
+
printError5(opts, err);
|
|
2029
2506
|
process.exitCode = 1;
|
|
2030
2507
|
}
|
|
2031
2508
|
}
|
|
@@ -2051,12 +2528,12 @@ function formatWebhooksHuman(webhooks2) {
|
|
|
2051
2528
|
const widths = HEADERS3.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
2052
2529
|
const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
|
|
2053
2530
|
const join3 = (cells) => cells.join(" ").trimEnd();
|
|
2054
|
-
const lines = [
|
|
2531
|
+
const lines = [chalk16.bold(join3(padCells(HEADERS3)))];
|
|
2055
2532
|
for (const row of rows) lines.push(join3(padCells(row)));
|
|
2056
2533
|
return lines.join("\n") + "\n";
|
|
2057
2534
|
}
|
|
2058
2535
|
async function webhooksList(opts, deps) {
|
|
2059
|
-
const d = deps ??
|
|
2536
|
+
const d = deps ?? defaultDeps6(clientFor3(opts));
|
|
2060
2537
|
try {
|
|
2061
2538
|
const { webhooks: webhooks2 } = await d.listWebhooks();
|
|
2062
2539
|
if (opts.json) {
|
|
@@ -2066,22 +2543,22 @@ async function webhooksList(opts, deps) {
|
|
|
2066
2543
|
}
|
|
2067
2544
|
process.exitCode = 0;
|
|
2068
2545
|
} catch (err) {
|
|
2069
|
-
|
|
2546
|
+
printError5(opts, err);
|
|
2070
2547
|
process.exitCode = 1;
|
|
2071
2548
|
}
|
|
2072
2549
|
}
|
|
2073
2550
|
async function webhooksRm(id, opts, deps) {
|
|
2074
|
-
const d = deps ??
|
|
2551
|
+
const d = deps ?? defaultDeps6(clientFor3(opts));
|
|
2075
2552
|
try {
|
|
2076
2553
|
await d.deleteWebhook(id);
|
|
2077
2554
|
if (opts.json) {
|
|
2078
2555
|
console.log(JSON.stringify({ event: "deleted", id }));
|
|
2079
2556
|
} else {
|
|
2080
|
-
console.log(
|
|
2557
|
+
console.log(chalk16.green(`\u2713 removed ${id}`));
|
|
2081
2558
|
}
|
|
2082
2559
|
process.exitCode = 0;
|
|
2083
2560
|
} catch (err) {
|
|
2084
|
-
|
|
2561
|
+
printError5(opts, err);
|
|
2085
2562
|
process.exitCode = 1;
|
|
2086
2563
|
}
|
|
2087
2564
|
}
|
|
@@ -2098,7 +2575,7 @@ function parseUtcTimestamp(iso) {
|
|
|
2098
2575
|
var TEST_POLL_INTERVAL_MS = 3e3;
|
|
2099
2576
|
var TEST_POLL_BUDGET_MS = 6e4;
|
|
2100
2577
|
async function webhooksTest(id, opts, deps) {
|
|
2101
|
-
const d = deps ??
|
|
2578
|
+
const d = deps ?? defaultDeps6(clientFor3(opts));
|
|
2102
2579
|
try {
|
|
2103
2580
|
const startedAt = d.now();
|
|
2104
2581
|
await d.testWebhook(id);
|
|
@@ -2110,13 +2587,13 @@ async function webhooksTest(id, opts, deps) {
|
|
|
2110
2587
|
if (opts.json) console.log(JSON.stringify(ld));
|
|
2111
2588
|
if (ld.status === "succeeded") {
|
|
2112
2589
|
if (!opts.json) {
|
|
2113
|
-
console.log(
|
|
2590
|
+
console.log(chalk16.green(`\u2713 Delivered (HTTP ${ld.response_code})`));
|
|
2114
2591
|
}
|
|
2115
2592
|
process.exitCode = 0;
|
|
2116
2593
|
} else {
|
|
2117
2594
|
if (!opts.json) {
|
|
2118
2595
|
const code = ld.response_code != null ? `HTTP ${ld.response_code}` : "no response";
|
|
2119
|
-
console.error(
|
|
2596
|
+
console.error(chalk16.red(`\u2717 Failed (${code}) at ${ld.at}`));
|
|
2120
2597
|
}
|
|
2121
2598
|
process.exitCode = 1;
|
|
2122
2599
|
}
|
|
@@ -2125,24 +2602,24 @@ async function webhooksTest(id, opts, deps) {
|
|
|
2125
2602
|
}
|
|
2126
2603
|
if (opts.json) console.log(JSON.stringify({ status: "pending" }));
|
|
2127
2604
|
console.error(
|
|
2128
|
-
|
|
2605
|
+
chalk16.yellow(
|
|
2129
2606
|
`Delivery still pending after ${TEST_POLL_BUDGET_MS / 1e3}s \u2014 check \`vibeview webhooks list\` later for the result.`
|
|
2130
2607
|
)
|
|
2131
2608
|
);
|
|
2132
2609
|
process.exitCode = 1;
|
|
2133
2610
|
} catch (err) {
|
|
2134
|
-
|
|
2611
|
+
printError5(opts, err);
|
|
2135
2612
|
process.exitCode = 1;
|
|
2136
2613
|
}
|
|
2137
2614
|
}
|
|
2138
|
-
function
|
|
2615
|
+
function printError5(opts, err) {
|
|
2139
2616
|
const message = err instanceof Error ? err.message : String(err);
|
|
2140
2617
|
if (opts.json) console.log(JSON.stringify({ event: "error", message }));
|
|
2141
|
-
console.error(
|
|
2618
|
+
console.error(chalk16.red(`error: ${message}`));
|
|
2142
2619
|
}
|
|
2143
2620
|
|
|
2144
2621
|
// src/lib/formatters/devices.ts
|
|
2145
|
-
import
|
|
2622
|
+
import chalk17 from "chalk";
|
|
2146
2623
|
var HEADERS2 = ["DEVICE_ID", "NAME", "TYPE", "CATEGORY", "OS", "STATUS"];
|
|
2147
2624
|
function formatDevicesHuman(devices) {
|
|
2148
2625
|
if (devices.length === 0) {
|
|
@@ -2158,9 +2635,9 @@ function formatDevicesHuman(devices) {
|
|
|
2158
2635
|
]);
|
|
2159
2636
|
const widths = HEADERS2.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
2160
2637
|
const fmt = (cells) => cells.map((c, i) => c.padEnd(widths[i])).join(" ").trimEnd();
|
|
2161
|
-
const lines = [
|
|
2638
|
+
const lines = [chalk17.bold(fmt(HEADERS2))];
|
|
2162
2639
|
for (const row of rows.sort(rowSort)) {
|
|
2163
|
-
const statusColored = row[5] === "available" ?
|
|
2640
|
+
const statusColored = row[5] === "available" ? chalk17.green(row[5]) : row[5] === "busy" ? chalk17.yellow(row[5]) : chalk17.dim(row[5]);
|
|
2164
2641
|
lines.push(fmt([row[0], row[1], row[2], row[3], row[4], statusColored]));
|
|
2165
2642
|
}
|
|
2166
2643
|
return lines.join("\n") + "\n";
|
|
@@ -2250,7 +2727,7 @@ import { dirname, join as join2 } from "path";
|
|
|
2250
2727
|
import { fileURLToPath } from "url";
|
|
2251
2728
|
import { spawnSync } from "child_process";
|
|
2252
2729
|
import { confirm } from "@inquirer/prompts";
|
|
2253
|
-
import
|
|
2730
|
+
import chalk18 from "chalk";
|
|
2254
2731
|
function resolveShippedSkillPath(fromDir) {
|
|
2255
2732
|
for (const rel of ["..", join2("..", "..")]) {
|
|
2256
2733
|
const candidate = join2(fromDir, rel, "skills", "vibeview-agent", "SKILL.md");
|
|
@@ -2278,23 +2755,23 @@ function registerMcp() {
|
|
|
2278
2755
|
encoding: "utf8"
|
|
2279
2756
|
});
|
|
2280
2757
|
if (res.status === 0) {
|
|
2281
|
-
console.log(
|
|
2758
|
+
console.log(chalk18.green("\u2713 MCP server registered with Claude Code (claude mcp add vibeview)"));
|
|
2282
2759
|
return;
|
|
2283
2760
|
}
|
|
2284
2761
|
const out = `${res.stderr ?? ""}${res.stdout ?? ""}`;
|
|
2285
2762
|
if (out.includes("already exists")) {
|
|
2286
|
-
console.log(
|
|
2763
|
+
console.log(chalk18.dim("MCP server already registered with Claude Code \u2014 nothing to do"));
|
|
2287
2764
|
return;
|
|
2288
2765
|
}
|
|
2289
|
-
console.error(
|
|
2290
|
-
console.error(
|
|
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"));
|
|
2291
2768
|
}
|
|
2292
2769
|
async function agentSetup(opts) {
|
|
2293
2770
|
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
2294
2771
|
const shipped = resolveShippedSkillPath(moduleDir);
|
|
2295
2772
|
if (!shipped) {
|
|
2296
2773
|
console.error(
|
|
2297
|
-
|
|
2774
|
+
chalk18.red(
|
|
2298
2775
|
"error: could not find the packaged skill (skills/vibeview-agent/SKILL.md) \u2014 is this a complete install of the vibeview package?"
|
|
2299
2776
|
)
|
|
2300
2777
|
);
|
|
@@ -2303,9 +2780,9 @@ async function agentSetup(opts) {
|
|
|
2303
2780
|
}
|
|
2304
2781
|
const targetDir = skillInstallDir(opts.project ?? false, process.cwd(), homedir());
|
|
2305
2782
|
const result = installSkill(shipped, targetDir);
|
|
2306
|
-
console.log(
|
|
2783
|
+
console.log(chalk18.green(`\u2713 vibeview-agent skill ${result}: ${join2(targetDir, "SKILL.md")}`));
|
|
2307
2784
|
console.log(
|
|
2308
|
-
|
|
2785
|
+
chalk18.dim(
|
|
2309
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."
|
|
2310
2787
|
)
|
|
2311
2788
|
);
|
|
@@ -2313,7 +2790,7 @@ async function agentSetup(opts) {
|
|
|
2313
2790
|
if (!claudeCliAvailable()) {
|
|
2314
2791
|
if (opts.mcp === true) {
|
|
2315
2792
|
console.error(
|
|
2316
|
-
|
|
2793
|
+
chalk18.yellow(
|
|
2317
2794
|
"\u26A0 claude CLI not found \u2014 install Claude Code, then run: claude mcp add vibeview -- vibeview mcp"
|
|
2318
2795
|
)
|
|
2319
2796
|
);
|
|
@@ -2327,7 +2804,7 @@ async function agentSetup(opts) {
|
|
|
2327
2804
|
}
|
|
2328
2805
|
console.log(
|
|
2329
2806
|
`
|
|
2330
|
-
Suggested line for your project's ${
|
|
2807
|
+
Suggested line for your project's ${chalk18.bold("CLAUDE.md")} (or ${chalk18.bold(
|
|
2331
2808
|
"AGENTS.md"
|
|
2332
2809
|
)} for other agents):
|
|
2333
2810
|
`
|
|
@@ -2335,7 +2812,7 @@ Suggested line for your project's ${chalk17.bold("CLAUDE.md")} (or ${chalk17.bol
|
|
|
2335
2812
|
console.log(` ${CLAUDE_MD_SUGGESTION}
|
|
2336
2813
|
`);
|
|
2337
2814
|
console.log(
|
|
2338
|
-
|
|
2815
|
+
chalk18.dim(
|
|
2339
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"
|
|
2340
2817
|
)
|
|
2341
2818
|
);
|
|
@@ -2499,6 +2976,22 @@ program.command("build").description("Run your configured build command and uplo
|
|
|
2499
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",
|
|
2500
2977
|
false
|
|
2501
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));
|
|
2502
2995
|
var builds = program.command("builds").description("Manage cloud builds (see `vibeview build --cloud`)");
|
|
2503
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));
|
|
2504
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));
|
|
@@ -2528,19 +3021,47 @@ credentials.command("add").description(
|
|
|
2528
3021
|
"ios/tvos: the .mobileprovision file (required; tvOS profiles are distinct from iOS ones)"
|
|
2529
3022
|
).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));
|
|
2530
3023
|
credentials.command("generate").description(
|
|
2531
|
-
"Generate
|
|
2532
|
-
).option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option(
|
|
2533
|
-
"--
|
|
2534
|
-
"
|
|
2535
|
-
).option(
|
|
3024
|
+
"Generate a signing credential server-side. android: saves the keystore file locally and prints its passwords ONCE. ios/tvos: creates the distribution certificate and provisioning profile through the App Store Connect API (requires a stored key \u2014 see `vibeview credentials asc-key add`); fetch the files later with `vibeview credentials download`."
|
|
3025
|
+
).option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--platform <platform>", "android, ios, or tvos").option("--name <name>", "Credential name").option("--app <id>", "Scope to a single app (default: org-wide)").option("--alias <alias>", "android: key alias (default: server-chosen)").option(
|
|
3026
|
+
"--bundle-id <id>",
|
|
3027
|
+
"ios/tvos: bundle id for the provisioning profile (default: the --app's package id)"
|
|
3028
|
+
).option(
|
|
3029
|
+
"--asc-key <name>",
|
|
3030
|
+
"ios/tvos: which stored App Store Connect key to use (default when exactly one)"
|
|
3031
|
+
).option(
|
|
3032
|
+
"--out <dir>",
|
|
3033
|
+
"android: directory to save the keystore file into (default: current directory)"
|
|
3034
|
+
).option(
|
|
2536
3035
|
"--force",
|
|
2537
3036
|
"Replace an existing credential of the same name \u2014 irreversible; breaks Play Store updates signed with the old key",
|
|
2538
3037
|
false
|
|
2539
3038
|
).option(
|
|
2540
3039
|
"--json",
|
|
2541
|
-
"Print the raw generation response (including the one-time secrets) as JSON",
|
|
3040
|
+
"Print the raw generation response (android: including the one-time secrets) as JSON",
|
|
2542
3041
|
false
|
|
2543
3042
|
).action((opts) => credentialsGenerate(opts));
|
|
3043
|
+
credentials.command("download <name>").description(
|
|
3044
|
+
"Download the files of a credential VibeView GENERATED (uploaded credentials are never readable back). Admin-only; the download is recorded in the audit log."
|
|
3045
|
+
).option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--app <id>", "Scope to a single app (default: org-wide)").option("--out <dir>", "Directory to save the files into (default: current directory)").option("--json", "Print the material (including passwords) and saved paths as JSON", false).action((name, opts) => credentialsDownload(name, opts));
|
|
3046
|
+
var ascKey = credentials.command("asc-key").description(
|
|
3047
|
+
"Manage App Store Connect API keys (create one in App Store Connect \u2192 Users and Access \u2192 Integrations) \u2014 they power `credentials generate --platform ios|tvos`"
|
|
3048
|
+
);
|
|
3049
|
+
ascKey.command("add").description(
|
|
3050
|
+
"Store an App Store Connect API key. The .p8 is stored encrypted and never shown again; uploading the same name again rotates the key."
|
|
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));
|
|
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));
|
|
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));
|
|
2544
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));
|
|
2545
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));
|
|
2546
3067
|
var webhooks = program.command("webhooks").description("Manage build-completion webhook endpoints");
|
|
@@ -2555,7 +3076,7 @@ program.command("dev-stop").description("Stop a `vibeview dev --detach` session
|
|
|
2555
3076
|
program.command("mcp").description(
|
|
2556
3077
|
"Run an MCP stdio server exposing the agent verbs (tap, swipe, type, ...) plus dev_start/dev_stop as tools"
|
|
2557
3078
|
).option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").action(async (opts) => {
|
|
2558
|
-
const { runMcpServer } = await import("./server-
|
|
3079
|
+
const { runMcpServer } = await import("./server-GMC6F545.js");
|
|
2559
3080
|
await runMcpServer(opts);
|
|
2560
3081
|
});
|
|
2561
3082
|
program.command("agent-setup").description(
|