vibeview 0.9.0 → 0.10.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/README.md CHANGED
@@ -95,6 +95,7 @@ Output formats: `human`, `json`, and `junit` for any CI system. The exit code re
95
95
  | `vibeview agent-setup` | Make VibeView discoverable to your coding agent (skill + MCP) |
96
96
  | `vibeview mcp` | Run the MCP server so an agent can drive the device |
97
97
  | `vibeview ui-tree` | The current screen's elements, each with a ref to act on |
98
+ | `vibeview logs` | Recent app logs from the device (console output, errors, crashes) |
98
99
  | `vibeview screenshot` | Capture the screen to a PNG |
99
100
  | `vibeview tap` / `type` / `scroll` / `press` | Drive the device |
100
101
  | `vibeview scroll-to` / `drag` / `alert` / `find` | Search, precise gestures, iOS system alerts |
@@ -495,6 +495,8 @@ function fail(opts, message, code) {
495
495
 
496
496
  // src/commands/dev.ts
497
497
  import { spawn as spawn3 } from "child_process";
498
+ import { createWriteStream, mkdirSync as mkdirSync3 } from "fs";
499
+ import { dirname as dirname4 } from "path";
498
500
  import { createInterface as createInterface3 } from "readline";
499
501
  import { select as select3 } from "@inquirer/prompts";
500
502
  import chalk2 from "chalk";
@@ -676,6 +678,9 @@ var STATE_FILE2 = "metro-state.json";
676
678
  function statePath2(cwd) {
677
679
  return join5(cwd, STATE_DIR2, STATE_FILE2);
678
680
  }
681
+ function metroLogPath(cwd) {
682
+ return join5(cwd, STATE_DIR2, "metro.log");
683
+ }
679
684
  function isPidAlive2(pid) {
680
685
  try {
681
686
  process.kill(pid, 0);
@@ -720,6 +725,11 @@ function killOwnedMetro(cwd) {
720
725
 
721
726
  // src/lib/metro-manager.ts
722
727
  var TAIL_LINES2 = 20;
728
+ var METRO_ECHO_RE = /error|failed|unable to|exception|bundled |bundling failed/i;
729
+ function shouldEchoMetroLine(line, debug) {
730
+ if (debug) return true;
731
+ return METRO_ECHO_RE.test(line);
732
+ }
723
733
  async function ensureMetro(cwd, metro, log, timeoutMs = 9e4) {
724
734
  const warnings = [];
725
735
  const status = await checkMetro(metro.port);
@@ -1151,6 +1161,18 @@ async function runDevInProcess(opts) {
1151
1161
  const metroPort = cfg.metroPort ?? 8081;
1152
1162
  let metroResult;
1153
1163
  try {
1164
+ let metroLogStream = null;
1165
+ const captureMetroLine = (line) => {
1166
+ try {
1167
+ if (!metroLogStream) {
1168
+ mkdirSync3(dirname4(metroLogPath(process.cwd())), { recursive: true });
1169
+ metroLogStream = createWriteStream(metroLogPath(process.cwd()), { flags: "w" });
1170
+ }
1171
+ metroLogStream.write(line.replace(/^\[metro\] /, "") + "\n");
1172
+ } catch {
1173
+ }
1174
+ if (shouldEchoMetroLine(line, opts.debug === true)) console.error(chalk2.dim(line));
1175
+ };
1154
1176
  metroResult = await ensureMetro(
1155
1177
  process.cwd(),
1156
1178
  {
@@ -1158,7 +1180,7 @@ async function runDevInProcess(opts) {
1158
1180
  port: metroPort,
1159
1181
  platformHasOverride: cfg.platformHasMetroOverride
1160
1182
  },
1161
- (line) => console.error(chalk2.dim(line))
1183
+ captureMetroLine
1162
1184
  );
1163
1185
  } catch (err) {
1164
1186
  const message = err.message;
@@ -1169,6 +1191,9 @@ async function runDevInProcess(opts) {
1169
1191
  for (const w of metroResult.warnings) console.error(chalk2.yellow(`\u26A0 ${w}`));
1170
1192
  if (metroResult.status === "spawned" || metroResult.status === "respawned") {
1171
1193
  console.error(chalk2.green(`\u2713 started Metro (${cfg.metroCommand})`));
1194
+ console.error(
1195
+ chalk2.dim(` full Metro output: ${metroLogPath(process.cwd())} (--debug echoes it here)`)
1196
+ );
1172
1197
  }
1173
1198
  const metroOwnedThisRun = metroResult.status === "spawned" || metroResult.status === "respawned";
1174
1199
  console.error(chalk2.dim(`Starting a ${cfg.platform} dev session for ${cfg.appId}...`));
@@ -1352,6 +1377,28 @@ var AGENT_COMMANDS = [
1352
1377
  mutating: false,
1353
1378
  params: []
1354
1379
  },
1380
+ {
1381
+ verb: "logs",
1382
+ tool: "logs",
1383
+ summary: "Read recent app logs from the device \u2014 JS console output, native errors, and crash messages from the app under test (not full system logs). Check after a crash, a frozen screen, or an unexpected blank tree, and to confirm an expected log line fired. Pass the previous response's cursor as --since to get only lines that arrived after it.",
1384
+ mutating: false,
1385
+ params: [
1386
+ {
1387
+ name: "tail",
1388
+ cli: "--tail",
1389
+ type: "number",
1390
+ required: false,
1391
+ description: "Maximum lines to return, newest kept (default 100, max 500)."
1392
+ },
1393
+ {
1394
+ name: "since",
1395
+ cli: "--since",
1396
+ type: "number",
1397
+ required: false,
1398
+ description: "Only lines newer than this cursor (use the previous response's `cursor` value)."
1399
+ }
1400
+ ]
1401
+ },
1355
1402
  {
1356
1403
  verb: "screenshot",
1357
1404
  tool: "screenshot",
@@ -1749,6 +1796,20 @@ function resultLines(result) {
1749
1796
  if (!result || typeof result !== "object") return [];
1750
1797
  const r = result;
1751
1798
  const lines = [];
1799
+ if (Array.isArray(r.entries) && typeof r.cursor === "number") {
1800
+ if (r.entries.length === 0) {
1801
+ lines.push(chalk3.dim("(no log lines)"));
1802
+ }
1803
+ for (const e of r.entries) {
1804
+ if (!e || typeof e !== "object") continue;
1805
+ const entry = e;
1806
+ const ts = typeof entry.timestamp === "number" ? new Date(entry.timestamp).toISOString().slice(11, 23) : "??:??:??.???";
1807
+ const level = typeof entry.level === "string" ? entry.level : "info";
1808
+ const levelTag = level === "error" ? chalk3.red(level) : level === "warn" ? chalk3.yellow(level) : chalk3.dim(level);
1809
+ lines.push(`${chalk3.dim(ts)} ${levelTag} ${String(entry.message ?? "")}`);
1810
+ }
1811
+ lines.push(chalk3.dim(`cursor: ${r.cursor}`));
1812
+ }
1752
1813
  if (r.found === true && typeof r.ref === "string" && r.ref) {
1753
1814
  let found = `found: @${r.ref}`;
1754
1815
  if (typeof r.text === "string" && r.text) found += ` ${JSON.stringify(r.text)}`;
package/dist/cli.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  readDevState,
9
9
  readMetroState,
10
10
  runAgentVerb
11
- } from "./chunk-WG2XR45H.js";
11
+ } from "./chunk-NA3JOV5Z.js";
12
12
  import {
13
13
  ApiClient,
14
14
  EXIT_INFRA_ERROR,
@@ -1448,7 +1448,7 @@ program.command("dev-stop").description("Stop a `vibeview dev --detach` session
1448
1448
  program.command("mcp").description(
1449
1449
  "Run an MCP stdio server exposing the agent verbs (tap, swipe, type, ...) plus dev_start/dev_stop as tools"
1450
1450
  ).option("--token <token>", "API token (or set VIBEVIEW_API_TOKEN)").action(async (opts) => {
1451
- const { runMcpServer } = await import("./server-S3D724K3.js");
1451
+ const { runMcpServer } = await import("./server-B2PDQJLR.js");
1452
1452
  await runMcpServer(opts);
1453
1453
  });
1454
1454
  program.command("agent-setup").description(
@@ -10,7 +10,7 @@ import {
10
10
  readDevState,
11
11
  renderEnvelope,
12
12
  startDevSession
13
- } from "./chunk-WG2XR45H.js";
13
+ } from "./chunk-NA3JOV5Z.js";
14
14
  import {
15
15
  ApiClient,
16
16
  resolveAuth
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibeview",
3
- "version": "0.9.0",
3
+ "version": "0.10.1",
4
4
  "description": "VibeView CLI -- run mobile tests from CI/CD pipelines",
5
5
  "type": "module",
6
6
  "bin": {
@@ -50,6 +50,11 @@ https://vibeview.io/docs/preparing-your-build.
50
50
  fires once the session accepts commands (device up, app installed) —
51
51
  no need to poll `dev-status` first. If the app ships no native changes
52
52
  since the last run, skip step 1 and just do steps 2–4.
53
+ 5. **Immediately tell the developer the session `url`** — "watch or take
54
+ over at any time: `<url>`". The page is a live, fully interactive view
55
+ of the device; the developer watching along is a feature, not a risk.
56
+ (For a session you attached to with `--session` instead of starting,
57
+ the page is `https://vibeview.io/sandbox/<session_id>`.)
53
58
 
54
59
  Running `dev` writes two things into the project root: a `.vibeview/`
55
60
  directory (live session state — local machine state, never commit) and a
@@ -103,6 +108,17 @@ it rather than letting it appear unexplained in their `git status`.
103
108
  way you will ever see the screen. It costs a lot of context, so use it
104
109
  when appearance matters — not to confirm a tap landed, which the
105
110
  response's observation already tells you.
111
+ - **When something breaks — a crash, a frozen screen, an unexpectedly empty
112
+ tree — read the app logs before guessing.** `logs` returns the app's own
113
+ output from the device: JS console lines, native errors, and crash
114
+ messages (not full system logs, and not Metro's terminal — native crashes
115
+ never appear there). The response ends with a `cursor`; pass it back as
116
+ `--since` to see only lines that arrived after your last read, which is
117
+ the natural loop for "did my action log what I expected".
118
+ ```bash
119
+ vibeview logs --session <id> # recent lines (default 100)
120
+ vibeview logs --since 42 --session <id> # only lines after cursor 42
121
+ ```
106
122
  - **Off-screen elements**: use `scroll-to <text>` rather than repeating
107
123
  `scroll down`. It scrolls until an element whose label, value, or
108
124
  accessibility id contains that text is visible, and stops early when the
@@ -139,7 +155,26 @@ it rather than letting it appear unexplained in their `git status`.
139
155
  focus directly with `tap-focused` (moves focus AND activates) / `focus`
140
156
  (moves focus only, no activation).
141
157
 
142
- ## 4. Cleanup
158
+ ## 4. When only a human can act — stop and hand off
159
+
160
+ Some screens need things you must never guess or invent: **login
161
+ credentials, 2FA/OTP codes, CAPTCHAs, destructive confirmations
162
+ ("Delete account?"), anything payment- or real-account-shaped.** When you
163
+ hit one:
164
+
165
+ 1. **Stop driving.** Do not type guessed credentials, do not tap through a
166
+ destructive confirmation to "see what happens".
167
+ 2. **Ask the developer, naming exactly what you need and where**: "I'm at
168
+ the login screen — please sign in at `<session url>` and tell me when
169
+ you're done." The session page is fully interactive; the developer can
170
+ drive the device directly while you wait.
171
+ 3. **On resume, run `ui-tree` FIRST** — the human just changed the screen,
172
+ so every ref you held is stale — and **verify the blocker is actually
173
+ gone from the tree** (e.g. the login form is absent) instead of trusting
174
+ the confirmation alone. If it's still there, say what you still see and
175
+ ask again.
176
+
177
+ ## 5. Cleanup
143
178
 
144
179
  **Always** stop the session when done verifying — a running session bills
145
180
  streaming minutes even when idle:
@@ -147,7 +182,7 @@ streaming minutes even when idle:
147
182
  vibeview dev-stop
148
183
  ```
149
184
 
150
- ## 5. Command reference
185
+ ## 6. Command reference
151
186
 
152
187
  Every verb accepts `--session <id>` to target a specific session (falls
153
188
  back to the current project's `vibeview dev --detach` session if omitted)
@@ -157,6 +192,7 @@ human-readable text.
157
192
  | Verb | Args | Purpose |
158
193
  |------|------|---------|
159
194
  | `ui-tree` | — | Fetch the ref-annotated UI tree for the current screen. |
195
+ | `logs` | `[--tail <n>] [--since <cursor>]` | Read recent app logs from the device (JS console, native errors, crash messages). The response's `cursor` feeds the next call's `--since`. |
160
196
  | `screenshot` | `[--out <path>]` | Capture a screenshot of the current screen and save it to disk. |
161
197
  | `tap` | `<target>` | Tap an element by ref (e.g. `@e5`), or raw coordinates (e.g. `100,200`). |
162
198
  | `long-press` | `<ref>` `[--ms <n>]` | Long-press (touch and hold) an element by its ref. |
@@ -182,7 +218,7 @@ Dev-loop lifecycle commands (not registry verbs, but needed for every run):
182
218
  | `dev-status` | Check on the current project's detached dev session. |
183
219
  | `dev-stop` | End the current project's detached dev session. |
184
220
 
185
- ## 6. If you're calling VibeView over MCP
221
+ ## 7. If you're calling VibeView over MCP
186
222
 
187
223
  Everything above is also exposed as MCP tools by `vibeview mcp`, so an agent
188
224
  with no shell can run the whole loop. Tool names are the CLI verbs with
@@ -198,7 +234,7 @@ steps the CLI does with plain shell commands:
198
234
  | MCP tool | Args | Purpose |
199
235
  |----------|------|---------|
200
236
  | `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`. |
201
- | `dev_start` | `platform` (required: `ios`/`android`/`tvos`/`androidtv`), `app`, `metro_port` | Start a dev-loop session held open by the MCP server. The MCP equivalent of `vibeview dev --detach`. |
237
+ | `dev_start` | `platform` (required: `ios`/`android`/`tvos`/`androidtv`), `app`, `metro_port` | Start a dev-loop session held open by the MCP server. The MCP equivalent of `vibeview dev --detach`. Returns `page_url` — relay it to the developer immediately, same as step 5 of the loop. |
202
238
  | `dev_stop` | — | Stop the session `dev_start` started. The MCP equivalent of `vibeview dev-stop`. |
203
239
 
204
240
  **`dev_start` is stateful — it changes the default session for every later