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