Skip to content

fix(screenshot): honor --output paths with a directory component - #119

Closed
HermeticOrmus wants to merge 2 commits into
VibiumDev:mainfrom
HermeticOrmus:fix/screenshot-honor-output-path
Closed

fix(screenshot): honor --output paths with a directory component#119
HermeticOrmus wants to merge 2 commits into
VibiumDev:mainfrom
HermeticOrmus:fix/screenshot-honor-output-path

Conversation

@HermeticOrmus

@HermeticOrmus HermeticOrmus commented Apr 30, 2026

Copy link
Copy Markdown

Why

vibium screenshot -o /tmp/foo.png (or ./foo.png, or subdir/foo.png) silently lands at ~/Pictures/Vibium/foo.png — the daemon strips any path information down to the basename and joins with the configured screenshot dir. Driving vibium against SPAs from a project working dir, every screenshot needed a follow-up mv to land where I expected.

The basename-strip is guarded by a "prevent path traversal" comment, but the daemon is local-only and the flag is supplied by the operator who already controls the host — the guard wasn't buying real safety, just costing usability.

What changed

The CLI resolves any -o value with a directory component to an absolute path against the operator's CWD before sending to the daemon. The daemon honors a filename-with-directory as written; bare basenames still go to the configured screenshotDir, so the "Screenshots save to a sensible location automatically" default from CLAUDE.md is preserved.

Input Before After
-o foo.png ~/Pictures/Vibium/foo.png ~/Pictures/Vibium/foo.png (unchanged)
-o ./foo.png ~/Pictures/Vibium/foo.png <cwd>/foo.png
-o /tmp/foo.png ~/Pictures/Vibium/foo.png /tmp/foo.png
-o subdir/foo.png ~/Pictures/Vibium/foo.png <cwd>/subdir/foo.png (parent auto-created)

Help text and -o flag description updated to document the new behaviour.

How to test

make build-go
node --test tests/daemon/cli-commands.test.js

New cases under Daemon CLI: Screenshot honors --output path:

  • absolute path is honored as written
  • relative path with ./ prefix lands in CWD, not screenshot dir
  • subdir relative path creates parent and saves there
  • bare basename still goes to default screenshot dir (regression guard)

All 4 pass; the existing Screenshot --full-page test continues to pass with default behaviour unchanged.

Notes

  • The pre-existing Daemon CLI: quit command test fails on main too — it references a vibium quit command that no longer exists in the binary. Not introduced here, but flagging.
  • make test couldn't run on this Linux dev box because npm install errors with Invalid Version: during dedup of the @vibium/<platform> optionalDependencies (npm 11.12.1 + arborist quirk). Daemon test ran directly with node --test, which only depends on the locally-built binary at clicker/bin/vibium.

Before this change, `vibium screenshot -o /tmp/foo.png` (or `./foo.png`,
or `subdir/foo.png`) silently landed at `~/Pictures/Vibium/foo.png` —
the daemon stripped any path information down to the basename and joined
with the configured screenshot dir. The behaviour was guarded by a
"prevent path traversal" comment, but the daemon is local-only and the
flag is supplied by the operator who already controls the host, so the
guard was costing usability without buying real safety.

Now:

- `-o foo.png`            → `~/Pictures/Vibium/foo.png` (default preserved)
- `-o ./foo.png`          → `<cwd>/foo.png`
- `-o /tmp/foo.png`       → `/tmp/foo.png`
- `-o subdir/foo.png`     → `<cwd>/subdir/foo.png` (parent dir auto-created)

The CLI resolves any path with a directory component to an absolute path
against the operator's CWD before sending to the daemon, so relative
paths land where the operator typed them rather than against the daemon
process's CWD.

Backwards compatible: a bare basename (no separator) still uses the
configured `screenshotDir`, preserving the "Screenshots save to a
sensible location automatically" default from CLAUDE.md.

Tests:

- `tests/daemon/cli-commands.test.js` gains 4 new cases under
  "Daemon CLI: Screenshot honors --output path" covering absolute,
  relative, subdir, and bare-basename inputs.
