feat: rk present — one-verb "show this to the user" + skill page rewrite - #589
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a new “present” capability to run-kit’s CLI + backend so agents can show files/dirs/ports/URLs to users by attaching a web tile to the current tmux window (or optionally spawning a standalone iframe window), and rewrites the skill docs to teach the new workflow.
Changes:
- Introduces
rk present(target parsing, tmux option writes, optional--window+--notify) with command-level tests. - Adds a new GET content route
/present/{windowId}/…that serves from a tmux-derived@rk_present_rootwith containment checks, plus handler tests. - Updates README + skill pages + memory docs to reflect the new verb and conventions.
Reviewed changes
Copilot reviewed 23 out of 23 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| README.md | Adds run-kit present to the command table. |
| fab/changes/260813-becu-rk-present-attach-verb/plan.md | Captures design/requirements/tasks for rk present + /present/ route. |
| fab/changes/260813-becu-rk-present-attach-verb/intake.md | Intake record for the change and rationale. |
| fab/changes/260813-becu-rk-present-attach-verb/.status.yaml | Fab status metadata for the change lifecycle. |
| fab/changes/260813-becu-rk-present-attach-verb/.history.jsonl | Fab stage transition history. |
| docs/site/skill/display.md | Rewrites the visual display topic around rk present. |
| docs/site/skill.md | Updates the core skill bundle to teach rk present as the primary recipe. |
| docs/memory/run-kit/toolkit-standards.md | Documents toolkit-standards conformance for the new CLI surface. |
| docs/memory/run-kit/tmux-sessions.md | Updates tmux option registry + semantics for @rk_present_root / @rk_url. |
| docs/memory/run-kit/index.md | Updates memory index entries to include new present-related details. |
| docs/memory/run-kit/architecture.md | Adds present to the CLI list and endpoint inventory. |
| app/backend/internal/tmux/tmux.go | Adds GetWindowOption and CreateWindowWithOptionsID. |
| app/backend/internal/tmux/tmux_test.go | Adds a round-trip test for GetWindowOption. |
| app/backend/internal/present/present.go | New pure target parser + URL derivation + port probe helper. |
| app/backend/internal/present/present_test.go | Unit tests for target parsing, URL derivation, and probing behavior. |
| app/backend/cmd/rk/skill/skill.md | Synced embedded copy of updated core skill bundle. |
| app/backend/cmd/rk/skill/display.md | Synced embedded copy of updated display topic. |
| app/backend/cmd/rk/root.go | Registers the new present subcommand. |
| app/backend/cmd/rk/present.go | Implements rk present (attach, --window, --notify) with seams for testing. |
| app/backend/cmd/rk/present_test.go | Command-level tests for exit codes, stdout contract, option writes, notify behavior. |
| app/backend/api/router.go | Registers /present/{windowId} routes. |
| app/backend/api/present.go | Implements request-time tmux-derived file serving with containment checks. |
| app/backend/api/present_test.go | Handler tests for serving + traversal/symlink containment matrix. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| t.Cleanup(func() { getWindowOptionFn = nil }) | ||
| // NOTE: the production default is restored by reassigning below — see | ||
| // TestMain-level hygiene: the seam is package-global, so restore it. | ||
| t.Cleanup(func() { getWindowOptionFn = defaultGetWindowOption }) |
There was a problem hiding this comment.
Fixed — removed the nil cleanup so the LIFO-last cleanup restores the production default seam instead of leaving it nil. (ee367eb)
| ``` | ||
|
|
||
| ## Iframe Windows | ||
| The resolved relative URL prints to stdout; diagnostics go to stderr. Exit codes: `0` success, `1` operational failure (not in tmux, file missing, port not listening), `2` usage. |
There was a problem hiding this comment.
Fixed — reworded to "resolved URL" with the relative/absolute split (relative for /present and /proxy targets, absolute for external URLs). (ee367eb)
| - **Principle 9: the resolved URL is the only stdout line — data.** stdout | ||
| carries exactly the resolved relative `@rk_url` value and prints it even | ||
| under `--quiet` (silence would hide the command's one result); diagnostics | ||
| go to stderr. `--notify`'s send failure is the documented fail-silent |
There was a problem hiding this comment.
Fixed — the Principle 9 bullet now says stdout carries the resolved @rk_url value, relative for /present//proxy and absolute for external URLs. (ee367eb)
| "By default the content attaches to the caller's own tmux window (@rk_url),\n" + | ||
| "and the resolved relative URL prints to stdout. --window spawns a standalone\n" + | ||
| "iframe window instead; --notify sends a Web Push after attaching (fail-silent).\n" + |
There was a problem hiding this comment.
Fixed — help text now says "resolved URL" with the relative/absolute split per target kind. (ee367eb)
| ``` | ||
|
|
||
| ## Iframe Windows | ||
| The resolved relative URL prints to stdout; diagnostics go to stderr. Exit codes: `0` success, `1` operational failure (not in tmux, file missing, port not listening), `2` usage. |
There was a problem hiding this comment.
Fixed — reworded in the canonical docs/site/skill/display.md and re-synced the embedded copy via scripts/sync-skill.sh, so the byte-equality drift guard stays green. (ee367eb)
| ops := []tmux.WindowOptionOp{{Key: presentURLOption, Value: &urlOp}} | ||
| if target.NeedsRoot() { | ||
| root := target.Root | ||
| ops = append(ops, tmux.WindowOptionOp{Key: presentRootOption, Value: &root}) | ||
| } |
There was a problem hiding this comment.
Fixed — presentAttach now unsets @rk_present_root (nil-Value op, i.e. set-option -u) for port/local-URL/external targets, clearing any stale serve root; TestPresentAttachComposition pins the unset. (ee367eb)
Meta
becuexcludes
fab/,docs/· generated by fab-kit v2.20.1Pipeline: intake ✓ → apply ✓ → review ✓ → hydrate ✓ → ship → review-pr
Summary
The agent-facing
rk skillrecipe still taught the retired synthetic-iframe-window pattern (spawn a window +@rk_type=iframe+ a hand-runpython3 -m http.server), polluting the sidebar and carrying port-collision/orphan-process footguns.rk present <target>bakes the new convention into one verb: resolve a file/dir/port/URL target, attach it as awebtile on the caller's own window via@rk_url, and serve file targets through a new tmux-option-derived/present/{windowId}/…route — no spawned server, no spool copy, no registration state.Changes
rk presentcommand (new cobra subcommand,app/backend/cmd/rk/)/present/{windowId}/…route reading@rk_present_rootat request time--windowfallback (the residual standalone-iframe-window case)--notifyscope decision (v1 plain-text only; deep-link deferred)@rk_url,?v=cache-buster, additive web tile)rk skill/rk skill displayrewrite teaching the new verb