vibeview 0.20.4 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-BBGXBKJQ.js → chunk-SHS5PCSS.js} +112 -47
- package/dist/{chunk-KON2FOEC.js → chunk-UGCGJ6IN.js} +7 -0
- package/dist/cli.js +205 -120
- package/dist/{server-WOIB6CDQ.js → server-6IJ44HAX.js} +126 -11
- package/dist/{upload-app-W6NOSJPA.js → upload-app-LJH3PETS.js} +1 -1
- package/package.json +1 -1
- package/skills/vibeview-agent/SKILL.md +20 -4
|
@@ -1,21 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
AGENT_COMMANDS,
|
|
4
|
+
DEV_PLATFORMS,
|
|
4
5
|
PLATFORM_DEVICE,
|
|
5
6
|
PLATFORM_NEEDS_METRO,
|
|
7
|
+
ROKU_LIVENESS_INTERVAL_MS,
|
|
6
8
|
buildToolCall,
|
|
7
9
|
checkMetro,
|
|
10
|
+
createRokuPush,
|
|
8
11
|
decodeScreenshotResult,
|
|
9
12
|
isDevPlatform,
|
|
10
13
|
loadDevConfig,
|
|
11
14
|
readDevState,
|
|
12
15
|
renderEnvelope,
|
|
13
16
|
startDevSession
|
|
14
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-SHS5PCSS.js";
|
|
15
18
|
import {
|
|
16
19
|
ApiClient,
|
|
17
20
|
resolveAuth
|
|
18
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-UGCGJ6IN.js";
|
|
19
22
|
|
|
20
23
|
// src/mcp/server.ts
|
|
21
24
|
import { writeFileSync } from "fs";
|
|
@@ -26,7 +29,7 @@ var SESSION_ID_PROPERTY = {
|
|
|
26
29
|
type: "string",
|
|
27
30
|
description: "Session ID to target. Optional: falls back to the MCP-server-held dev_start session, then this project's `vibeview dev --detach` state, if either is available."
|
|
28
31
|
};
|
|
29
|
-
var MCP_DEV_PLATFORMS =
|
|
32
|
+
var MCP_DEV_PLATFORMS = DEV_PLATFORMS;
|
|
30
33
|
function isMcpDevPlatform(p) {
|
|
31
34
|
return isDevPlatform(p) && MCP_DEV_PLATFORMS.includes(p);
|
|
32
35
|
}
|
|
@@ -86,9 +89,16 @@ var DEV_STOP_DEF = {
|
|
|
86
89
|
properties: { session_id: SESSION_ID_PROPERTY },
|
|
87
90
|
required: []
|
|
88
91
|
};
|
|
92
|
+
var DEV_RELOAD_DEF = {
|
|
93
|
+
name: "dev_reload",
|
|
94
|
+
description: "Roku only: re-package the channel with the build command from vibeview.json (platforms.roku.build), upload it, and reload it into the session dev_start is holding (or, with no held session, this project's `vibeview dev --detach` session) \u2014 the MCP equivalent of pressing `r` in `vibeview dev`. Returns once the device restarts the channel (a few seconds); call ui_tree afterwards. Other platforms hot-reload through Metro and need no call.",
|
|
95
|
+
mutating: true,
|
|
96
|
+
properties: { session_id: SESSION_ID_PROPERTY },
|
|
97
|
+
required: []
|
|
98
|
+
};
|
|
89
99
|
var UPLOAD_APP_DEF = {
|
|
90
100
|
name: "upload_app",
|
|
91
|
-
description: "Upload an app build (zipped simulator .app for iOS, .apk for Android) and return its app_id for dev_start. The MCP equivalent of `vibeview upload-app`.",
|
|
101
|
+
description: "Upload an app build (zipped simulator .app for iOS/tvOS, .apk for Android/Android TV, or a Roku channel .zip) and return its app_id for dev_start. The MCP equivalent of `vibeview upload-app`.",
|
|
92
102
|
mutating: true,
|
|
93
103
|
properties: {
|
|
94
104
|
file: {
|
|
@@ -103,7 +113,13 @@ var UPLOAD_APP_DEF = {
|
|
|
103
113
|
required: ["file"]
|
|
104
114
|
};
|
|
105
115
|
function buildToolDefinitions() {
|
|
106
|
-
return [
|
|
116
|
+
return [
|
|
117
|
+
...AGENT_COMMANDS.map(toolDefFromCommand),
|
|
118
|
+
DEV_START_DEF,
|
|
119
|
+
DEV_STOP_DEF,
|
|
120
|
+
DEV_RELOAD_DEF,
|
|
121
|
+
UPLOAD_APP_DEF
|
|
122
|
+
];
|
|
107
123
|
}
|
|
108
124
|
function textResult(text, structuredContent, isError = false) {
|
|
109
125
|
return { content: [{ type: "text", text }], structuredContent, isError };
|
|
@@ -121,6 +137,9 @@ async function handleToolCall(toolName, args, deps) {
|
|
|
121
137
|
if (toolName === "dev_stop") {
|
|
122
138
|
return handleDevStop(deps);
|
|
123
139
|
}
|
|
140
|
+
if (toolName === "dev_reload") {
|
|
141
|
+
return handleDevReload(args, deps);
|
|
142
|
+
}
|
|
124
143
|
if (toolName === "upload_app") {
|
|
125
144
|
return handleUploadApp(args, deps);
|
|
126
145
|
}
|
|
@@ -198,11 +217,6 @@ function optsFor(cmd, args) {
|
|
|
198
217
|
}
|
|
199
218
|
async function handleDevStart(args, deps) {
|
|
200
219
|
const platform = args.platform;
|
|
201
|
-
if (platform === "roku") {
|
|
202
|
-
return errorResult(
|
|
203
|
-
"dev_start does not support roku \u2014 the reload loop needs a foreground terminal; run `vibeview dev --platform roku` instead"
|
|
204
|
-
);
|
|
205
|
-
}
|
|
206
220
|
if (!isMcpDevPlatform(platform)) {
|
|
207
221
|
return errorResult(
|
|
208
222
|
`dev_start: platform must be one of ${MCP_DEV_PLATFORMS.join(", ")}, got "${String(platform)}"`
|
|
@@ -221,6 +235,18 @@ ${payload.page_url}`, payload);
|
|
|
221
235
|
return errorResult(`error: ${err instanceof Error ? err.message : String(err)}`);
|
|
222
236
|
}
|
|
223
237
|
}
|
|
238
|
+
async function handleDevReload(args, deps) {
|
|
239
|
+
const requested = typeof args.session_id === "string" ? args.session_id : void 0;
|
|
240
|
+
try {
|
|
241
|
+
const { sessionId, seconds } = await deps.devSession.reload(requested);
|
|
242
|
+
return textResult(`channel reloaded in ${seconds.toFixed(1)}s`, {
|
|
243
|
+
session_id: sessionId,
|
|
244
|
+
seconds
|
|
245
|
+
});
|
|
246
|
+
} catch (err) {
|
|
247
|
+
return errorResult(`error: ${err instanceof Error ? err.message : String(err)}`);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
224
250
|
async function handleDevStop(deps) {
|
|
225
251
|
const current = deps.devSession.current();
|
|
226
252
|
try {
|
|
@@ -239,7 +265,7 @@ async function handleUploadApp(args, deps) {
|
|
|
239
265
|
}
|
|
240
266
|
const name = typeof args.name === "string" && args.name ? args.name : void 0;
|
|
241
267
|
const upload = deps.uploadApp ?? (async (f, n) => {
|
|
242
|
-
const { uploadAppBuild } = await import("./upload-app-
|
|
268
|
+
const { uploadAppBuild } = await import("./upload-app-LJH3PETS.js");
|
|
243
269
|
const { result: result2 } = await uploadAppBuild(f, { ...deps.auth, name: n });
|
|
244
270
|
return result2;
|
|
245
271
|
});
|
|
@@ -268,6 +294,22 @@ async function handleUploadApp(args, deps) {
|
|
|
268
294
|
}
|
|
269
295
|
function createDevSessionController(auth, webUrl) {
|
|
270
296
|
let handle = null;
|
|
297
|
+
let platform = null;
|
|
298
|
+
let presence = null;
|
|
299
|
+
let reloading = null;
|
|
300
|
+
const stopPresence = () => {
|
|
301
|
+
if (presence) clearInterval(presence);
|
|
302
|
+
presence = null;
|
|
303
|
+
};
|
|
304
|
+
const readRokuBuild = () => {
|
|
305
|
+
try {
|
|
306
|
+
return loadDevConfig(process.cwd())?.platforms?.roku?.build ?? null;
|
|
307
|
+
} catch (err) {
|
|
308
|
+
throw new Error(
|
|
309
|
+
`vibeview.json could not be read: ${err instanceof Error ? err.message : String(err)}`
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
};
|
|
271
313
|
return {
|
|
272
314
|
async start(cfg) {
|
|
273
315
|
if (handle) {
|
|
@@ -298,6 +340,7 @@ function createDevSessionController(auth, webUrl) {
|
|
|
298
340
|
}
|
|
299
341
|
}
|
|
300
342
|
const isTv = PLATFORM_DEVICE[cfg.platform].tv;
|
|
343
|
+
platform = cfg.platform;
|
|
301
344
|
handle = await startDevSession({
|
|
302
345
|
token: auth.token,
|
|
303
346
|
baseUrl: auth.baseUrl,
|
|
@@ -308,16 +351,87 @@ function createDevSessionController(auth, webUrl) {
|
|
|
308
351
|
metroPort,
|
|
309
352
|
open: false
|
|
310
353
|
});
|
|
354
|
+
if (cfg.platform === "roku") {
|
|
355
|
+
const api = new ApiClient(auth.baseUrl, auth.token);
|
|
356
|
+
const sessionId = handle.sessionId;
|
|
357
|
+
presence = setInterval(() => {
|
|
358
|
+
api.touchSession(sessionId).catch(() => {
|
|
359
|
+
});
|
|
360
|
+
}, ROKU_LIVENESS_INTERVAL_MS);
|
|
361
|
+
presence.unref?.();
|
|
362
|
+
}
|
|
311
363
|
return handle;
|
|
312
364
|
},
|
|
313
365
|
async stop() {
|
|
366
|
+
stopPresence();
|
|
314
367
|
await handle?.stop();
|
|
315
368
|
handle = null;
|
|
369
|
+
platform = null;
|
|
316
370
|
},
|
|
317
371
|
current() {
|
|
318
372
|
return handle ? { sessionId: handle.sessionId, pageUrl: handle.pageUrl } : null;
|
|
373
|
+
},
|
|
374
|
+
async reload(requested) {
|
|
375
|
+
if (reloading) {
|
|
376
|
+
throw new Error(
|
|
377
|
+
"a reload is already in progress \u2014 wait for it to finish, then call dev_reload again"
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
reloading = reloadOnce(requested).finally(() => {
|
|
381
|
+
reloading = null;
|
|
382
|
+
});
|
|
383
|
+
return reloading;
|
|
319
384
|
}
|
|
320
385
|
};
|
|
386
|
+
async function reloadOnce(requested) {
|
|
387
|
+
{
|
|
388
|
+
let target;
|
|
389
|
+
if (handle) {
|
|
390
|
+
if (requested && requested !== handle.sessionId) {
|
|
391
|
+
throw new Error(
|
|
392
|
+
`dev_reload can only reload the session this server holds (${handle.sessionId}); got session_id "${requested}". Call dev_stop first, or omit session_id.`
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
target = {
|
|
396
|
+
sessionId: handle.sessionId,
|
|
397
|
+
platform: platform ?? "",
|
|
398
|
+
build: platform === "roku" ? readRokuBuild() : null
|
|
399
|
+
};
|
|
400
|
+
} else {
|
|
401
|
+
const state = readDevState(process.cwd());
|
|
402
|
+
if (!state || requested && requested !== state.session_id) {
|
|
403
|
+
throw new Error(
|
|
404
|
+
requested ? `session "${requested}" is neither held by dev_start nor this project's \`vibeview dev --detach\` session \u2014 dev_reload needs one of those two` : 'no dev session to reload \u2014 call dev_start with platform "roku" first'
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
target = { sessionId: state.session_id, platform: state.platform, build: readRokuBuild() };
|
|
408
|
+
}
|
|
409
|
+
if (target.platform !== "roku") {
|
|
410
|
+
throw new Error(
|
|
411
|
+
`dev_reload is for Roku channels; a ${target.platform} session hot-reloads through Metro on its own`
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
if (!target.build) {
|
|
415
|
+
throw new Error(
|
|
416
|
+
"no build config for roku \u2014 add platforms.roku.build ({ command, artifact }) to vibeview.json, or run `vibeview build --platform roku` once to set it up"
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
const started = Date.now();
|
|
420
|
+
const push = createRokuPush({
|
|
421
|
+
cwd: process.cwd(),
|
|
422
|
+
sessionId: target.sessionId,
|
|
423
|
+
build: target.build,
|
|
424
|
+
token: auth.token,
|
|
425
|
+
baseUrl: auth.baseUrl,
|
|
426
|
+
api: new ApiClient(auth.baseUrl, auth.token),
|
|
427
|
+
// Build output has no stdout to go to over MCP (stdout IS the
|
|
428
|
+
// protocol); stderr is the server's log.
|
|
429
|
+
log: (line) => console.error(line)
|
|
430
|
+
});
|
|
431
|
+
await push();
|
|
432
|
+
return { sessionId: target.sessionId, seconds: (Date.now() - started) / 1e3 };
|
|
433
|
+
}
|
|
434
|
+
}
|
|
321
435
|
}
|
|
322
436
|
function makeSessionResolver(devSession) {
|
|
323
437
|
return () => devSession.current()?.sessionId ?? readDevState(process.cwd())?.session_id;
|
|
@@ -373,6 +487,7 @@ async function runMcpServer(opts) {
|
|
|
373
487
|
}
|
|
374
488
|
export {
|
|
375
489
|
buildToolDefinitions,
|
|
490
|
+
createDevSessionController,
|
|
376
491
|
handleToolCall,
|
|
377
492
|
runMcpServer
|
|
378
493
|
};
|
package/package.json
CHANGED
|
@@ -5,8 +5,8 @@ description: Verify a React Native change on a real running app — install/laun
|
|
|
5
5
|
|
|
6
6
|
# VibeView agent control
|
|
7
7
|
|
|
8
|
-
Drive a live iOS
|
|
9
|
-
Native change actually works, not just that it compiles. Requires the
|
|
8
|
+
Drive a live iOS, Android, Apple TV or Android TV VibeView session from the
|
|
9
|
+
CLI to verify a React Native change actually works, not just that it compiles. Requires the
|
|
10
10
|
`vibeview` CLI installed and authenticated (`vibeview login`, or
|
|
11
11
|
`VIBEVIEW_API_TOKEN` set).
|
|
12
12
|
|
|
@@ -137,13 +137,15 @@ it rather than letting it appear unexplained in their `git status`.
|
|
|
137
137
|
```
|
|
138
138
|
Use `--press-ms 500` or more for long-press-to-reorder lists, and
|
|
139
139
|
`--velocity 150` for slow, precise slider adjustments.
|
|
140
|
-
- **
|
|
140
|
+
- **System alerts** (permission prompts and similar) sit above the app
|
|
141
141
|
and block it. Call `alert get` first to read the message and the exact
|
|
142
142
|
button labels, then act on one:
|
|
143
143
|
```bash
|
|
144
144
|
vibeview alert get --session <id>
|
|
145
145
|
vibeview alert accept --button "Allow" --session <id>
|
|
146
146
|
```
|
|
147
|
+
On Apple TV this also covers the "Open in …?" prompt a deep link raises;
|
|
148
|
+
the button is answered with the remote.
|
|
147
149
|
- Use `open-url` to jump straight to a deep link instead of navigating by
|
|
148
150
|
hand:
|
|
149
151
|
```bash
|
|
@@ -154,6 +156,19 @@ it rather than letting it appear unexplained in their `git status`.
|
|
|
154
156
|
(`dpad_up`/`dpad_down`/`dpad_left`/`dpad_right`/`dpad_center`), or move
|
|
155
157
|
focus directly with `tap-focused` (moves focus AND activates) / `focus`
|
|
156
158
|
(moves focus only, no activation).
|
|
159
|
+
- **Roku channels (beta)**: start the session with
|
|
160
|
+
`vibeview dev --platform roku --detach` (or `dev_start` with platform
|
|
161
|
+
`roku` over MCP); after an edit run `vibeview dev-reload` (MCP:
|
|
162
|
+
`dev_reload`) to re-package, upload and restart the channel in the same
|
|
163
|
+
session — about five seconds. The remote's names are `press up/down/left/right/select/back`
|
|
164
|
+
(`dpad_*` also works); `home` is not forwarded. `type` lands in a focused
|
|
165
|
+
on-screen keyboard; the typed text shows up in the tree as the text box's
|
|
166
|
+
child label, which is what the response verifies against. `logs` returns
|
|
167
|
+
the channel's console output (its `print` lines, why it exited, and the
|
|
168
|
+
error plus backtrace after a runtime error); `open-url` is unavailable. A
|
|
169
|
+
Back on the channel's root
|
|
170
|
+
screen exits it; the session relaunches it within seconds, so a fresh
|
|
171
|
+
first screen after `press back` is not a crash.
|
|
157
172
|
|
|
158
173
|
## 4. When only a human can act — stop and hand off
|
|
159
174
|
|
|
@@ -200,7 +215,7 @@ human-readable text.
|
|
|
200
215
|
| `scroll` | `<direction>` | Scroll the current view up, down, left, or right using a gesture preset. |
|
|
201
216
|
| `scroll-to` | `<text>` `[--direction <up\|down\|left\|right>] [--max-scrolls <n>] [--element-type <type>]` | Scroll until an element matching the text is visible. Stops at end of list. |
|
|
202
217
|
| `drag` | `<from>` `<to>` `[--velocity <n>] [--press-ms <n>] [--hold-ms <n>]` | Drag between two points, each an `@ref` or `x,y`. The only precise two-point gesture. |
|
|
203
|
-
| `alert` | `<get\|accept\|dismiss>` `[--button <label>]` | Inspect or respond to a
|
|
218
|
+
| `alert` | `<get\|accept\|dismiss>` `[--button <label>]` | Inspect or respond to a system alert on iOS or Apple TV. |
|
|
204
219
|
| `type` | `<text>` | Type text into the currently focused input field. |
|
|
205
220
|
| `clear-text` | — | Clear the text in the currently focused input field. |
|
|
206
221
|
| `press` | `<button>` | Press a device button or perform a system gesture (home, back, d-pad, etc). |
|
|
@@ -217,6 +232,7 @@ Dev-loop lifecycle commands (not registry verbs, but needed for every run):
|
|
|
217
232
|
| `dev --detach --json` | Start a device + Metro-tunnel session in the background, emitting `session_ready`/`warning`/`error` JSON events on stdout. |
|
|
218
233
|
| `dev-status` | Check on the current project's detached dev session. |
|
|
219
234
|
| `dev-stop` | End the current project's detached dev session. |
|
|
235
|
+
| `dev-reload` | Roku only: re-package, upload and restart the channel in the detached session (the `r` key of the foreground loop). |
|
|
220
236
|
|
|
221
237
|
## 7. If you're calling VibeView over MCP
|
|
222
238
|
|