- The existing `Screenshot --full-page` test continues to pass —
  it uses a bare basename and the default behaviour is unchanged.
- All 4 new tests pass locally; the only failing test in
  cli-commands.test.js is the pre-existing `Daemon CLI: quit
  command` which references a `vibium quit` command that no longer
  exists in the binary (unrelated to this PR).
Drop multi-line comment blocks and verbose flag prose for the project's
"one short line max" comment style.
hugs added a commit that referenced this pull request Aug 3, 2026
vibium screenshot reduced -o to its basename and joined it with the
screenshot dir, so every path the user typed was discarded: -o /tmp/x.png
landed in ~/Pictures/Vibium/x.png.

The daemon is a separate long-lived process, so it cannot resolve a
relative path against the user's shell. The CLI now resolves -o itself
and sends an absolute path; the daemon writes an absolute path as given
and creates any missing parent directories.

A bare filename still goes to the screenshot dir, which keeps the MCP
default (~/Pictures/Vibium) intact. That default exists because an agent
has no working directory the user can see; a developer typing into a
terminal does, so the CLI now writes there.

Adds docs/how-to-guides/screenshots.md.
@hugs

hugs commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Thanks for this! You had the right diagnosis. The daemon half alone could not fix it though, since the daemon's working directory is not yours, so the CLI had to resolve -o before sending it. That landed in #286, which also moves the CLI default to the current directory. Closing this out.

@hugs hugs closed this Aug 3, 2026
hugs added a commit that referenced this pull request Aug 4, 2026
vibium pdf -o and vibium record stop -o hand the path to the daemon, whose
working directory is not the caller's, so a relative path landed wherever
the daemon happened to be started while the command reported success.

#286 fixed this for screenshot and stopped there. These are the same bug in
the two sibling commands. storage -o was already fine: it writes the file
client-side.

pdf also now creates missing parent directories, which screenshot and
record already did.
vincebln2 pushed a commit to vincebln2/vibium that referenced this pull request Aug 10, 2026
vibium record start without -o declared record.zip relative to the
daemon's working directory, which is not the user's shell (VibiumDev#119 fixed
the same surprise for stop -o). The CLI now resolves the default before
it goes over the socket, so the zip lands where the user ran the
command.

The MCP/CLI start line also no longer inlines the full engine reason
when video is unavailable — the sentence stays compact and the full
actionable reason still arrives in the stop result.
vincebln2 pushed a commit to vincebln2/vibium that referenced this pull request Aug 10, 2026
…d name-seeded

stop() no longer buffers the zip to hand back bytes nobody asked for.
Every client (JS async+sync, Python async+sync, Java) now returns
{path, steps, durationMs, videos | videoUnavailable}; the zip bytes
ride along only for bytes-only recordings (path null at start), where
they are the point. The Java file read-back is gone with it. stopChunk
returns the same shape.

The default destination is record-YYYYMMDD-HHMMSS.zip — a rerun never
clobbers the previous artifact; same-second collisions get a -2
suffix. The recording's name, sanitized, seeds the stem:
start({name: 'login'}) lands login-20260808-094123.zip. Explicit
paths are honored exactly and overwrite. The CLI resolves the default
against the user's shell, not the daemon's working directory (VibiumDev#119).

The recording-video spec records both decisions; the vibium play spec
gains the no-arg form: `vibium play` opens the most recently modified
recording zip in the working directory — the counterpart to
timestamped names.
vincebln2 pushed a commit to vincebln2/vibium that referenced this pull request Aug 10, 2026
A pathless browser_record_start landed wherever the MCP server process
happened to start. Now it lands in the server's working directory when
that is a real, writable place — hosts like Claude Code launch the
server in the project, so the recording sits next to the code — and in
~/Documents/Vibium otherwise. Same reasoning as the screenshots'
~/Pictures/Vibium default (VibiumDev#119): an MCP caller may have no working
directory to reason about. Recordings are work artifacts, so Documents,
not Pictures or Movies.

Explicit paths and the CLI (which resolves in the user's shell) are
unchanged.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants