vibeview 0.12.3 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-2OI7HL6X.js → chunk-J64ENS7W.js} +235 -82
- package/dist/{chunk-OHRTLUBD.js → chunk-KUU4IUZU.js} +98 -3
- package/dist/cli.js +526 -22
- package/dist/{server-25CXVTUV.js → server-VF3N2CO5.js} +3 -3
- package/dist/{upload-app-G5SEYBTU.js → upload-app-3WXNF57W.js} +1 -1
- package/package.json +2 -1
package/dist/cli.js
CHANGED
|
@@ -13,19 +13,20 @@ import {
|
|
|
13
13
|
readDevState,
|
|
14
14
|
readMetroState,
|
|
15
15
|
runAgentVerb
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-J64ENS7W.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-
|
|
29
|
+
} from "./chunk-KUU4IUZU.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
|
|
913
|
-
if (
|
|
914
|
-
return JSON.parse(
|
|
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,14 +1349,490 @@ 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
|
|
|
1356
|
-
// src/
|
|
1375
|
+
// src/commands/whoami.ts
|
|
1357
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
|
+
|
|
1433
|
+
// src/commands/env.ts
|
|
1434
|
+
import { chmod, open as open2, readFile as readFileAsync } from "fs/promises";
|
|
1435
|
+
import { basename } from "path";
|
|
1436
|
+
import chalk13 from "chalk";
|
|
1437
|
+
import { password } from "@inquirer/prompts";
|
|
1438
|
+
|
|
1439
|
+
// src/lib/gitignore.ts
|
|
1440
|
+
import { execFile } from "child_process";
|
|
1441
|
+
function isGitIgnored(path, cwd = process.cwd()) {
|
|
1442
|
+
return new Promise((resolvePromise) => {
|
|
1443
|
+
execFile("git", ["check-ignore", "-q", path], { cwd }, (error) => {
|
|
1444
|
+
resolvePromise(!error);
|
|
1445
|
+
});
|
|
1446
|
+
});
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
// src/commands/env.ts
|
|
1450
|
+
function clientFor(opts) {
|
|
1451
|
+
const { token, baseUrl } = resolveAuth(opts);
|
|
1452
|
+
return new ApiClient(baseUrl, token);
|
|
1453
|
+
}
|
|
1454
|
+
async function writeSecureFile(path, content) {
|
|
1455
|
+
const fh = await open2(path, "w", 384);
|
|
1456
|
+
try {
|
|
1457
|
+
await fh.writeFile(content, "utf-8");
|
|
1458
|
+
} finally {
|
|
1459
|
+
await fh.close();
|
|
1460
|
+
}
|
|
1461
|
+
await chmod(path, 384);
|
|
1462
|
+
}
|
|
1463
|
+
function defaultDeps3(client) {
|
|
1464
|
+
return {
|
|
1465
|
+
listVars: (app) => client.listBuildEnvVars(app),
|
|
1466
|
+
setVar: (body) => client.setBuildEnvVar(body),
|
|
1467
|
+
setFileVar: (body) => client.setBuildEnvFileVar(body),
|
|
1468
|
+
rmVar: async ({ name, app }) => {
|
|
1469
|
+
const { variables } = await client.listBuildEnvVars(app);
|
|
1470
|
+
const wantApp = app ?? null;
|
|
1471
|
+
const match = variables.find((v) => v.name === name && (v.app_public_id ?? null) === wantApp);
|
|
1472
|
+
if (!match) {
|
|
1473
|
+
const otherScope = variables.find((v) => v.name === name);
|
|
1474
|
+
if (otherScope) {
|
|
1475
|
+
throw new Error(
|
|
1476
|
+
`No variable named "${name}" in ${app ? `app "${app}"` : "the org-wide scope"}. It exists ${otherScope.app_public_id ? `for app "${otherScope.app_public_id}"` : "org-wide"} \u2014 re-run ${otherScope.app_public_id ? `with --app ${otherScope.app_public_id}` : "without --app"} to remove that one.`
|
|
1477
|
+
);
|
|
1478
|
+
}
|
|
1479
|
+
throw new Error(`No variable named "${name}" found${app ? ` for app "${app}"` : ""}.`);
|
|
1480
|
+
}
|
|
1481
|
+
await client.deleteBuildEnvVar(match.id);
|
|
1482
|
+
},
|
|
1483
|
+
pullVars: (app) => client.pullBuildEnvVars(app),
|
|
1484
|
+
// `hidden` is always true in practice (env set only prompts for secret
|
|
1485
|
+
// values), but the parameter exists so a future caller/test can't
|
|
1486
|
+
// accidentally get echoed input by omitting it.
|
|
1487
|
+
prompt: ({ message }) => password({ message }),
|
|
1488
|
+
isTTY: Boolean(process.stdin.isTTY),
|
|
1489
|
+
isIgnored: (path) => isGitIgnored(path),
|
|
1490
|
+
writeFile: writeSecureFile,
|
|
1491
|
+
readFile: (path) => readFileAsync(path),
|
|
1492
|
+
warn: (message) => console.error(chalk13.yellow(`warning: ${message}`))
|
|
1493
|
+
};
|
|
1494
|
+
}
|
|
1495
|
+
async function envList(opts, deps) {
|
|
1496
|
+
const d = deps ?? defaultDeps3(clientFor(opts));
|
|
1497
|
+
try {
|
|
1498
|
+
const { variables } = await d.listVars(opts.app);
|
|
1499
|
+
if (opts.json) {
|
|
1500
|
+
console.log(JSON.stringify(variables, null, 2));
|
|
1501
|
+
} else {
|
|
1502
|
+
process.stdout.write(formatEnvVarsHuman(variables));
|
|
1503
|
+
}
|
|
1504
|
+
process.exitCode = 0;
|
|
1505
|
+
} catch (err) {
|
|
1506
|
+
printError2(opts, err);
|
|
1507
|
+
process.exitCode = 1;
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
function formatEnvVarsHuman(variables) {
|
|
1511
|
+
if (variables.length === 0) {
|
|
1512
|
+
return "No build variables found.\n";
|
|
1513
|
+
}
|
|
1514
|
+
const HEADERS3 = ["NAME", "VISIBILITY", "SCOPE", "HINT"];
|
|
1515
|
+
const rows = variables.map((v) => [
|
|
1516
|
+
v.name,
|
|
1517
|
+
v.is_file ? `${v.visibility} (file)` : v.visibility,
|
|
1518
|
+
v.app_public_id ?? "org-wide",
|
|
1519
|
+
v.value_hint ?? ""
|
|
1520
|
+
]);
|
|
1521
|
+
const widths = HEADERS3.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
1522
|
+
const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
|
|
1523
|
+
const join2 = (cells) => cells.join(" ").trimEnd();
|
|
1524
|
+
const lines = [chalk13.bold(join2(padCells(HEADERS3)))];
|
|
1525
|
+
for (const row of rows) lines.push(join2(padCells(row)));
|
|
1526
|
+
return lines.join("\n") + "\n";
|
|
1527
|
+
}
|
|
1528
|
+
async function envSet(name, value, opts, deps) {
|
|
1529
|
+
const d = deps ?? defaultDeps3(clientFor(opts));
|
|
1530
|
+
let resolvedValue = value;
|
|
1531
|
+
if (resolvedValue === void 0 && !opts.file) {
|
|
1532
|
+
if (opts.json || !d.isTTY) {
|
|
1533
|
+
throw new Error(
|
|
1534
|
+
"A value is required: pass it as the VALUE argument, or run interactively to be prompted."
|
|
1535
|
+
);
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
try {
|
|
1539
|
+
const visibility = resolveVisibility(opts);
|
|
1540
|
+
if (opts.file) {
|
|
1541
|
+
const content = await d.readFile(opts.file);
|
|
1542
|
+
const filename = basename(opts.file);
|
|
1543
|
+
const result2 = await d.setFileVar({ name, content, filename, visibility, app: opts.app });
|
|
1544
|
+
printSetResult(opts, result2);
|
|
1545
|
+
process.exitCode = 0;
|
|
1546
|
+
return;
|
|
1547
|
+
}
|
|
1548
|
+
if (resolvedValue === void 0) {
|
|
1549
|
+
resolvedValue = await d.prompt({ message: `Value for ${name}`, hidden: true });
|
|
1550
|
+
}
|
|
1551
|
+
const result = await d.setVar({ name, value: resolvedValue, visibility, app: opts.app });
|
|
1552
|
+
printSetResult(opts, result);
|
|
1553
|
+
process.exitCode = 0;
|
|
1554
|
+
} catch (err) {
|
|
1555
|
+
printError2(opts, err);
|
|
1556
|
+
process.exitCode = 1;
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
function resolveVisibility(opts) {
|
|
1560
|
+
const chosen = [
|
|
1561
|
+
opts.secret && "secret",
|
|
1562
|
+
opts.sensitive && "sensitive",
|
|
1563
|
+
opts.plain && "plain"
|
|
1564
|
+
].filter((v) => Boolean(v));
|
|
1565
|
+
if (chosen.length > 1) {
|
|
1566
|
+
throw new Error("Pass at most one of --secret, --sensitive, --plain.");
|
|
1567
|
+
}
|
|
1568
|
+
return chosen[0] ?? "secret";
|
|
1569
|
+
}
|
|
1570
|
+
function printSetResult(opts, result) {
|
|
1571
|
+
if (opts.json) {
|
|
1572
|
+
console.log(JSON.stringify(result));
|
|
1573
|
+
} else {
|
|
1574
|
+
console.log(chalk13.green(`\u2713 set ${result.name} (${result.visibility})`));
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
async function envRm(name, opts, deps) {
|
|
1578
|
+
const d = deps ?? defaultDeps3(clientFor(opts));
|
|
1579
|
+
try {
|
|
1580
|
+
await d.rmVar({ name, app: opts.app });
|
|
1581
|
+
if (opts.json) {
|
|
1582
|
+
console.log(JSON.stringify({ event: "deleted", name }));
|
|
1583
|
+
} else {
|
|
1584
|
+
console.log(chalk13.green(`\u2713 removed ${name}`));
|
|
1585
|
+
}
|
|
1586
|
+
process.exitCode = 0;
|
|
1587
|
+
} catch (err) {
|
|
1588
|
+
printError2(opts, err);
|
|
1589
|
+
process.exitCode = 1;
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
async function envPull(opts, deps) {
|
|
1593
|
+
const d = deps ?? defaultDeps3(clientFor(opts));
|
|
1594
|
+
const outPath = opts.out ?? ".env";
|
|
1595
|
+
try {
|
|
1596
|
+
const { variables } = await d.pullVars(opts.app);
|
|
1597
|
+
const ignored = await d.isIgnored(outPath);
|
|
1598
|
+
if (!ignored) {
|
|
1599
|
+
d.warn(
|
|
1600
|
+
`${outPath} is not covered by .gitignore \u2014 writing credentials into a tracked file risks committing them. Add it to .gitignore.`
|
|
1601
|
+
);
|
|
1602
|
+
}
|
|
1603
|
+
for (const v of variables) {
|
|
1604
|
+
if (isLossyEnvValue(v.value)) {
|
|
1605
|
+
const why = v.value.includes("\r") ? "contains a carriage return, which the .env format normalizes away" : "contains a single quote, a double quote and a backtick, which no .env quoting form can carry together";
|
|
1606
|
+
d.warn(
|
|
1607
|
+
`${v.name} ${why} \u2014 the written value differs from the stored one. Read this variable from the API instead of ${outPath}.`
|
|
1608
|
+
);
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1611
|
+
const content = formatDotEnv(variables);
|
|
1612
|
+
await d.writeFile(outPath, content);
|
|
1613
|
+
if (opts.json) {
|
|
1614
|
+
console.log(JSON.stringify({ event: "pulled", path: outPath, count: variables.length }));
|
|
1615
|
+
} else {
|
|
1616
|
+
console.log(chalk13.green(`\u2713 wrote ${variables.length} variable(s) to ${outPath}`));
|
|
1617
|
+
}
|
|
1618
|
+
process.exitCode = 0;
|
|
1619
|
+
} catch (err) {
|
|
1620
|
+
printError2(opts, err);
|
|
1621
|
+
process.exitCode = 1;
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1624
|
+
function isLossyEnvValue(value) {
|
|
1625
|
+
if (value.includes("\r")) return true;
|
|
1626
|
+
return value.includes("'") && value.includes('"') && value.includes("`");
|
|
1627
|
+
}
|
|
1628
|
+
function formatDotEnv(variables) {
|
|
1629
|
+
const lines = [
|
|
1630
|
+
"# Generated by `vibeview env pull` \u2014 do not commit.",
|
|
1631
|
+
"# Values are quoted/escaped only where the .env format requires it."
|
|
1632
|
+
];
|
|
1633
|
+
for (const v of variables) {
|
|
1634
|
+
lines.push(`${v.name}=${escapeEnvValue(v.value)}`);
|
|
1635
|
+
}
|
|
1636
|
+
return lines.join("\n") + "\n";
|
|
1637
|
+
}
|
|
1638
|
+
function escapeEnvValue(value) {
|
|
1639
|
+
if (value === "") return "''";
|
|
1640
|
+
if (!value.includes("'")) {
|
|
1641
|
+
if (!/[\s"'#\\=`]/.test(value)) return value;
|
|
1642
|
+
return `'${value}'`;
|
|
1643
|
+
}
|
|
1644
|
+
if (!value.includes('"') && !value.includes("\\")) {
|
|
1645
|
+
return `"${value}"`;
|
|
1646
|
+
}
|
|
1647
|
+
if (!value.includes("`")) {
|
|
1648
|
+
return `\`${value}\``;
|
|
1649
|
+
}
|
|
1650
|
+
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
1651
|
+
}
|
|
1652
|
+
function printError2(opts, err) {
|
|
1653
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1654
|
+
if (opts.json) console.log(JSON.stringify({ event: "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 formatCredentialsHuman(credentials2) {
|
|
1795
|
+
if (credentials2.length === 0) {
|
|
1796
|
+
return "No signing credentials found.\n";
|
|
1797
|
+
}
|
|
1798
|
+
const HEADERS3 = ["NAME", "PLATFORM", "SCOPE", "FINGERPRINT", "EXPIRES"];
|
|
1799
|
+
const rows = credentials2.map((c) => [
|
|
1800
|
+
c.name,
|
|
1801
|
+
c.platform,
|
|
1802
|
+
c.app_public_id ?? "org-wide",
|
|
1803
|
+
c.fingerprint ?? "",
|
|
1804
|
+
c.expires_at ? c.expires_soon ? `${c.expires_at} (soon)` : c.expires_at : ""
|
|
1805
|
+
]);
|
|
1806
|
+
const widths = HEADERS3.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
1807
|
+
const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
|
|
1808
|
+
const join2 = (cells) => cells.join(" ").trimEnd();
|
|
1809
|
+
const lines = [chalk14.bold(join2(padCells(HEADERS3)))];
|
|
1810
|
+
for (const row of rows) lines.push(join2(padCells(row)));
|
|
1811
|
+
return lines.join("\n") + "\n";
|
|
1812
|
+
}
|
|
1813
|
+
async function credentialsRm(name, opts, deps) {
|
|
1814
|
+
const d = deps ?? defaultDeps4(clientFor2(opts));
|
|
1815
|
+
try {
|
|
1816
|
+
await d.deleteCredential({ name, app: opts.app });
|
|
1817
|
+
if (opts.json) {
|
|
1818
|
+
console.log(JSON.stringify({ event: "deleted", name }));
|
|
1819
|
+
} else {
|
|
1820
|
+
console.log(chalk14.green(`\u2713 removed ${name}`));
|
|
1821
|
+
}
|
|
1822
|
+
process.exitCode = 0;
|
|
1823
|
+
} catch (err) {
|
|
1824
|
+
printError3(opts, err);
|
|
1825
|
+
process.exitCode = 1;
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1828
|
+
function printError3(opts, err) {
|
|
1829
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1830
|
+
if (opts.json) console.log(JSON.stringify({ event: "error", message }));
|
|
1831
|
+
console.error(chalk14.red(`error: ${message}`));
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
// src/lib/formatters/devices.ts
|
|
1835
|
+
import chalk15 from "chalk";
|
|
1358
1836
|
var HEADERS2 = ["DEVICE_ID", "NAME", "TYPE", "CATEGORY", "OS", "STATUS"];
|
|
1359
1837
|
function formatDevicesHuman(devices) {
|
|
1360
1838
|
if (devices.length === 0) {
|
|
@@ -1370,9 +1848,9 @@ function formatDevicesHuman(devices) {
|
|
|
1370
1848
|
]);
|
|
1371
1849
|
const widths = HEADERS2.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
1372
1850
|
const fmt = (cells) => cells.map((c, i) => c.padEnd(widths[i])).join(" ").trimEnd();
|
|
1373
|
-
const lines = [
|
|
1851
|
+
const lines = [chalk15.bold(fmt(HEADERS2))];
|
|
1374
1852
|
for (const row of rows.sort(rowSort)) {
|
|
1375
|
-
const statusColored = row[5] === "available" ?
|
|
1853
|
+
const statusColored = row[5] === "available" ? chalk15.green(row[5]) : row[5] === "busy" ? chalk15.yellow(row[5]) : chalk15.dim(row[5]);
|
|
1376
1854
|
lines.push(fmt([row[0], row[1], row[2], row[3], row[4], statusColored]));
|
|
1377
1855
|
}
|
|
1378
1856
|
return lines.join("\n") + "\n";
|
|
@@ -1462,7 +1940,7 @@ import { dirname, join } from "path";
|
|
|
1462
1940
|
import { fileURLToPath } from "url";
|
|
1463
1941
|
import { spawnSync } from "child_process";
|
|
1464
1942
|
import { confirm } from "@inquirer/prompts";
|
|
1465
|
-
import
|
|
1943
|
+
import chalk16 from "chalk";
|
|
1466
1944
|
function resolveShippedSkillPath(fromDir) {
|
|
1467
1945
|
for (const rel of ["..", join("..", "..")]) {
|
|
1468
1946
|
const candidate = join(fromDir, rel, "skills", "vibeview-agent", "SKILL.md");
|
|
@@ -1490,23 +1968,23 @@ function registerMcp() {
|
|
|
1490
1968
|
encoding: "utf8"
|
|
1491
1969
|
});
|
|
1492
1970
|
if (res.status === 0) {
|
|
1493
|
-
console.log(
|
|
1971
|
+
console.log(chalk16.green("\u2713 MCP server registered with Claude Code (claude mcp add vibeview)"));
|
|
1494
1972
|
return;
|
|
1495
1973
|
}
|
|
1496
1974
|
const out = `${res.stderr ?? ""}${res.stdout ?? ""}`;
|
|
1497
1975
|
if (out.includes("already exists")) {
|
|
1498
|
-
console.log(
|
|
1976
|
+
console.log(chalk16.dim("MCP server already registered with Claude Code \u2014 nothing to do"));
|
|
1499
1977
|
return;
|
|
1500
1978
|
}
|
|
1501
|
-
console.error(
|
|
1502
|
-
console.error(
|
|
1979
|
+
console.error(chalk16.yellow(`\u26A0 could not register the MCP server: ${out.trim() || res.error}`));
|
|
1980
|
+
console.error(chalk16.yellow(" register it manually: claude mcp add vibeview -- vibeview mcp"));
|
|
1503
1981
|
}
|
|
1504
1982
|
async function agentSetup(opts) {
|
|
1505
1983
|
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
1506
1984
|
const shipped = resolveShippedSkillPath(moduleDir);
|
|
1507
1985
|
if (!shipped) {
|
|
1508
1986
|
console.error(
|
|
1509
|
-
|
|
1987
|
+
chalk16.red(
|
|
1510
1988
|
"error: could not find the packaged skill (skills/vibeview-agent/SKILL.md) \u2014 is this a complete install of the vibeview package?"
|
|
1511
1989
|
)
|
|
1512
1990
|
);
|
|
@@ -1515,9 +1993,9 @@ async function agentSetup(opts) {
|
|
|
1515
1993
|
}
|
|
1516
1994
|
const targetDir = skillInstallDir(opts.project ?? false, process.cwd(), homedir());
|
|
1517
1995
|
const result = installSkill(shipped, targetDir);
|
|
1518
|
-
console.log(
|
|
1996
|
+
console.log(chalk16.green(`\u2713 vibeview-agent skill ${result}: ${join(targetDir, "SKILL.md")}`));
|
|
1519
1997
|
console.log(
|
|
1520
|
-
|
|
1998
|
+
chalk16.dim(
|
|
1521
1999
|
opts.project ? " Claude Code discovers it automatically for anyone working in this project." : " Claude Code discovers it automatically in every project on this machine."
|
|
1522
2000
|
)
|
|
1523
2001
|
);
|
|
@@ -1525,7 +2003,7 @@ async function agentSetup(opts) {
|
|
|
1525
2003
|
if (!claudeCliAvailable()) {
|
|
1526
2004
|
if (opts.mcp === true) {
|
|
1527
2005
|
console.error(
|
|
1528
|
-
|
|
2006
|
+
chalk16.yellow(
|
|
1529
2007
|
"\u26A0 claude CLI not found \u2014 install Claude Code, then run: claude mcp add vibeview -- vibeview mcp"
|
|
1530
2008
|
)
|
|
1531
2009
|
);
|
|
@@ -1539,7 +2017,7 @@ async function agentSetup(opts) {
|
|
|
1539
2017
|
}
|
|
1540
2018
|
console.log(
|
|
1541
2019
|
`
|
|
1542
|
-
Suggested line for your project's ${
|
|
2020
|
+
Suggested line for your project's ${chalk16.bold("CLAUDE.md")} (or ${chalk16.bold(
|
|
1543
2021
|
"AGENTS.md"
|
|
1544
2022
|
)} for other agents):
|
|
1545
2023
|
`
|
|
@@ -1547,7 +2025,7 @@ Suggested line for your project's ${chalk13.bold("CLAUDE.md")} (or ${chalk13.bol
|
|
|
1547
2025
|
console.log(` ${CLAUDE_MD_SUGGESTION}
|
|
1548
2026
|
`);
|
|
1549
2027
|
console.log(
|
|
1550
|
-
|
|
2028
|
+
chalk16.dim(
|
|
1551
2029
|
"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"
|
|
1552
2030
|
)
|
|
1553
2031
|
);
|
|
@@ -1706,18 +2184,44 @@ program.command("build").description("Run your configured build command and uplo
|
|
|
1706
2184
|
"--download",
|
|
1707
2185
|
"--cloud only: download the artifact of each succeeded build (implies wait)",
|
|
1708
2186
|
false
|
|
2187
|
+
).option(
|
|
2188
|
+
"--production",
|
|
2189
|
+
"--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",
|
|
2190
|
+
false
|
|
1709
2191
|
).option("--json", "Print the result as one JSON line (NDJSON events with --cloud)", false).action((opts) => buildCommand(opts));
|
|
1710
2192
|
var builds = program.command("builds").description("Manage cloud builds (see `vibeview build --cloud`)");
|
|
1711
2193
|
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));
|
|
1712
2194
|
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));
|
|
1713
2195
|
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));
|
|
1714
2196
|
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));
|
|
2197
|
+
builds.command("delete-artifact <id>").description(
|
|
2198
|
+
"Delete a production build's stored artifact file (irreversible; the build record, logs, and cost stay intact \u2014 only the downloadable file is removed)"
|
|
2199
|
+
).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));
|
|
2200
|
+
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));
|
|
2201
|
+
var env = program.command("env").description("Manage cloud build environment variables and secrets");
|
|
2202
|
+
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));
|
|
2203
|
+
env.command("set <name> [value]").description(
|
|
2204
|
+
"Set a build variable or secret. Defaults to --secret (never retrievable via the API) unless --plain or --sensitive is passed explicitly."
|
|
2205
|
+
).option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option("--app <id>", "Scope to a single app (default: org-wide)").option("--secret", "Store as secret (default): never returned by the API, including `env pull`").option(
|
|
2206
|
+
"--sensitive",
|
|
2207
|
+
"Store as sensitive: value_hint only in `env list`, full value via `env pull`"
|
|
2208
|
+
).option("--plain", "Store as plain: value returned by `env pull`, unmasked").option("--file <path>", "Upload a file as the value (e.g. a keystore) instead of a text value").option("--json", "Print the resulting variable as one JSON line", false).action((name, value, opts) => envSet(name, value, opts));
|
|
2209
|
+
env.command("rm <name>").description("Remove a build variable or secret").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) => envRm(name, opts));
|
|
2210
|
+
env.command("pull").description(
|
|
2211
|
+
"Write plain/sensitive variable values to a .env file (secrets and files are never included)"
|
|
2212
|
+
).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));
|
|
2213
|
+
var credentials = program.command("credentials").description("Manage app-signing credentials for production cloud builds");
|
|
2214
|
+
credentials.command("add").description(
|
|
2215
|
+
"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`)."
|
|
2216
|
+
).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));
|
|
2217
|
+
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));
|
|
2218
|
+
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));
|
|
1715
2219
|
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));
|
|
1716
2220
|
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));
|
|
1717
2221
|
program.command("mcp").description(
|
|
1718
2222
|
"Run an MCP stdio server exposing the agent verbs (tap, swipe, type, ...) plus dev_start/dev_stop as tools"
|
|
1719
2223
|
).option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").action(async (opts) => {
|
|
1720
|
-
const { runMcpServer } = await import("./server-
|
|
2224
|
+
const { runMcpServer } = await import("./server-VF3N2CO5.js");
|
|
1721
2225
|
await runMcpServer(opts);
|
|
1722
2226
|
});
|
|
1723
2227
|
program.command("agent-setup").description(
|
|
@@ -1746,5 +2250,5 @@ program.parseAsync().catch((e) => {
|
|
|
1746
2250
|
if (process.env.VIBEVIEW_DEBUG) throw e;
|
|
1747
2251
|
process.stderr.write(`error: ${e instanceof Error ? e.message : String(e)}
|
|
1748
2252
|
`);
|
|
1749
|
-
process.exit(EXIT_INFRA_ERROR);
|
|
2253
|
+
process.exit(e instanceof UsageError ? EXIT_TEST_FAIL : EXIT_INFRA_ERROR);
|
|
1750
2254
|
});
|