vibeview 0.11.0 → 0.12.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-4UGLSIZM.js → chunk-NFFLJNUI.js} +752 -73
- package/dist/{chunk-AG3RZCPW.js → chunk-OHRTLUBD.js} +123 -8
- package/dist/cli.js +253 -21
- package/dist/{server-S6OCJERW.js → server-A44Z2HUK.js} +3 -3
- package/dist/{upload-app-JIOFLMS3.js → upload-app-G5SEYBTU.js} +1 -1
- package/package.json +2 -1
|
@@ -37,8 +37,25 @@ function resolveAuth(options) {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
// src/lib/api-client.ts
|
|
40
|
-
import { readFileSync as readFileSync2 } from "fs";
|
|
41
|
-
import {
|
|
40
|
+
import { openAsBlob, readFileSync as readFileSync2 } from "fs";
|
|
41
|
+
import { createWriteStream, renameSync, rmSync } from "fs";
|
|
42
|
+
import { basename, join as join2, resolve, sep } from "path";
|
|
43
|
+
import { pipeline } from "stream/promises";
|
|
44
|
+
import { Readable } from "stream";
|
|
45
|
+
var NotFoundError = class extends Error {
|
|
46
|
+
constructor(message) {
|
|
47
|
+
super(message);
|
|
48
|
+
this.name = "NotFoundError";
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
function extractDetail(text) {
|
|
52
|
+
try {
|
|
53
|
+
const parsed = JSON.parse(text);
|
|
54
|
+
if (parsed && typeof parsed.detail === "string") return parsed.detail;
|
|
55
|
+
} catch {
|
|
56
|
+
}
|
|
57
|
+
return void 0;
|
|
58
|
+
}
|
|
42
59
|
var ApiClient = class {
|
|
43
60
|
constructor(baseUrl, token) {
|
|
44
61
|
this.baseUrl = baseUrl;
|
|
@@ -76,8 +93,8 @@ var ApiClient = class {
|
|
|
76
93
|
if (!resp.ok) {
|
|
77
94
|
const text = await resp.text().catch(() => "");
|
|
78
95
|
if (resp.status === 401) throw new Error("Authentication failed. Check your API token.");
|
|
79
|
-
if (resp.status === 404) throw new
|
|
80
|
-
throw new Error(`API error ${resp.status}: ${text}`);
|
|
96
|
+
if (resp.status === 404) throw new NotFoundError(`Not found: ${extractDetail(text) ?? path}`);
|
|
97
|
+
throw new Error(extractDetail(text) ?? `API error ${resp.status}: ${text}`);
|
|
81
98
|
}
|
|
82
99
|
return resp.json();
|
|
83
100
|
}
|
|
@@ -148,8 +165,8 @@ var ApiClient = class {
|
|
|
148
165
|
if (!resp.ok) {
|
|
149
166
|
const text = await resp.text().catch(() => "");
|
|
150
167
|
if (resp.status === 401) throw new Error("Authentication failed. Check your API token.");
|
|
151
|
-
if (resp.status === 404) throw new
|
|
152
|
-
throw new Error(`API error ${resp.status}: ${text}`);
|
|
168
|
+
if (resp.status === 404) throw new NotFoundError(`Not found: ${extractDetail(text) ?? path}`);
|
|
169
|
+
throw new Error(extractDetail(text) ?? `API error ${resp.status}: ${text}`);
|
|
153
170
|
}
|
|
154
171
|
return resp.json();
|
|
155
172
|
}
|
|
@@ -168,8 +185,8 @@ var ApiClient = class {
|
|
|
168
185
|
if (!resp.ok) {
|
|
169
186
|
const text = await resp.text().catch(() => "");
|
|
170
187
|
if (resp.status === 401) throw new Error("Authentication failed. Check your API token.");
|
|
171
|
-
if (resp.status === 404) throw new
|
|
172
|
-
throw new Error(`API error ${resp.status}: ${text}`);
|
|
188
|
+
if (resp.status === 404) throw new NotFoundError(`Not found: ${extractDetail(text) ?? path}`);
|
|
189
|
+
throw new Error(extractDetail(text) ?? `API error ${resp.status}: ${text}`);
|
|
173
190
|
}
|
|
174
191
|
return resp.json();
|
|
175
192
|
}
|
|
@@ -244,7 +261,104 @@ var ApiClient = class {
|
|
|
244
261
|
async agentTool(sessionId, tool, args) {
|
|
245
262
|
return this.requestRaw("POST", `/sessions/${sessionId}/agent/tools`, { tool, args });
|
|
246
263
|
}
|
|
264
|
+
/**
|
|
265
|
+
* Create up to 4 cloud builds from a single manifest + source tarball
|
|
266
|
+
* (POST /api/v1/builds/). Multipart: "manifest" is the JSON-encoded
|
|
267
|
+
* manifest, "source" is the tarball attached via fs.openAsBlob (Node >=19.8;
|
|
268
|
+
* the CLI's engines floor is >=20, see package.json) — mirrors uploadBuild's
|
|
269
|
+
* read-then-attach pattern but avoids buffering the (up to 2 GB) tarball
|
|
270
|
+
* into memory.
|
|
271
|
+
*/
|
|
272
|
+
async createCloudBuilds(manifest, tarPath) {
|
|
273
|
+
const blob = await openAsBlob(tarPath);
|
|
274
|
+
const formData = new FormData();
|
|
275
|
+
formData.append("manifest", JSON.stringify(manifest));
|
|
276
|
+
formData.append("source", blob, "source.tgz");
|
|
277
|
+
return this.requestFormData("POST", "/builds/", formData);
|
|
278
|
+
}
|
|
279
|
+
async getCloudBuild(id) {
|
|
280
|
+
return this.requestRaw("GET", `/builds/${id}`);
|
|
281
|
+
}
|
|
282
|
+
async getCloudBuildLogs(id, offset) {
|
|
283
|
+
const qs = offset !== void 0 ? `?offset=${offset}` : "";
|
|
284
|
+
return this.requestRaw("GET", `/builds/${id}/logs${qs}`);
|
|
285
|
+
}
|
|
286
|
+
async listCloudBuilds(limit, offset) {
|
|
287
|
+
const params = new URLSearchParams();
|
|
288
|
+
if (limit !== void 0) params.set("limit", String(limit));
|
|
289
|
+
if (offset !== void 0) params.set("offset", String(offset));
|
|
290
|
+
const qs = params.toString();
|
|
291
|
+
return this.requestRaw("GET", `/builds/${qs ? `?${qs}` : ""}`);
|
|
292
|
+
}
|
|
293
|
+
async cancelCloudBuild(id) {
|
|
294
|
+
return this.requestRaw("POST", `/builds/${id}/cancel`);
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Download a finished build's artifact (GET /api/v1/builds/{id}/artifact),
|
|
298
|
+
* streaming the response body straight to a file in destDir. The filename
|
|
299
|
+
* is taken from Content-Disposition — RFC5987 filename* preferred over the
|
|
300
|
+
* plain filename= form — falling back to "<id>.bin" if the header is
|
|
301
|
+
* missing or unparseable.
|
|
302
|
+
*/
|
|
303
|
+
async downloadCloudBuildArtifact(id, destDir) {
|
|
304
|
+
const url = `${this.baseUrl}/api/v1/builds/${id}/artifact`;
|
|
305
|
+
const headers = {
|
|
306
|
+
Authorization: `Bearer ${this.token}`
|
|
307
|
+
};
|
|
308
|
+
const resp = await this.fetchOrExplain(url, { method: "GET", headers });
|
|
309
|
+
if (!resp.ok) {
|
|
310
|
+
const text = await resp.text().catch(() => "");
|
|
311
|
+
if (resp.status === 401) throw new Error("Authentication failed. Check your API token.");
|
|
312
|
+
if (resp.status === 404)
|
|
313
|
+
throw new NotFoundError(`Not found: ${extractDetail(text) ?? `/builds/${id}/artifact`}`);
|
|
314
|
+
throw new Error(extractDetail(text) ?? `API error ${resp.status}: ${text}`);
|
|
315
|
+
}
|
|
316
|
+
const rawFilename = parseContentDispositionFilename(resp.headers.get("content-disposition"));
|
|
317
|
+
const filename = sanitizeDownloadFilename(rawFilename) ?? `${id}.bin`;
|
|
318
|
+
const path = join2(destDir, filename);
|
|
319
|
+
const resolvedDestDir = resolve(destDir);
|
|
320
|
+
const resolvedPath = resolve(path);
|
|
321
|
+
if (resolvedPath !== join2(resolvedDestDir, filename) || !resolvedPath.startsWith(resolvedDestDir + sep)) {
|
|
322
|
+
throw new Error(`Refusing to write artifact outside destination directory: ${filename}`);
|
|
323
|
+
}
|
|
324
|
+
if (!resp.body) {
|
|
325
|
+
createWriteStream(path).end();
|
|
326
|
+
return { filename, path };
|
|
327
|
+
}
|
|
328
|
+
const partialPath = `${path}.partial`;
|
|
329
|
+
try {
|
|
330
|
+
await pipeline(Readable.fromWeb(resp.body), createWriteStream(partialPath));
|
|
331
|
+
renameSync(partialPath, path);
|
|
332
|
+
} catch (err) {
|
|
333
|
+
rmSync(partialPath, { force: true });
|
|
334
|
+
throw err;
|
|
335
|
+
}
|
|
336
|
+
return { filename, path };
|
|
337
|
+
}
|
|
247
338
|
};
|
|
339
|
+
function parseContentDispositionFilename(header) {
|
|
340
|
+
if (!header) return void 0;
|
|
341
|
+
const extMatch = header.match(/filename\*\s*=\s*[^']*''([^;]+)/i);
|
|
342
|
+
if (extMatch) {
|
|
343
|
+
try {
|
|
344
|
+
return decodeURIComponent(extMatch[1].trim());
|
|
345
|
+
} catch {
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
const plainMatch = header.match(/filename\s*=\s*"?([^";]+)"?/i);
|
|
349
|
+
if (plainMatch) {
|
|
350
|
+
return plainMatch[1].trim();
|
|
351
|
+
}
|
|
352
|
+
return void 0;
|
|
353
|
+
}
|
|
354
|
+
function sanitizeDownloadFilename(raw) {
|
|
355
|
+
if (!raw) return void 0;
|
|
356
|
+
if (raw.includes("\0")) return void 0;
|
|
357
|
+
const base = basename(raw);
|
|
358
|
+
if (!base || base === "." || base === "..") return void 0;
|
|
359
|
+
if (base.includes("/") || base.includes("\\")) return void 0;
|
|
360
|
+
return base;
|
|
361
|
+
}
|
|
248
362
|
|
|
249
363
|
// src/lib/spinner.ts
|
|
250
364
|
import ora from "ora";
|
|
@@ -321,6 +435,7 @@ export {
|
|
|
321
435
|
loadConfig,
|
|
322
436
|
saveConfig,
|
|
323
437
|
resolveAuth,
|
|
438
|
+
NotFoundError,
|
|
324
439
|
ApiClient,
|
|
325
440
|
createSpinner,
|
|
326
441
|
EXIT_PASS,
|
package/dist/cli.js
CHANGED
|
@@ -1,25 +1,31 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
AGENT_COMMANDS,
|
|
4
|
+
BUILD_TERMINAL_STATUSES,
|
|
4
5
|
buildCommand,
|
|
5
6
|
clearDevState,
|
|
6
7
|
dev,
|
|
8
|
+
fetchFinalSummary,
|
|
7
9
|
killOwnedMetro,
|
|
10
|
+
newTransitionState,
|
|
11
|
+
printOutcomeLine,
|
|
12
|
+
printTransition,
|
|
8
13
|
readDevState,
|
|
9
14
|
readMetroState,
|
|
10
15
|
runAgentVerb
|
|
11
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-NFFLJNUI.js";
|
|
12
17
|
import {
|
|
13
18
|
ApiClient,
|
|
14
19
|
EXIT_INFRA_ERROR,
|
|
15
20
|
EXIT_PASS,
|
|
16
21
|
EXIT_TEST_FAIL,
|
|
22
|
+
NotFoundError,
|
|
17
23
|
createSpinner,
|
|
18
24
|
loadConfig,
|
|
19
25
|
resolveAuth,
|
|
20
26
|
saveConfig,
|
|
21
27
|
uploadApp
|
|
22
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-OHRTLUBD.js";
|
|
23
29
|
|
|
24
30
|
// src/cli.ts
|
|
25
31
|
import { createRequire } from "module";
|
|
@@ -1134,9 +1140,222 @@ async function devStop(opts) {
|
|
|
1134
1140
|
process.exit(0);
|
|
1135
1141
|
}
|
|
1136
1142
|
|
|
1137
|
-
// src/
|
|
1143
|
+
// src/commands/builds.ts
|
|
1144
|
+
import chalk11 from "chalk";
|
|
1145
|
+
|
|
1146
|
+
// src/lib/formatters/builds.ts
|
|
1138
1147
|
import chalk10 from "chalk";
|
|
1139
|
-
var HEADERS = ["
|
|
1148
|
+
var HEADERS = ["ID", "PLATFORM", "STATUS", "CREATED", "COST", "APP"];
|
|
1149
|
+
function formatCostCell(costMicro) {
|
|
1150
|
+
return costMicro > 0 ? `$${(costMicro / 1e6).toFixed(2)}` : "";
|
|
1151
|
+
}
|
|
1152
|
+
var STATUS_COLOR = {
|
|
1153
|
+
succeeded: chalk10.green,
|
|
1154
|
+
failed: chalk10.red,
|
|
1155
|
+
timed_out: chalk10.red,
|
|
1156
|
+
infra_failed: chalk10.red,
|
|
1157
|
+
canceled: chalk10.dim,
|
|
1158
|
+
running: chalk10.yellow,
|
|
1159
|
+
queued: chalk10.dim
|
|
1160
|
+
};
|
|
1161
|
+
function formatBuildsHuman(builds2) {
|
|
1162
|
+
if (builds2.length === 0) {
|
|
1163
|
+
return "No cloud builds found.\n";
|
|
1164
|
+
}
|
|
1165
|
+
const rows = builds2.map((b) => [
|
|
1166
|
+
b.id,
|
|
1167
|
+
b.platform,
|
|
1168
|
+
b.status,
|
|
1169
|
+
b.created_at ?? "",
|
|
1170
|
+
formatCostCell(b.cost_micro),
|
|
1171
|
+
b.app_public_id ?? ""
|
|
1172
|
+
]);
|
|
1173
|
+
const widths = HEADERS.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
1174
|
+
const padCells = (cells) => cells.map((c, i) => c.padEnd(widths[i]));
|
|
1175
|
+
const join2 = (cells) => cells.join(" ").trimEnd();
|
|
1176
|
+
const lines = [chalk10.bold(join2(padCells(HEADERS)))];
|
|
1177
|
+
for (const row of rows) {
|
|
1178
|
+
const padded = padCells(row);
|
|
1179
|
+
const colorFn = STATUS_COLOR[row[2]] ?? ((s) => s);
|
|
1180
|
+
padded[2] = colorFn(row[2]) + padded[2].slice(row[2].length);
|
|
1181
|
+
lines.push(join2(padded));
|
|
1182
|
+
}
|
|
1183
|
+
return lines.join("\n") + "\n";
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
// src/commands/builds.ts
|
|
1187
|
+
function defaultDeps(client) {
|
|
1188
|
+
return {
|
|
1189
|
+
listCloudBuilds: client.listCloudBuilds.bind(client),
|
|
1190
|
+
getCloudBuild: client.getCloudBuild.bind(client),
|
|
1191
|
+
getCloudBuildLogs: client.getCloudBuildLogs.bind(client),
|
|
1192
|
+
cancelCloudBuild: client.cancelCloudBuild.bind(client),
|
|
1193
|
+
downloadCloudBuildArtifact: client.downloadCloudBuildArtifact.bind(client)
|
|
1194
|
+
};
|
|
1195
|
+
}
|
|
1196
|
+
var POLL_INTERVAL_MS = 2e3;
|
|
1197
|
+
async function buildsList(opts, deps) {
|
|
1198
|
+
const { token, baseUrl } = resolveAuth(opts);
|
|
1199
|
+
const client = new ApiClient(baseUrl, token);
|
|
1200
|
+
const d = deps ?? defaultDeps(client);
|
|
1201
|
+
let limit;
|
|
1202
|
+
if (opts.limit !== void 0) {
|
|
1203
|
+
limit = parseInt(opts.limit, 10);
|
|
1204
|
+
if (!Number.isInteger(limit) || limit < 1) {
|
|
1205
|
+
printError(opts, new Error(`invalid --limit "${opts.limit}": must be a positive integer`));
|
|
1206
|
+
process.exitCode = 1;
|
|
1207
|
+
return;
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
try {
|
|
1211
|
+
const { builds: builds2 } = await d.listCloudBuilds(limit);
|
|
1212
|
+
if (opts.json) {
|
|
1213
|
+
console.log(JSON.stringify(builds2, null, 2));
|
|
1214
|
+
} else {
|
|
1215
|
+
process.stdout.write(formatBuildsHuman(builds2));
|
|
1216
|
+
}
|
|
1217
|
+
process.exitCode = 0;
|
|
1218
|
+
} catch (err) {
|
|
1219
|
+
printError(opts, err);
|
|
1220
|
+
process.exitCode = 1;
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
async function buildsLogs(id, opts, deps) {
|
|
1224
|
+
const { token, baseUrl } = resolveAuth(opts);
|
|
1225
|
+
const client = new ApiClient(baseUrl, token);
|
|
1226
|
+
const d = deps ?? defaultDeps(client);
|
|
1227
|
+
try {
|
|
1228
|
+
if (!opts.follow) {
|
|
1229
|
+
const logs = await d.getCloudBuildLogs(id, 0);
|
|
1230
|
+
if (opts.json) {
|
|
1231
|
+
console.log(JSON.stringify(logs));
|
|
1232
|
+
} else {
|
|
1233
|
+
if (logs.content) process.stdout.write(logs.content);
|
|
1234
|
+
console.error(chalk11.dim(`status: ${logs.status}`));
|
|
1235
|
+
}
|
|
1236
|
+
process.exitCode = 0;
|
|
1237
|
+
return;
|
|
1238
|
+
}
|
|
1239
|
+
const final = await followLogs(id, opts, d);
|
|
1240
|
+
process.exitCode = final.status === "succeeded" ? 0 : 1;
|
|
1241
|
+
} catch (err) {
|
|
1242
|
+
printError(opts, err);
|
|
1243
|
+
process.exitCode = 1;
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
var MAX_CONSECUTIVE_POLL_FAILURES = 5;
|
|
1247
|
+
async function followLogs(id, opts, deps) {
|
|
1248
|
+
const pollIntervalMs = deps.pollIntervalMs ?? POLL_INTERVAL_MS;
|
|
1249
|
+
let offset = 0;
|
|
1250
|
+
let status = "queued";
|
|
1251
|
+
let consecutiveFailures = 0;
|
|
1252
|
+
const transitionState = newTransitionState();
|
|
1253
|
+
const printLine = (_id, _platform, line) => {
|
|
1254
|
+
if (!opts.json) console.error(line);
|
|
1255
|
+
};
|
|
1256
|
+
while (!BUILD_TERMINAL_STATUSES.has(status)) {
|
|
1257
|
+
try {
|
|
1258
|
+
const logs = await deps.getCloudBuildLogs(id, offset);
|
|
1259
|
+
printTransition(id, "", logs.status, transitionState, opts.json, printLine);
|
|
1260
|
+
if (logs.content) {
|
|
1261
|
+
if (opts.json) {
|
|
1262
|
+
console.log(JSON.stringify({ event: "build_log", id, content: logs.content }));
|
|
1263
|
+
} else {
|
|
1264
|
+
process.stdout.write(logs.content);
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
offset = logs.offset;
|
|
1268
|
+
status = logs.status;
|
|
1269
|
+
consecutiveFailures = 0;
|
|
1270
|
+
} catch (err) {
|
|
1271
|
+
if (err instanceof NotFoundError) throw err;
|
|
1272
|
+
consecutiveFailures++;
|
|
1273
|
+
if (consecutiveFailures >= MAX_CONSECUTIVE_POLL_FAILURES) {
|
|
1274
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1275
|
+
throw new Error(
|
|
1276
|
+
`cloud build poll failed ${consecutiveFailures} times in a row, giving up: ${message}
|
|
1277
|
+
The build may still be running server-side \u2014 resume watching it with \`vibeview builds logs ${id} --follow\`.`
|
|
1278
|
+
);
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
if (!BUILD_TERMINAL_STATUSES.has(status)) {
|
|
1282
|
+
await new Promise((r) => setTimeout(r, pollIntervalMs));
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
const fallback = {
|
|
1286
|
+
id,
|
|
1287
|
+
platform: "",
|
|
1288
|
+
status,
|
|
1289
|
+
app_public_id: null,
|
|
1290
|
+
created_at: null,
|
|
1291
|
+
started_at: null,
|
|
1292
|
+
finished_at: null,
|
|
1293
|
+
cost_micro: 0,
|
|
1294
|
+
used_free_quota: false,
|
|
1295
|
+
error_summary: null,
|
|
1296
|
+
app_build_id: null,
|
|
1297
|
+
timeout_s: 0
|
|
1298
|
+
};
|
|
1299
|
+
const final = await fetchFinalSummary(id, fallback, deps);
|
|
1300
|
+
if (opts.json) {
|
|
1301
|
+
console.log(
|
|
1302
|
+
JSON.stringify({
|
|
1303
|
+
event: "build_finished",
|
|
1304
|
+
id: final.id,
|
|
1305
|
+
status: final.status,
|
|
1306
|
+
cost_micro: final.cost_micro,
|
|
1307
|
+
error_summary: final.error_summary
|
|
1308
|
+
})
|
|
1309
|
+
);
|
|
1310
|
+
} else {
|
|
1311
|
+
printOutcomeLine(final, false);
|
|
1312
|
+
}
|
|
1313
|
+
return final;
|
|
1314
|
+
}
|
|
1315
|
+
async function buildsDownload(id, opts, deps) {
|
|
1316
|
+
const { token, baseUrl } = resolveAuth(opts);
|
|
1317
|
+
const client = new ApiClient(baseUrl, token);
|
|
1318
|
+
const d = deps ?? defaultDeps(client);
|
|
1319
|
+
const destDir = opts.out ?? process.cwd();
|
|
1320
|
+
try {
|
|
1321
|
+
const result = await d.downloadCloudBuildArtifact(id, destDir);
|
|
1322
|
+
if (opts.json) {
|
|
1323
|
+
console.log(JSON.stringify(result));
|
|
1324
|
+
} else {
|
|
1325
|
+
console.log(chalk11.green(`\u2713 saved ${result.path}`));
|
|
1326
|
+
}
|
|
1327
|
+
process.exitCode = 0;
|
|
1328
|
+
} catch (err) {
|
|
1329
|
+
printError(opts, err);
|
|
1330
|
+
process.exitCode = 1;
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
async function buildsCancel(id, opts, deps) {
|
|
1334
|
+
const { token, baseUrl } = resolveAuth(opts);
|
|
1335
|
+
const client = new ApiClient(baseUrl, token);
|
|
1336
|
+
const d = deps ?? defaultDeps(client);
|
|
1337
|
+
try {
|
|
1338
|
+
const build = await d.cancelCloudBuild(id);
|
|
1339
|
+
if (opts.json) {
|
|
1340
|
+
console.log(JSON.stringify(build));
|
|
1341
|
+
} else {
|
|
1342
|
+
console.log(`${chalk11.cyan(build.id)} ${build.status}`);
|
|
1343
|
+
}
|
|
1344
|
+
process.exitCode = 0;
|
|
1345
|
+
} catch (err) {
|
|
1346
|
+
printError(opts, err);
|
|
1347
|
+
process.exitCode = 1;
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
function printError(opts, err) {
|
|
1351
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1352
|
+
if (opts.json) console.log(JSON.stringify({ event: "error", message }));
|
|
1353
|
+
console.error(chalk11.red(`error: ${message}`));
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
// src/lib/formatters/devices.ts
|
|
1357
|
+
import chalk12 from "chalk";
|
|
1358
|
+
var HEADERS2 = ["DEVICE_ID", "NAME", "TYPE", "CATEGORY", "OS", "STATUS"];
|
|
1140
1359
|
function formatDevicesHuman(devices) {
|
|
1141
1360
|
if (devices.length === 0) {
|
|
1142
1361
|
return "No devices registered.\n";
|
|
@@ -1149,11 +1368,11 @@ function formatDevicesHuman(devices) {
|
|
|
1149
1368
|
d.os_version,
|
|
1150
1369
|
d.status
|
|
1151
1370
|
]);
|
|
1152
|
-
const widths =
|
|
1371
|
+
const widths = HEADERS2.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
1153
1372
|
const fmt = (cells) => cells.map((c, i) => c.padEnd(widths[i])).join(" ").trimEnd();
|
|
1154
|
-
const lines = [
|
|
1373
|
+
const lines = [chalk12.bold(fmt(HEADERS2))];
|
|
1155
1374
|
for (const row of rows.sort(rowSort)) {
|
|
1156
|
-
const statusColored = row[5] === "available" ?
|
|
1375
|
+
const statusColored = row[5] === "available" ? chalk12.green(row[5]) : row[5] === "busy" ? chalk12.yellow(row[5]) : chalk12.dim(row[5]);
|
|
1157
1376
|
lines.push(fmt([row[0], row[1], row[2], row[3], row[4], statusColored]));
|
|
1158
1377
|
}
|
|
1159
1378
|
return lines.join("\n") + "\n";
|
|
@@ -1243,7 +1462,7 @@ import { dirname, join } from "path";
|
|
|
1243
1462
|
import { fileURLToPath } from "url";
|
|
1244
1463
|
import { spawnSync } from "child_process";
|
|
1245
1464
|
import { confirm } from "@inquirer/prompts";
|
|
1246
|
-
import
|
|
1465
|
+
import chalk13 from "chalk";
|
|
1247
1466
|
function resolveShippedSkillPath(fromDir) {
|
|
1248
1467
|
for (const rel of ["..", join("..", "..")]) {
|
|
1249
1468
|
const candidate = join(fromDir, rel, "skills", "vibeview-agent", "SKILL.md");
|
|
@@ -1271,23 +1490,23 @@ function registerMcp() {
|
|
|
1271
1490
|
encoding: "utf8"
|
|
1272
1491
|
});
|
|
1273
1492
|
if (res.status === 0) {
|
|
1274
|
-
console.log(
|
|
1493
|
+
console.log(chalk13.green("\u2713 MCP server registered with Claude Code (claude mcp add vibeview)"));
|
|
1275
1494
|
return;
|
|
1276
1495
|
}
|
|
1277
1496
|
const out = `${res.stderr ?? ""}${res.stdout ?? ""}`;
|
|
1278
1497
|
if (out.includes("already exists")) {
|
|
1279
|
-
console.log(
|
|
1498
|
+
console.log(chalk13.dim("MCP server already registered with Claude Code \u2014 nothing to do"));
|
|
1280
1499
|
return;
|
|
1281
1500
|
}
|
|
1282
|
-
console.error(
|
|
1283
|
-
console.error(
|
|
1501
|
+
console.error(chalk13.yellow(`\u26A0 could not register the MCP server: ${out.trim() || res.error}`));
|
|
1502
|
+
console.error(chalk13.yellow(" register it manually: claude mcp add vibeview -- vibeview mcp"));
|
|
1284
1503
|
}
|
|
1285
1504
|
async function agentSetup(opts) {
|
|
1286
1505
|
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
1287
1506
|
const shipped = resolveShippedSkillPath(moduleDir);
|
|
1288
1507
|
if (!shipped) {
|
|
1289
1508
|
console.error(
|
|
1290
|
-
|
|
1509
|
+
chalk13.red(
|
|
1291
1510
|
"error: could not find the packaged skill (skills/vibeview-agent/SKILL.md) \u2014 is this a complete install of the vibeview package?"
|
|
1292
1511
|
)
|
|
1293
1512
|
);
|
|
@@ -1296,9 +1515,9 @@ async function agentSetup(opts) {
|
|
|
1296
1515
|
}
|
|
1297
1516
|
const targetDir = skillInstallDir(opts.project ?? false, process.cwd(), homedir());
|
|
1298
1517
|
const result = installSkill(shipped, targetDir);
|
|
1299
|
-
console.log(
|
|
1518
|
+
console.log(chalk13.green(`\u2713 vibeview-agent skill ${result}: ${join(targetDir, "SKILL.md")}`));
|
|
1300
1519
|
console.log(
|
|
1301
|
-
|
|
1520
|
+
chalk13.dim(
|
|
1302
1521
|
opts.project ? " Claude Code discovers it automatically for anyone working in this project." : " Claude Code discovers it automatically in every project on this machine."
|
|
1303
1522
|
)
|
|
1304
1523
|
);
|
|
@@ -1306,7 +1525,7 @@ async function agentSetup(opts) {
|
|
|
1306
1525
|
if (!claudeCliAvailable()) {
|
|
1307
1526
|
if (opts.mcp === true) {
|
|
1308
1527
|
console.error(
|
|
1309
|
-
|
|
1528
|
+
chalk13.yellow(
|
|
1310
1529
|
"\u26A0 claude CLI not found \u2014 install Claude Code, then run: claude mcp add vibeview -- vibeview mcp"
|
|
1311
1530
|
)
|
|
1312
1531
|
);
|
|
@@ -1320,7 +1539,7 @@ async function agentSetup(opts) {
|
|
|
1320
1539
|
}
|
|
1321
1540
|
console.log(
|
|
1322
1541
|
`
|
|
1323
|
-
Suggested line for your project's ${
|
|
1542
|
+
Suggested line for your project's ${chalk13.bold("CLAUDE.md")} (or ${chalk13.bold(
|
|
1324
1543
|
"AGENTS.md"
|
|
1325
1544
|
)} for other agents):
|
|
1326
1545
|
`
|
|
@@ -1328,7 +1547,7 @@ Suggested line for your project's ${chalk11.bold("CLAUDE.md")} (or ${chalk11.bol
|
|
|
1328
1547
|
console.log(` ${CLAUDE_MD_SUGGESTION}
|
|
1329
1548
|
`);
|
|
1330
1549
|
console.log(
|
|
1331
|
-
|
|
1550
|
+
chalk13.dim(
|
|
1332
1551
|
"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"
|
|
1333
1552
|
)
|
|
1334
1553
|
);
|
|
@@ -1478,14 +1697,27 @@ program.command("dev").description(
|
|
|
1478
1697
|
).action((opts) => dev(opts));
|
|
1479
1698
|
program.command("build").description("Run your configured build command and upload the result (see vibeview.json)").option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").option(
|
|
1480
1699
|
"--platform <platform>",
|
|
1481
|
-
|
|
1482
|
-
).option(
|
|
1700
|
+
'ios, android, tvos, androidtv, or roku (default: vibeview.json defaultPlatform); with --cloud, "all" targets every configured platform with a build block'
|
|
1701
|
+
).option(
|
|
1702
|
+
"--cloud",
|
|
1703
|
+
"Build in the cloud instead of locally (uploads your project source, honoring .gitignore)",
|
|
1704
|
+
false
|
|
1705
|
+
).option("--no-wait", "--cloud only: print build ids and exit without waiting for completion").option(
|
|
1706
|
+
"--download",
|
|
1707
|
+
"--cloud only: download the artifact of each succeeded build (implies wait)",
|
|
1708
|
+
false
|
|
1709
|
+
).option("--json", "Print the result as one JSON line (NDJSON events with --cloud)", false).action((opts) => buildCommand(opts));
|
|
1710
|
+
var builds = program.command("builds").description("Manage cloud builds (see `vibeview build --cloud`)");
|
|
1711
|
+
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
|
+
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
|
+
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
|
+
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));
|
|
1483
1715
|
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));
|
|
1484
1716
|
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));
|
|
1485
1717
|
program.command("mcp").description(
|
|
1486
1718
|
"Run an MCP stdio server exposing the agent verbs (tap, swipe, type, ...) plus dev_start/dev_stop as tools"
|
|
1487
1719
|
).option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").action(async (opts) => {
|
|
1488
|
-
const { runMcpServer } = await import("./server-
|
|
1720
|
+
const { runMcpServer } = await import("./server-A44Z2HUK.js");
|
|
1489
1721
|
await runMcpServer(opts);
|
|
1490
1722
|
});
|
|
1491
1723
|
program.command("agent-setup").description(
|
|
@@ -11,11 +11,11 @@ import {
|
|
|
11
11
|
readDevState,
|
|
12
12
|
renderEnvelope,
|
|
13
13
|
startDevSession
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-NFFLJNUI.js";
|
|
15
15
|
import {
|
|
16
16
|
ApiClient,
|
|
17
17
|
resolveAuth
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-OHRTLUBD.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-G5SEYBTU.js");
|
|
243
243
|
const { result: result2 } = await uploadAppBuild(f, { ...deps.auth, name: n });
|
|
244
244
|
return result2;
|
|
245
245
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vibeview",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.1",
|
|
4
4
|
"description": "VibeView CLI -- run mobile tests from CI/CD pipelines",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
30
30
|
"chalk": "^5.0.0",
|
|
31
31
|
"commander": "^14.0.0",
|
|
32
|
+
"ignore": "^7.0.0",
|
|
32
33
|
"open": "^11.0.0",
|
|
33
34
|
"ora": "^9.0.0",
|
|
34
35
|
"ws": "^8.18.0",
|