vibeview 0.24.0 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/{chunk-MVNMGB6I.js → chunk-JK3T3AMW.js} +327 -184
- package/dist/{chunk-2WNJYNDW.js → chunk-RFFCCPGZ.js} +5 -0
- package/dist/cli.js +178 -126
- package/dist/{server-KIJCEN6T.js → server-KYDTXPRZ.js} +92 -8
- package/dist/{upload-app-BXXKH65S.js → upload-app-E32EZXYQ.js} +1 -1
- package/package.json +1 -1
- package/skills/vibeview-agent/SKILL.md +40 -14
|
@@ -5,22 +5,27 @@ 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,
|
|
20
|
+
platformWithArticle,
|
|
16
21
|
readDevState,
|
|
17
22
|
renderEnvelope,
|
|
18
23
|
startDevSession
|
|
19
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-JK3T3AMW.js";
|
|
20
25
|
import {
|
|
21
26
|
ApiClient,
|
|
22
27
|
resolveAuth
|
|
23
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-RFFCCPGZ.js";
|
|
24
29
|
|
|
25
30
|
// src/mcp/server.ts
|
|
26
31
|
import { writeFileSync } from "fs";
|
|
@@ -83,14 +88,36 @@ var DEV_START_DEF = {
|
|
|
83
88
|
},
|
|
84
89
|
model: {
|
|
85
90
|
type: "string",
|
|
86
|
-
description: 'Run on this exact device model, e.g. "iPhone Duo" for
|
|
91
|
+
description: 'Run on this exact device model, e.g. "iPhone Duo" or "Pixel 9 Pro Fold" for a foldable (any model list_device_models marks form_factor "foldable"). Names come from list_device_models. Omit for a default phone (or TV on TV platforms).'
|
|
92
|
+
},
|
|
93
|
+
build_id: {
|
|
94
|
+
type: "number",
|
|
95
|
+
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
96
|
}
|
|
88
97
|
},
|
|
89
98
|
required: ["platform"]
|
|
90
99
|
};
|
|
100
|
+
var LIST_BUILDS_DEF = {
|
|
101
|
+
name: "list_builds",
|
|
102
|
+
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.",
|
|
103
|
+
mutating: false,
|
|
104
|
+
properties: {
|
|
105
|
+
app: { type: "string", description: "App id (public_id, from upload_app or list-apps)." }
|
|
106
|
+
},
|
|
107
|
+
required: ["app"]
|
|
108
|
+
};
|
|
109
|
+
var STOP_SESSION_DEF = {
|
|
110
|
+
name: "stop_session",
|
|
111
|
+
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.",
|
|
112
|
+
mutating: true,
|
|
113
|
+
properties: {
|
|
114
|
+
session_id: { type: "string", description: "The session to stop." }
|
|
115
|
+
},
|
|
116
|
+
required: ["session_id"]
|
|
117
|
+
};
|
|
91
118
|
var LIST_DEVICE_MODELS_DEF = {
|
|
92
119
|
name: "list_device_models",
|
|
93
|
-
description: 'List the device models you can start right now (model, OS version, platform, category, and form_factor "foldable" for
|
|
120
|
+
description: 'List the device models you can start right now (model, OS version, platform, category, and form_factor "foldable" for any device with a hinge \u2014 the iPhone Duo, Android foldables such as the Pixel 9 Pro Fold). Pass a model name to dev_start to run on it.',
|
|
94
121
|
mutating: false,
|
|
95
122
|
properties: {
|
|
96
123
|
device_type: {
|
|
@@ -138,7 +165,9 @@ function buildToolDefinitions() {
|
|
|
138
165
|
DEV_STOP_DEF,
|
|
139
166
|
DEV_RELOAD_DEF,
|
|
140
167
|
UPLOAD_APP_DEF,
|
|
141
|
-
LIST_DEVICE_MODELS_DEF
|
|
168
|
+
LIST_DEVICE_MODELS_DEF,
|
|
169
|
+
LIST_BUILDS_DEF,
|
|
170
|
+
STOP_SESSION_DEF
|
|
142
171
|
];
|
|
143
172
|
}
|
|
144
173
|
function textResult(text, structuredContent, isError = false) {
|
|
@@ -166,6 +195,12 @@ async function handleToolCall(toolName, args, deps) {
|
|
|
166
195
|
if (toolName === "list_device_models") {
|
|
167
196
|
return handleListDeviceModels(args, deps);
|
|
168
197
|
}
|
|
198
|
+
if (toolName === "list_builds") {
|
|
199
|
+
return handleListBuilds(args, deps);
|
|
200
|
+
}
|
|
201
|
+
if (toolName === "stop_session") {
|
|
202
|
+
return handleStopSession(args, deps);
|
|
203
|
+
}
|
|
169
204
|
const cmd = findCommand(toolName);
|
|
170
205
|
if (!cmd) {
|
|
171
206
|
return errorResult(`Unknown tool: ${toolName}`);
|
|
@@ -245,12 +280,23 @@ async function handleDevStart(args, deps) {
|
|
|
245
280
|
`dev_start: platform must be one of ${MCP_DEV_PLATFORMS.join(", ")}, got "${String(platform)}"`
|
|
246
281
|
);
|
|
247
282
|
}
|
|
283
|
+
let buildId;
|
|
284
|
+
if (args.build_id !== void 0 && args.build_id !== null) {
|
|
285
|
+
try {
|
|
286
|
+
buildId = parseBuildId(args.build_id);
|
|
287
|
+
} catch {
|
|
288
|
+
return errorResult(
|
|
289
|
+
`dev_start: build_id must be a positive build id (see list_builds), got "${String(args.build_id)}"`
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
248
293
|
try {
|
|
249
294
|
const result = await deps.devSession.start({
|
|
250
295
|
platform,
|
|
251
296
|
app: typeof args.app === "string" ? args.app : void 0,
|
|
252
297
|
metroPort: typeof args.metro_port === "number" ? args.metro_port : void 0,
|
|
253
|
-
model: typeof args.model === "string" && args.model.trim() ? args.model : void 0
|
|
298
|
+
model: typeof args.model === "string" && args.model.trim() ? args.model : void 0,
|
|
299
|
+
...buildId !== void 0 ? { buildId } : {}
|
|
254
300
|
});
|
|
255
301
|
const payload = { session_id: result.sessionId, page_url: result.pageUrl };
|
|
256
302
|
return textResult(`dev session started: ${payload.session_id}
|
|
@@ -271,6 +317,36 @@ async function handleListDeviceModels(args, deps) {
|
|
|
271
317
|
}
|
|
272
318
|
return textResult(formatDeviceModelsHuman(models).trimEnd(), { models });
|
|
273
319
|
}
|
|
320
|
+
async function handleListBuilds(args, deps) {
|
|
321
|
+
const appId = typeof args.app === "string" ? args.app.trim() : "";
|
|
322
|
+
if (!appId) return errorResult("list_builds: `app` is required \u2014 an app id from list-apps");
|
|
323
|
+
const getApp = deps.getApp ?? ((id) => new ApiClient(deps.auth.baseUrl, deps.auth.token).getApp(id));
|
|
324
|
+
try {
|
|
325
|
+
const app = await getApp(appId);
|
|
326
|
+
const builds = buildRows(app);
|
|
327
|
+
return textResult(formatBuildRowsHuman(app, builds).trimEnd(), {
|
|
328
|
+
app_id: app.public_id,
|
|
329
|
+
builds
|
|
330
|
+
});
|
|
331
|
+
} catch (err) {
|
|
332
|
+
return errorResult(`error: ${err instanceof Error ? err.message : String(err)}`);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
async function handleStopSession(args, deps) {
|
|
336
|
+
const sessionId = typeof args.session_id === "string" ? args.session_id.trim() : "";
|
|
337
|
+
if (!sessionId) return errorResult("stop_session: `session_id` is required");
|
|
338
|
+
const stop = deps.stopSession ?? ((id) => new ApiClient(deps.auth.baseUrl, deps.auth.token).stopSession(id));
|
|
339
|
+
try {
|
|
340
|
+
if (deps.devSession.current()?.sessionId === sessionId) {
|
|
341
|
+
await deps.devSession.stop();
|
|
342
|
+
} else {
|
|
343
|
+
await stop(sessionId);
|
|
344
|
+
}
|
|
345
|
+
} catch (err) {
|
|
346
|
+
return errorResult(`error: ${err instanceof Error ? err.message : String(err)}`);
|
|
347
|
+
}
|
|
348
|
+
return textResult(`session stopped: ${sessionId}`, { session_id: sessionId, stopped: true });
|
|
349
|
+
}
|
|
274
350
|
async function handleDevReload(args, deps) {
|
|
275
351
|
const requested = typeof args.session_id === "string" ? args.session_id : void 0;
|
|
276
352
|
try {
|
|
@@ -301,7 +377,7 @@ async function handleUploadApp(args, deps) {
|
|
|
301
377
|
}
|
|
302
378
|
const name = typeof args.name === "string" && args.name ? args.name : void 0;
|
|
303
379
|
const upload = deps.uploadApp ?? (async (f, n) => {
|
|
304
|
-
const { uploadAppBuild } = await import("./upload-app-
|
|
380
|
+
const { uploadAppBuild } = await import("./upload-app-E32EZXYQ.js");
|
|
305
381
|
const { result: result2 } = await uploadAppBuild(f, { ...deps.auth, name: n });
|
|
306
382
|
return result2;
|
|
307
383
|
});
|
|
@@ -381,6 +457,13 @@ function createDevSessionController(auth, webUrl) {
|
|
|
381
457
|
);
|
|
382
458
|
}
|
|
383
459
|
}
|
|
460
|
+
if (cfg.buildId !== void 0) {
|
|
461
|
+
checkDevBuild(
|
|
462
|
+
await new ApiClient(auth.baseUrl, auth.token).getApp(appId),
|
|
463
|
+
cfg.buildId,
|
|
464
|
+
cfg.platform
|
|
465
|
+
);
|
|
466
|
+
}
|
|
384
467
|
const isTv = PLATFORM_DEVICE[cfg.platform].tv;
|
|
385
468
|
platform = cfg.platform;
|
|
386
469
|
handle = await startDevSession({
|
|
@@ -392,6 +475,7 @@ function createDevSessionController(auth, webUrl) {
|
|
|
392
475
|
deviceCategory: isTv ? "tv" : entry?.deviceCategory ?? "phone",
|
|
393
476
|
// A named model decides the category itself (startDevSession).
|
|
394
477
|
model: cfg.model,
|
|
478
|
+
buildId: cfg.buildId,
|
|
395
479
|
metroPort,
|
|
396
480
|
open: false
|
|
397
481
|
});
|
|
@@ -452,7 +536,7 @@ function createDevSessionController(auth, webUrl) {
|
|
|
452
536
|
}
|
|
453
537
|
if (target.platform !== "roku") {
|
|
454
538
|
throw new Error(
|
|
455
|
-
`dev_reload is for Roku channels;
|
|
539
|
+
`dev_reload is for Roku channels; ${platformWithArticle(target.platform)} session hot-reloads through Metro on its own`
|
|
456
540
|
);
|
|
457
541
|
}
|
|
458
542
|
if (!target.build) {
|
package/package.json
CHANGED
|
@@ -38,10 +38,17 @@ https://vibeview.io/docs/preparing-your-build.
|
|
|
38
38
|
```
|
|
39
39
|
2. Start Metro in the React Native project (`yarn start` or equivalent) and
|
|
40
40
|
leave it running.
|
|
41
|
-
3. Start a dev session in the background and get structured output
|
|
41
|
+
3. Start a dev session in the background and get structured output. No
|
|
42
|
+
`vibeview.json` yet in this project (first run here)? Pass `--platform`
|
|
43
|
+
and `--app` explicitly — non-interactive runs never prompt, so both must
|
|
44
|
+
be given up front (`--app` is `app_id` from step 1's upload output):
|
|
42
45
|
```bash
|
|
43
|
-
vibeview dev --detach --json
|
|
46
|
+
vibeview dev --platform ios --app <app_id> --detach --json
|
|
44
47
|
```
|
|
48
|
+
Once `vibeview.json` exists (this run or an earlier one), a bare
|
|
49
|
+
`vibeview dev --detach --json` reruns the same platform/app. A
|
|
50
|
+
`{"event":"error","code":"config_required",...}` line names exactly what's
|
|
51
|
+
still missing (usually `--platform` or `--app`) — pass it and retry.
|
|
45
52
|
4. Parse the `session_ready` line from stdout:
|
|
46
53
|
```json
|
|
47
54
|
{"event":"session_ready","session_id":"...","url":"...","pid":1234}
|
|
@@ -156,20 +163,29 @@ it rather than letting it appear unexplained in their `git status`.
|
|
|
156
163
|
(`dpad_up`/`dpad_down`/`dpad_left`/`dpad_right`/`dpad_center`), or move
|
|
157
164
|
focus directly with `tap-focused` (moves focus AND activates) / `focus`
|
|
158
165
|
(moves focus only, no activation).
|
|
159
|
-
- **
|
|
160
|
-
`
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
166
|
+
- **Foldables**: any model `vibeview list-devices --models` (MCP:
|
|
167
|
+
`list_device_models`) marks form factor `foldable` — the iPhone Duo,
|
|
168
|
+
Android foldables such as the Pixel 9 Pro Fold. Start on one by its exact
|
|
169
|
+
model name from that list, e.g.
|
|
170
|
+
`vibeview dev --detach --json --model "iPhone Duo"` or
|
|
171
|
+
`vibeview dev --platform android --detach --json --model "Pixel 9 Pro Fold"`
|
|
172
|
+
(MCP: `dev_start` with `model`). It starts closed, on the cover screen. Fold with a preset
|
|
173
|
+
or an exact hinge angle, and rotate:
|
|
164
174
|
```bash
|
|
165
175
|
vibeview set-posture open --session <id> # closed | partial | open
|
|
166
176
|
vibeview set-posture --angle 75 --session <id> # 0 (shut) to 180 (flat)
|
|
167
|
-
vibeview rotate --session <id> # quarter turn clockwise
|
|
168
|
-
vibeview rotate --degrees 270 --session <id> # quarter turn back (180: half)
|
|
177
|
+
vibeview rotate --session <id> # Duo: quarter turn clockwise
|
|
178
|
+
vibeview rotate --degrees 270 --session <id> # Duo: quarter turn back (180: half)
|
|
169
179
|
```
|
|
170
180
|
Each response says where the device ended up — posture, angle and which
|
|
171
|
-
screen is lit (`cover` or `inner`; the device decides — a small
|
|
172
|
-
keep the inner screen lit on the way down), or the new
|
|
181
|
+
screen is lit (`cover` or `inner`; the device decides — on the Duo a small
|
|
182
|
+
angle can keep the inner screen lit on the way down), or the new
|
|
183
|
+
orientation. The posture names come from each device's own angle ranges,
|
|
184
|
+
so trust the reported `posture` over the angle you asked for: the Duo
|
|
185
|
+
reads 120-169 as `partial`, 170 up as `open`, and 1-119 by direction of
|
|
186
|
+
travel; an Android foldable uses its own ranges (the Pixel 9 Pro Fold: below
|
|
187
|
+
30 `closed`, 30-149 `partial`, 150 up `open`) and its inner screen whenever
|
|
188
|
+
it is not closed. An Android foldable rotates like a phone. Folding
|
|
173
189
|
switches screens of different sizes and rotating turns the picture, so run
|
|
174
190
|
`ui-tree` afterwards — old refs are stale. On other phones and tablets
|
|
175
191
|
`rotate` toggles portrait/landscape (only 90 is accepted); TV devices don't
|
|
@@ -214,6 +230,11 @@ streaming minutes even when idle:
|
|
|
214
230
|
```bash
|
|
215
231
|
vibeview dev-stop
|
|
216
232
|
```
|
|
233
|
+
A session you did not start with `dev` (one created through the API, the
|
|
234
|
+
sandbox, or a collaboration link) is stopped by id:
|
|
235
|
+
```bash
|
|
236
|
+
vibeview stop <session-id>
|
|
237
|
+
```
|
|
217
238
|
|
|
218
239
|
## 6. Command reference
|
|
219
240
|
|
|
@@ -238,8 +259,8 @@ human-readable text.
|
|
|
238
259
|
| `clear-text` | — | Clear the text in the currently focused input field. |
|
|
239
260
|
| `press` | `<button>` | Press a device button or perform a system gesture (home, back, d-pad, etc). |
|
|
240
261
|
| `open-url` | `<url>` | Open a deep link or URL in the app under test. |
|
|
241
|
-
| `set-posture` | `<closed\|partial\|open>` or `--angle <0-180>` | Fold or unfold a foldable device (iPhone Duo) to a preset or an exact hinge angle — exactly one. Reports posture, angle and lit screen. Errors on a device without a hinge. |
|
|
242
|
-
| `rotate` | `[--degrees <90\|180\|270>]` | Rotate the device and report the new orientation. Phones/tablets toggle portrait/landscape (90 only);
|
|
262
|
+
| `set-posture` | `<closed\|partial\|open>` or `--angle <0-180>` | Fold or unfold a foldable device (form factor `foldable`: the iPhone Duo, Android foldables) to a preset or an exact hinge angle — exactly one. Reports posture, angle and lit screen. Errors on a device without a hinge. |
|
|
263
|
+
| `rotate` | `[--degrees <90\|180\|270>]` | Rotate the device and report the new orientation. Phones/tablets and Android foldables toggle portrait/landscape (90 only); the iPhone Duo turns a quarter clockwise by default. Not on TV. |
|
|
243
264
|
| `wait` | `[--ms <n>]` | Wait for a specified number of seconds before continuing (default: 2s). |
|
|
244
265
|
| `find` | `<text>` `[--below <text>] [--above <text>] [--near <text>]` | Find an element by text with optional spatial constraints. |
|
|
245
266
|
| `tap-focused` | `<ref>` | TV: move focus to an element and press SELECT in one step. |
|
|
@@ -250,9 +271,12 @@ Dev-loop lifecycle commands (not registry verbs, but needed for every run):
|
|
|
250
271
|
| Command | Purpose |
|
|
251
272
|
|---------|---------|
|
|
252
273
|
| `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. |
|
|
274
|
+
| `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. |
|
|
275
|
+
| `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
276
|
| `list-devices --models` | List the device models you can start (the names `--model` takes); foldables are marked `foldable`. |
|
|
254
277
|
| `dev-status` | Check on the current project's detached dev session. |
|
|
255
278
|
| `dev-stop` | End the current project's detached dev session. |
|
|
279
|
+
| `vibeview stop <session-id>` | End any session by id — one created through the API, the sandbox, or a collaboration link. |
|
|
256
280
|
| `dev-reload` | Roku only: re-package, upload and restart the channel in the detached session (the `r` key of the foreground loop). |
|
|
257
281
|
|
|
258
282
|
## 7. If you're calling VibeView over MCP
|
|
@@ -271,9 +295,11 @@ steps the CLI does with plain shell commands:
|
|
|
271
295
|
| MCP tool | Args | Purpose |
|
|
272
296
|
|----------|------|---------|
|
|
273
297
|
| `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. |
|
|
298
|
+
| `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
299
|
| `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`. |
|
|
300
|
+
| `list_builds` | `app` (required) | List an app's uploaded builds with their kind (`debug`/`release`/`unknown`). The MCP equivalent of `vibeview list-builds`. |
|
|
276
301
|
| `dev_stop` | — | Stop the session `dev_start` started. The MCP equivalent of `vibeview dev-stop`. |
|
|
302
|
+
| `stop_session` | `session_id` (required) | Stop any session by id. The MCP equivalent of `vibeview stop <session-id>`. |
|
|
277
303
|
|
|
278
304
|
**`dev_start` is stateful — it changes the default session for every later
|
|
279
305
|
call.** Once it succeeds, any tool called without an explicit `session_id`
|