vibeview 0.23.0 → 0.25.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.
@@ -5,22 +5,26 @@ import {
5
5
  PLATFORM_DEVICE,
6
6
  PLATFORM_NEEDS_METRO,
7
7
  ROKU_LIVENESS_INTERVAL_MS,
8
+ buildRows,
8
9
  buildToolCall,
10
+ checkDevBuild,
9
11
  checkMetro,
10
12
  createRokuPush,
11
13
  decodeScreenshotResult,
12
14
  filterDeviceModels,
15
+ formatBuildRowsHuman,
13
16
  formatDeviceModelsHuman,
14
17
  isDevPlatform,
15
18
  loadDevConfig,
19
+ parseBuildId,
16
20
  readDevState,
17
21
  renderEnvelope,
18
22
  startDevSession
19
- } from "./chunk-E2LZSLRF.js";
23
+ } from "./chunk-GEKYHEWU.js";
20
24
  import {
21
25
  ApiClient,
22
26
  resolveAuth
23
- } from "./chunk-PSFXVUHG.js";
27
+ } from "./chunk-RFFCCPGZ.js";
24
28
 
25
29
  // src/mcp/server.ts
26
30
  import { writeFileSync } from "fs";
@@ -84,10 +88,32 @@ var DEV_START_DEF = {
84
88
  model: {
85
89
  type: "string",
86
90
  description: 'Run on this exact device model, e.g. "iPhone Duo" for the foldable. Names come from list_device_models. Omit for a default phone (or TV on TV platforms).'
91
+ },
92
+ build_id: {
93
+ type: "number",
94
+ description: 'Run this uploaded build of the app instead of its newest debug build. Must be a debug build (kind "debug" in list_builds) for the platform \u2014 a release build or one with an embedded JS bundle is refused. Not for roku.'
87
95
  }
88
96
  },
89
97
  required: ["platform"]
90
98
  };
99
+ var LIST_BUILDS_DEF = {
100
+ name: "list_builds",
101
+ description: "List an app's uploaded builds, newest first: id, version, build number, platform, kind (debug/release/unknown), upload time and note. Pass a debug build's id to dev_start as build_id.",
102
+ mutating: false,
103
+ properties: {
104
+ app: { type: "string", description: "App id (public_id, from upload_app or list-apps)." }
105
+ },
106
+ required: ["app"]
107
+ };
108
+ var STOP_SESSION_DEF = {
109
+ name: "stop_session",
110
+ description: "Stop any session by id \u2014 e.g. one created through the API, the sandbox, or a collaboration link. For the session dev_start started, dev_stop does the same without an id.",
111
+ mutating: true,
112
+ properties: {
113
+ session_id: { type: "string", description: "The session to stop." }
114
+ },
115
+ required: ["session_id"]
116
+ };
91
117
  var LIST_DEVICE_MODELS_DEF = {
92
118
  name: "list_device_models",
93
119
  description: 'List the device models you can start right now (model, OS version, platform, category, and form_factor "foldable" for a device that folds, like the iPhone Duo). Pass a model name to dev_start to run on it.',
@@ -138,7 +164,9 @@ function buildToolDefinitions() {
138
164
  DEV_STOP_DEF,
139
165
  DEV_RELOAD_DEF,
140
166
  UPLOAD_APP_DEF,
141
- LIST_DEVICE_MODELS_DEF
167
+ LIST_DEVICE_MODELS_DEF,
168
+ LIST_BUILDS_DEF,
169
+ STOP_SESSION_DEF
142
170
  ];
143
171
  }
144
172
  function textResult(text, structuredContent, isError = false) {
@@ -166,6 +194,12 @@ async function handleToolCall(toolName, args, deps) {
166
194
  if (toolName === "list_device_models") {
167
195
  return handleListDeviceModels(args, deps);
168
196
  }
197
+ if (toolName === "list_builds") {
198
+ return handleListBuilds(args, deps);
199
+ }
200
+ if (toolName === "stop_session") {
201
+ return handleStopSession(args, deps);
202
+ }
169
203
  const cmd = findCommand(toolName);
170
204
  if (!cmd) {
171
205
  return errorResult(`Unknown tool: ${toolName}`);
@@ -245,12 +279,23 @@ async function handleDevStart(args, deps) {
245
279
  `dev_start: platform must be one of ${MCP_DEV_PLATFORMS.join(", ")}, got "${String(platform)}"`
246
280
  );
247
281
  }
282
+ let buildId;
283
+ if (args.build_id !== void 0 && args.build_id !== null) {
284
+ try {
285
+ buildId = parseBuildId(args.build_id);
286
+ } catch {
287
+ return errorResult(
288
+ `dev_start: build_id must be a positive build id (see list_builds), got "${String(args.build_id)}"`
289
+ );
290
+ }
291
+ }
248
292
  try {
249
293
  const result = await deps.devSession.start({
250
294
  platform,
251
295
  app: typeof args.app === "string" ? args.app : void 0,
252
296
  metroPort: typeof args.metro_port === "number" ? args.metro_port : void 0,
253
- model: typeof args.model === "string" && args.model.trim() ? args.model : void 0
297
+ model: typeof args.model === "string" && args.model.trim() ? args.model : void 0,
298
+ ...buildId !== void 0 ? { buildId } : {}
254
299
  });
255
300
  const payload = { session_id: result.sessionId, page_url: result.pageUrl };
256
301
  return textResult(`dev session started: ${payload.session_id}
@@ -271,6 +316,36 @@ async function handleListDeviceModels(args, deps) {
271
316
  }
272
317
  return textResult(formatDeviceModelsHuman(models).trimEnd(), { models });
273
318
  }
319
+ async function handleListBuilds(args, deps) {
320
+ const appId = typeof args.app === "string" ? args.app.trim() : "";
321
+ if (!appId) return errorResult("list_builds: `app` is required \u2014 an app id from list-apps");
322
+ const getApp = deps.getApp ?? ((id) => new ApiClient(deps.auth.baseUrl, deps.auth.token).getApp(id));
323
+ try {
324
+ const app = await getApp(appId);
325
+ const builds = buildRows(app);
326
+ return textResult(formatBuildRowsHuman(app, builds).trimEnd(), {
327
+ app_id: app.public_id,
328
+ builds
329
+ });
330
+ } catch (err) {
331
+ return errorResult(`error: ${err instanceof Error ? err.message : String(err)}`);
332
+ }
333
+ }
334
+ async function handleStopSession(args, deps) {
335
+ const sessionId = typeof args.session_id === "string" ? args.session_id.trim() : "";
336
+ if (!sessionId) return errorResult("stop_session: `session_id` is required");
337
+ const stop = deps.stopSession ?? ((id) => new ApiClient(deps.auth.baseUrl, deps.auth.token).stopSession(id));
338
+ try {
339
+ if (deps.devSession.current()?.sessionId === sessionId) {
340
+ await deps.devSession.stop();
341
+ } else {
342
+ await stop(sessionId);
343
+ }
344
+ } catch (err) {
345
+ return errorResult(`error: ${err instanceof Error ? err.message : String(err)}`);
346
+ }
347
+ return textResult(`session stopped: ${sessionId}`, { session_id: sessionId, stopped: true });
348
+ }
274
349
  async function handleDevReload(args, deps) {
275
350
  const requested = typeof args.session_id === "string" ? args.session_id : void 0;
276
351
  try {
@@ -301,7 +376,7 @@ async function handleUploadApp(args, deps) {
301
376
  }
302
377
  const name = typeof args.name === "string" && args.name ? args.name : void 0;
303
378
  const upload = deps.uploadApp ?? (async (f, n) => {
304
- const { uploadAppBuild } = await import("./upload-app-ZAKAOJKE.js");
379
+ const { uploadAppBuild } = await import("./upload-app-E32EZXYQ.js");
305
380
  const { result: result2 } = await uploadAppBuild(f, { ...deps.auth, name: n });
306
381
  return result2;
307
382
  });
@@ -319,11 +394,17 @@ async function handleUploadApp(args, deps) {
319
394
  package_name: result.package_name,
320
395
  platform: result.platform,
321
396
  is_new: isNew,
397
+ build_id: result.build_id ?? null,
398
+ version: result.version ?? null,
399
+ build_number: result.build_number ?? null,
322
400
  warnings
323
401
  };
324
402
  const lines = [
325
403
  `${isNew ? "New app created" : "Build added to existing app"}: ${result.name}`,
326
404
  `app_id: ${result.public_id} [${result.platform}] ${result.package_name}`,
405
+ ...result.build_id != null ? [
406
+ `build_id: ${result.build_id}${result.version ? ` version ${result.version}${result.build_number ? ` (${result.build_number})` : ""}` : ""}`
407
+ ] : [],
327
408
  ...warnings.map((w) => `warning: ${w}`)
328
409
  ];
329
410
  return textResult(lines.join("\n"), payload);
@@ -375,6 +456,13 @@ function createDevSessionController(auth, webUrl) {
375
456
  );
376
457
  }
377
458
  }
459
+ if (cfg.buildId !== void 0) {
460
+ checkDevBuild(
461
+ await new ApiClient(auth.baseUrl, auth.token).getApp(appId),
462
+ cfg.buildId,
463
+ cfg.platform
464
+ );
465
+ }
378
466
  const isTv = PLATFORM_DEVICE[cfg.platform].tv;
379
467
  platform = cfg.platform;
380
468
  handle = await startDevSession({
@@ -386,6 +474,7 @@ function createDevSessionController(auth, webUrl) {
386
474
  deviceCategory: isTv ? "tv" : entry?.deviceCategory ?? "phone",
387
475
  // A named model decides the category itself (startDevSession).
388
476
  model: cfg.model,
477
+ buildId: cfg.buildId,
389
478
  metroPort,
390
479
  open: false
391
480
  });
@@ -1,9 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ describeBuild,
3
4
  uploadApp,
4
5
  uploadAppBuild
5
- } from "./chunk-PSFXVUHG.js";
6
+ } from "./chunk-RFFCCPGZ.js";
6
7
  export {
8
+ describeBuild,
7
9
  uploadApp,
8
10
  uploadAppBuild
9
11
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibeview",
3
- "version": "0.23.0",
3
+ "version": "0.25.0",
4
4
  "description": "VibeView CLI -- run mobile tests from CI/CD pipelines",
5
5
  "mcpName": "io.github.vibeview/vibeview",
6
6
  "type": "module",
@@ -214,6 +214,11 @@ streaming minutes even when idle:
214
214
  ```bash
215
215
  vibeview dev-stop
216
216
  ```
217
+ A session you did not start with `dev` (one created through the API, the
218
+ sandbox, or a collaboration link) is stopped by id:
219
+ ```bash
220
+ vibeview stop <session-id>
221
+ ```
217
222
 
218
223
  ## 6. Command reference
219
224
 
@@ -250,9 +255,12 @@ Dev-loop lifecycle commands (not registry verbs, but needed for every run):
250
255
  | Command | Purpose |
251
256
  |---------|---------|
252
257
  | `dev --detach --json` | Start a device + Metro-tunnel session in the background, emitting `session_ready`/`warning`/`error` JSON events on stdout. Add `--model "<name>"` to pick an exact device model. |
258
+ | `dev --build <id>` | Run that uploaded build instead of the app's newest debug build (combine with `--app`, `--platform`, `--model`, `--detach --json`). Only a debug build (kind `debug` in `list-builds`) is accepted; a release build or one with an embedded JS bundle is refused. Not on Roku. |
259
+ | `list-builds <app>` | List an app's uploaded builds, newest first: id, version (build number), platform, kind (`debug`/`release`/`unknown`), upload time, note. `--json` prints an array. |
253
260
  | `list-devices --models` | List the device models you can start (the names `--model` takes); foldables are marked `foldable`. |
254
261
  | `dev-status` | Check on the current project's detached dev session. |
255
262
  | `dev-stop` | End the current project's detached dev session. |
263
+ | `vibeview stop <session-id>` | End any session by id — one created through the API, the sandbox, or a collaboration link. |
256
264
  | `dev-reload` | Roku only: re-package, upload and restart the channel in the detached session (the `r` key of the foreground loop). |
257
265
 
258
266
  ## 7. If you're calling VibeView over MCP
@@ -271,9 +279,11 @@ steps the CLI does with plain shell commands:
271
279
  | MCP tool | Args | Purpose |
272
280
  |----------|------|---------|
273
281
  | `upload_app` | `file` (required path), `name` | Upload a debug build. The MCP equivalent of `vibeview upload-app`, so step 1 of the loop needs no shell. Returns the `app_id` to pass to `dev_start`. |
274
- | `dev_start` | `platform` (required: `ios`/`android`/`tvos`/`androidtv`/`roku`), `app`, `metro_port`, `model` | Start a dev-loop session held open by the MCP server. The MCP equivalent of `vibeview dev --detach`; `model` picks an exact device model (e.g. `"iPhone Duo"`). Returns `page_url` — relay it to the developer immediately, same as step 5 of the loop. |
282
+ | `dev_start` | `platform` (required: `ios`/`android`/`tvos`/`androidtv`/`roku`), `app`, `metro_port`, `model`, `build_id` | Start a dev-loop session held open by the MCP server. The MCP equivalent of `vibeview dev --detach`; `model` picks an exact device model (e.g. `"iPhone Duo"`); `build_id` runs that debug build instead of the newest one (see `list_builds`). Returns `page_url` — relay it to the developer immediately, same as step 5 of the loop. |
275
283
  | `list_device_models` | `device_type` | List the device models you can start (model, OS, platform, category, `foldable` marker). The MCP equivalent of `vibeview list-devices --models`. |
284
+ | `list_builds` | `app` (required) | List an app's uploaded builds with their kind (`debug`/`release`/`unknown`). The MCP equivalent of `vibeview list-builds`. |
276
285
  | `dev_stop` | — | Stop the session `dev_start` started. The MCP equivalent of `vibeview dev-stop`. |
286
+ | `stop_session` | `session_id` (required) | Stop any session by id. The MCP equivalent of `vibeview stop <session-id>`. |
277
287
 
278
288
  **`dev_start` is stateful — it changes the default session for every later
279
289
  call.** Once it succeeds, any tool called without an explicit `session_id`