@marver-design/marver 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/CHANGELOG.md CHANGED
@@ -2,6 +2,101 @@
2
2
 
3
3
  Notable changes to `@marver-design/marver`. Format follows [Keep a Changelog](https://keepachangelog.com); versions follow semver.
4
4
 
5
+ ## 0.10.1 - 2026-08-21
6
+
7
+ ### Added
8
+
9
+ - **Point an agent at anything on the canvas by its location.** Right-click a board, scene,
10
+ or frame in the sidebar for a Copy path action (the same signpost icon as the floating
11
+ toolbar): a board copies `board: <b>`, a scene `board: <b> - scene: <s> (design/scenes/<s>/)`,
12
+ a frame `board: <b> - frame: <id> (<file>)`. Laser copy now leads with `[board > scene]`
13
+ before the file and selector, and the floating toolbar plus Shift+P copy that same frame
14
+ address (board, frame, file) - all routed through one helper so the three never drift. On a
15
+ real copy the toolbar signpost flashes to a check. The AGENTS templates and the Live Jam
16
+ guide explain how to read every pasted address.
17
+ - **Manage boards from the sidebar.** Rename a board inline from its right-click menu; the
18
+ dev server performs an atomic no-clobber move and refuses a board that has a live connection
19
+ or a comment log (comment and sync state is board-keyed and cannot follow a file rename).
20
+ Reorder boards by dragging - a pointer-driven gesture (not native drag-and-drop) that holds
21
+ a grabbing cursor for the whole drag and marks the landing with a single brand-blue seam per
22
+ gap, overlaid so the list never shifts. A plain click still switches boards.
23
+
24
+ ### Removed
25
+
26
+ - The Duplicate-frame button on the floating selection toolbar.
27
+
28
+ ## 0.10.0 - 2026-08-21
29
+
30
+ ### Added
31
+
32
+ - **Live Jam speaks five more agent CLIs: Cursor, droid (Factory), opencode, grok, and pi**,
33
+ alongside claude and codex - which also covers the apps built on them (Factory drives
34
+ `droid`, Cursor drives `cursor-agent`, Conductor drives `claude`). Each adapter spawns its
35
+ CLI headless with the same posture claude set: edits yes, shell no (or OS-sandboxed) -
36
+ cursor runs with its sandbox forced on and never gets `--force`, droid loses its shell,
37
+ delegation, and connector tools, opencode runs under a per-spawn default-deny permission
38
+ grant, grok has its shell and subagents removed, pi's tool allowlist simply omits bash. Detection knows their env markers (`CURSOR_AGENT`,
39
+ `OPENCODE`, `PI_CODING_AGENT`; droid and grok set none and are found by PATH), and
40
+ `jam: "droid"` in the config block names one exactly as before. The Live Jam guide carries
41
+ the full spawn-and-jail table.
42
+
43
+ - **The verify loop: agents can now SEE what they built.** Field feedback from 0.9.0: a
44
+ jam agent shipped variants that were blank at render time, because no-shell (deliberate -
45
+ the job packet carries untrusted text) also meant no screenshots. The answer is a jailed
46
+ capability, not a shell: `GET /api/shot?frame=<id>&theme=<t>` on the dev server renders
47
+ the frame with the machine's own headless Chrome (CDP over Node's built-in WebSocket -
48
+ zero new dependencies) and returns a PNG under `design/.local/shots/`. Two transports,
49
+ because the no-shell jail rules out the obvious one: a **file-drop inbox** (the agent
50
+ writes a `<slug>.request.json`, the dev server renders and writes a `<slug>.result.json`
51
+ with the PNG path) works for every agent including Claude Code, whose WebFetch refuses
52
+ localhost; and `npx marver shot <frame>` / `GET /api/shot` for shell-ful agents and
53
+ humans. The inbox watcher pairs `fs.watch` with a 1s sweep, so a request lands even on
54
+ filesystems where watching is flaky (macOS temp, network mounts) - the same belt-and-
55
+ braces the comments daemon uses. Readiness is deterministic (root mounted, fonts ready);
56
+ a failed navigation, an unreachable server, or a frame that threw at render all return an
57
+ honest `{ok:false,error}` carrying the real cause (the frame's own exception, surfaced via
58
+ the frame host) rather than a blank that reads as success - so even an agent whose model
59
+ cannot read images still learns from the JSON whether the frame rendered. The generated
60
+ jam instructions require: shoot, read the result, LOOK at the PNG when you can - and say
61
+ so honestly when you cannot. Verified live: Claude Code screenshotted a tour frame through
62
+ the file-drop path and read back its real headline; Claude, Codex, Cursor, grok, and pi
63
+ all read a rendered PNG correctly in isolation (opencode's configured model has no vision,
64
+ and degrades to the JSON signal).
65
+ - **A troubleshooting drill written for the agent, with an upstream loop**
66
+ (`design/instructions/jam.md`): boot line first, then the raw run log in
67
+ `design/.local/jam-logs/`, then the CLI's own headless auth check - fix what belongs to
68
+ the workspace (a wrong `jam.agent`, a logged-out CLI), and file what belongs to marver
69
+ at github.com/TNEP4/marver/issues with the evidence and, when debugging surfaced one,
70
+ the patch. The give-up reply on the canvas now points at the same drill.
71
+
72
+ ### Fixed
73
+
74
+ - **The daemon pins `PWD` to the workspace when spawning an agent.** `spawn(cwd:)` changes
75
+ the directory but not the inherited env var, and some CLIs (opencode, verified) trust
76
+ `PWD` over `getcwd` - a dev server whose own cwd differed from the repo root would have
77
+ had the agent editing the wrong directory.
78
+ - **The early ack no longer leaks plan narration into the thread.** A first message like
79
+ "On it...\n\nNow let me gather context: ..." posted the note-to-self along with the ack;
80
+ a fenceless ack is now trimmed to its first paragraph (a fenced first message stays whole).
81
+ - **`/api/shot` is owner/token gated**, so a cross-origin page can't trigger the browser it
82
+ spawns; the `marver shot` CLI sends the dev-session token, and the file-drop jam path is
83
+ unaffected. It also renders from the dev server's actual listening address, not the
84
+ client-supplied Host header. And a hung headless Chrome can no longer wedge the shot queue -
85
+ a watchdog kills it past a deadline and settles every in-flight CDP call.
86
+ - **The working glow follows the agent to the frames it actually builds.** A comment on one
87
+ frame whose answer is several NEW frames ("one frame per page") left the glow stuck on the
88
+ commented frame. Jam agents have no shell to run `marver work`, so the daemon now moves the
89
+ glow itself: it watches which frames the agent creates or edits during the job, lights those,
90
+ and clears the commented frame once the agent is clearly building elsewhere.
91
+ - **Tighter agent jails, verified live.** grok's shell was actually reachable - its deny-list
92
+ named `run_terminal_cmd` but the real tool is `run_terminal_command`, so the flag missed and
93
+ a prompt-injected comment could run commands. grok now uses a read/edit tool ALLOWLIST (no
94
+ shell, no web, no subagents - a name a deny-list can't miss). opencode runs `--pure` so a
95
+ repo plugin can't execute outside its permission grant. Every other CLI's shell containment
96
+ was re-checked against the real binary (Cursor's OS sandbox blocks network egress; pi and
97
+ droid have no shell tool) rather than assumed. The Live Jam guide now states the trust model
98
+ plainly: your own agent, on your machine, every diff reviewed - not an airtight sandbox.
99
+
5
100
  ## 0.9.0 - 2026-08-21
6
101
 
7
102
  ### Added
package/README.md CHANGED
@@ -32,7 +32,7 @@ Frames appear on the canvas the moment the files land. That's the loop.
32
32
 
33
33
  ## The canvas
34
34
 
35
- - **Frames, scenes, boards.** Frames are screens, scenes group them (`design/scenes/<scene>/<frame>.tsx`), boards arrange them. Agents write `design/boards/<name>.json` (a frame list is enough); switch boards at the top of the sidebar. `all-scenes` is auto-managed.
35
+ - **Frames, scenes, boards.** Frames are screens, scenes group them (`design/scenes/<scene>/<frame>.tsx`), boards arrange them. Agents write `design/boards/<name>.json` (a frame list is enough); switch boards at the top of the sidebar. `all-scenes` is auto-managed. Right-click any board, scene, or frame in the sidebar to copy its path - the exact string to paste to your agent - and rename or drag-reorder boards from there too.
36
36
  - **Devices view.** Hotkeys `1`-`5` (or the Devices menu) size every frame to mobile / tablet / laptop / monitor / tv to sweep your breakpoints; `0` restores your own layout exactly. Widths live in `design/config.ts`.
37
37
  - **Prototype links.** `data-goto="scene/frame"` on any element links frames into a walkable prototype - across boards, too.
38
38
  - **Play mode.** Press `p`: the board becomes a full-screen, clickable walkthrough. `data-goto` links navigate, arrow keys step between frames, `[` / `]` cycle variants, `Escape` exits. Publish it and you have a shareable prototype.
@@ -46,9 +46,9 @@ Frames appear on the canvas the moment the files land. That's the loop.
46
46
 
47
47
  ## Live Jam
48
48
 
49
- Tag `@marver` in a comment and your own coding agent picks it up - reads the thread, edits the real frame source, replies with a receipt - while the frame wears a live working glow. Nothing to start and nothing to wire: it rides along with `marver dev`, on by default, armed with whichever agent CLI you have. The tool running the process wins, then whatever is on PATH, and `init` writes what it found into `design/config.ts` as `jam: { agent: "claude", concurrency: 6 }` - visible, one word to correct, `jam: false` to switch off.
49
+ Tag `@marver` in a comment and your own coding agent picks it up - reads the thread, edits the real frame source, replies with a receipt - while the frame wears a live working glow. Nothing to start and nothing to wire: it rides along with `marver dev`, on by default, armed with whichever agent CLI you have - Claude Code, Codex, Cursor, Factory's droid, opencode, grok, or pi, which also covers the apps built on them. The tool running the process wins, then whatever is on PATH, and `init` writes what it found into `design/config.ts` as `jam: { agent: "claude", concurrency: 6 }` - visible, one word to correct, `jam: false` to switch off.
50
50
 
51
- The trust boundary is hard: only comments written on the owner's machine trigger (a device-bound ledger - a drive-by comment on a published canvas cannot start work), the agent runs locked down (Claude Code with shell disabled entirely; Codex confined to its workspace-write sandbox), and every reply carries provenance: which agent ran it, as which dev user, on which model when the agent names one. Marver ships no AI; the agent that acts is the one you already run. The [Live Jam guide](docs/live-jam.md) has the config block, the two sandboxes, and what to check when a mention does nothing.
51
+ The trust boundary is hard: only comments written on the owner's machine trigger (a device-bound ledger - a drive-by comment on a published canvas cannot start work), the agent runs locked down (each CLI with its shell removed or OS-sandboxed - the per-agent table is in the guide), and every reply carries provenance: which agent ran it, as which dev user, on which model when the agent names one. Marver ships no AI; the agent that acts is the one you already run. The [Live Jam guide](docs/live-jam.md) has the config block, every agent's jail, and what to check when a mention does nothing.
52
52
 
53
53
  ## Working state
54
54
 
@@ -76,7 +76,7 @@ The same glow, driven from the terminal. When your agent takes a request, it cre
76
76
 
77
77
  **Board & chrome** - `t` tidy · `d` toggle light/dark for the board · `⌘\` (ctrl+\) collapse/open sidebar.
78
78
 
79
- **Selection** - click selects · shift+click (canvas or sidebar) builds a multi-selection · `⌘A` selects every frame on the board · `⇧P` copies the selected frames' file paths · double-click enters interact mode (`esc` or click outside leaves) · drag the title bar to move, edges to resize (widths snap to devices).
79
+ **Selection** - click selects · shift+click (canvas or sidebar) builds a multi-selection · `⌘A` selects every frame on the board · `⇧P` copies the selected frames' paths (board, frame, and file) · double-click enters interact mode (`esc` or click outside leaves) · drag the title bar to move, edges to resize (widths snap to devices).
80
80
 
81
81
  **Modes** - `c` comment mode · `l` laser mode · `⇧C` hide/show comment pins · `⇧L` laser comment (spotlight a thread's element) · `p` play mode · `h` hide all chrome.
82
82
 
@@ -1,6 +1,6 @@
1
1
  import { i as ROUTE, n as NAME } from "./cli.mjs";
2
- import { c as detectHost, o as loadConfig, r as scanFrames } from "./manifest-DJHU7qfu.mjs";
3
- import { n as tailwind3Css, r as tailwind4Plugin, t as marverPlugin } from "./plugin-BrBWk2Qn.mjs";
2
+ import { l as detectHost, r as scanFrames, s as loadConfig } from "./manifest-DIsp3ldB.mjs";
3
+ import { n as tailwind3Css, r as tailwind4Plugin, t as marverPlugin } from "./plugin-DhkR3NdW.mjs";
4
4
  import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, writeFileSync } from "node:fs";
5
5
  import { basename, dirname, join, sep } from "node:path";
6
6
  import { fileURLToPath } from "node:url";
package/dist/cli.mjs CHANGED
@@ -39,14 +39,14 @@ function version() {
39
39
  }
40
40
  const cli = cac(NAME);
41
41
  cli.command("init", "Scaffold design/ in this repo").option("--mode <mode>", "studio | embedded", { default: "studio" }).option("--no-demo", "Skip the demo scene (the demo ships unless this flag is passed)").option("--root <dir>", "Host repo root", { default: "." }).action(async (opts) => {
42
- const { init } = await import("./init-8Giknvy1.mjs");
42
+ const { init } = await import("./init-D2EQEbV6.mjs");
43
43
  init(resolve(opts.root), {
44
44
  mode: opts.mode === "embedded" ? "embedded" : "studio",
45
45
  demo: opts.demo !== false
46
46
  });
47
47
  });
48
48
  for (const [name, desc] of [["dev", "Start the local canvas (everything on: hot reload, comments, Live Jam)"], ["canvas", "Start the local canvas - same as dev"]]) cli.command(name, desc).option("--root <dir>", "Host repo root", { default: "." }).option("--port <port>", "Port (default 5199)").action(async (opts) => {
49
- const { dev } = await import("./dev-CTtkqVo_.mjs");
49
+ const { dev } = await import("./dev-Bm-R_-gs.mjs");
50
50
  let port;
51
51
  if (opts.port !== void 0) {
52
52
  const n = Number(opts.port);
@@ -56,7 +56,7 @@ for (const [name, desc] of [["dev", "Start the local canvas (everything on: hot
56
56
  await dev(resolve(opts.root), port);
57
57
  });
58
58
  cli.command("build", "Static export → design/.dist (what ships comes from design/publish.json - publishing is default-closed)").option("--boards <names>", "Publish only these boards (comma-separated); overrides the publish policy").option("--all-boards", "Publish every board - the loud override for the default-closed policy").option("--root <dir>", "Host repo root", { default: "." }).action(async (opts) => {
59
- const { buildSite } = await import("./build-D9gimz6K.mjs");
59
+ const { buildSite } = await import("./build-BkckEcZd.mjs");
60
60
  try {
61
61
  const boards = opts.boards === void 0 ? void 0 : typeof opts.boards === "string" ? opts.boards : "";
62
62
  await buildSite(resolve(opts.root), boards, opts.allBoards === true);
@@ -92,6 +92,15 @@ cli.command("work <action> [...frames]", "Working state on the canvas: start <sc
92
92
  process.exit(1);
93
93
  }
94
94
  });
95
+ cli.command("shot <frame>", "Render one frame headless and print the PNG path (needs `dev` running)").option("--root <dir>", "Host repo root", { default: "." }).option("--theme <name>", "Theme to render (default: light)").action(async (frame, opts) => {
96
+ const { shotCommand } = await import("./shot-DkkwuCZ2.mjs");
97
+ try {
98
+ await shotCommand(resolve(opts.root), frame, opts);
99
+ } catch (err) {
100
+ console.error(`[${NAME}] ${err.message}`);
101
+ process.exit(1);
102
+ }
103
+ });
95
104
  cli.help();
96
105
  cli.version(version());
97
106
  cli.parse();