Status: Draft (2026-05-07)
Branch: v6.0-warp-pivot
Companion: PRODUCT.md
This document complements PRODUCT.md. PRODUCT.md is the what and why; TECH.md is the how, in what order, what we already verified, what we still need to verify.
┌─────────────────┐ ┌─────────────────┐
│ Claude Desktop │ │ Warp Agent │
│ (TS client) │ │ (rmcp client) │
└────────┬────────┘ └────────┬────────┘
│ stdio │ stdio
│ MCP protocol │ MCP protocol
└──────────────┬───────────────────────┘
│
▼
┌─────────────────────────────┐
│ warp-command-runner │
│ (Swift, MCP server) │
│ 36+ tools │
└─────────────┬───────────────┘
│
│ Mostly subprocess + filesystem
│ Some warp:// deeplinks (Tier B)
│ Some OSC 777 emission (Tier B)
│ Optional shell-shim socket (Tier E)
▼
┌─────────────────────────────┐
│ Warp Terminal │
└─────────────────────────────┘
Two clients calling the same server. The MCP protocol abstracts the consumer; the server doesn't need to know which is calling. Verification work (Tier D) is to confirm this abstraction holds.
| Considered | Rejected because |
|---|---|
| Become a Warp cli-agent (OSC 777 events only) | One-way notification channel; no inbound. Closed enum at cli_agent.rs:124-160 keeps third parties as Unknown only. Doesn't let user talk to Claude in Warp. |
| Read Warp's SQLite directly | ~80 Diesel migrations in 5 years; schema renames columns. Stable for one Warp version, breaks the next. WarpDatabaseIntegration.swift already proves this approach is fragile (it's currently dead code, never merged into the dispatch). |
| Build a Warp plugin | No third-party plugin API. Internal plugin host is for Warp's own bundled JS. WASM only used for web build. |
| Build a Warp fork | AGPL invocation cascades; massive maintenance burden; defeats the point of an integration product. |
| Bridge Claude Desktop → Warp | Claude Desktop is pull-based; no server-push. Would require inventing a relay. Reinvents Claude Code. Out of scope. |
| Class | Count | Examples | Bridge mechanism after v6.0 |
|---|---|---|---|
| Pure server-side | 24 | copy_to_clipboard, ssh_execute, add_file_watch, save_workspace_profile, list_recent_commands |
None — direct Swift, no Warp interaction |
| Subprocess-execution (no Warp) | included above | execute_pipeline, get_environment_context |
Process / /bin/bash -c |
| Warp-driving (post-cleanup) | 6 | execute_command, execute_with_auto_retrieve, execute_with_streaming, run_template, open_terminal_tab, send_to_session |
Deeplinks for open; AppleScript keystroke for type; /tmp/<id>.json polling for capture (or shell shim if installed) |
| New in v6.0 | 1+ | emit_warp_event |
OSC 777 |
The order matters — earlier tiers de-risk later ones.
Tier A (cleanup)
└── lowest risk; pure deletions; build-verifiable
│
▼
Tier B (deeplinks + OSC 777)
└── new code; build-verifiable; testable without Warp running
│
▼
Tier C (workspace profile alignment)
└── small change; opt-in flag; doesn't touch existing path
│
▼
Tier D (Warp Agent integration)
└── verification-heavy; requires Warp running; some rework expected
│
▼
Tier E (shell shim)
└── separate component; user-installed; gated by flag
│
▼
Tier F (polish, CHANGELOG, README, demo)
Each tier ends with a clean commit on v6.0-warp-pivot. Build must pass after every commit.
| Path | LOC | Reason |
|---|---|---|
Sources/WarpCommandRunner/WarpDatabaseIntegration.swift |
260 | Never reached from any registered tool. Hardcoded Warp schema. Confirmed dead by audit. |
Sources/WarpCommandRunner/CommandReceiverService.swift |
~190 | TCP listener on 127.0.0.1:9876 with suggest/execute/ping mock handlers only. Never invoked from production code. |
| Path | Change |
|---|---|
Sources/WarpCommandRunner/CommandHistory.swift |
Remove the single :218-222 block that referenced WarpDatabaseIntegration (it's the only call site, and it's not reachable from a registered tool). Keep the rest of the file (used by list_recent_commands). |
Sources/WarpCommandRunner/WarpCommandRunner.swift |
Remove any startup wiring that instantiated CommandReceiverService (verify in init or run). |
Sources/WarpCommandRunner/CommandHandlers.swift |
The :386 Task-based background-monitor path is unused. Remove it. The CommandHandlersStable.swift path is the live one and stays. |
README.md |
Replace "30 tools" with "36 tools" (multiple occurrences). Add the six undocumented tools to the feature listing: set_notification_preference, cleanup_sessions, list_file_watches, delete_workspace_profile, list_ssh_profiles, delete_ssh_profile. |
swift build -c release
swift test
grep -r "WarpDatabaseIntegration\|CommandReceiverService" Sources/ # expect 0 hitschore: remove dead code (WarpDatabaseIntegration, CommandReceiverService, unused background monitor)
- WarpDatabaseIntegration.swift (260 LOC) was never reached from the dispatch
table. Hardcoded Warp schema; would silently break on Warp updates.
- CommandReceiverService.swift TCP listener (127.0.0.1:9876) had only mock
handlers; never invoked from production.
- CommandHandlers.swift:386 background-monitor path was disabled to prevent
server crashes; CommandHandlersStable.swift remains the live path.
README updated to document all 36 registered tools (was claiming 30).
No tool removed; no behavior change.
Before (TerminalSessions.swift:91-101):
let script = """
tell application "Warp"
activate
tell application "System Events" to tell process "Warp"
click menu item "New Tab" of menu "Shell" of menu bar 1
-- ...
end tell
end tell
"""
runOsascript(script)After:
let url = "warp://action/new_tab?path=\(escapedPath)"
let proc = Process()
proc.launchPath = "/usr/bin/open"
proc.arguments = [url]
try proc.run()
proc.waitUntilExit()Notes:
- Path must be percent-encoded (use
addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)). open(1)returns immediately; tab open is async. If we need to know when the tab is ready, fall back to a short delay (300ms) before subsequent operations. This mirrors the AppleScript behavior; not worse.- No Accessibility permission needed for opening. (Still needed for typing.)
New behavior: when we open a tab, capture and persist a session UUID. When the user later requests focus on a previously-opened tab, dispatch warp://session/<uuid>.
Open question: Warp's warp://action/new_tab does not return the new tab's UUID. Approaches:
- (preferred) Mint our own UUID, pass it as a query parameter Warp ignores, but include in our session registry. Use it for deduplication only. Warp's session UUID we can't directly bind to.
- Subscribe to OSC 777
session_startevents (which include the session ID) by running our shell shim — but shim is opt-in. - Open a tab, then immediately query Warp's SQLite for the most recent
tabsrow (race-prone, schema-fragile).
Decision: ship (1) for v6.0. Document the limitation. (2) becomes the "better path" once shell shim is installed. (3) is rejected.
Schema:
Implementation (OSCEmitter.swift, new file):
func emitOSC777(event: WarpCliAgentEvent) {
// Schema reference: warp/app/src/terminal/cli_agent_sessions/event/v1.rs:14-76
let json = try JSONEncoder().encode(event)
let hex = json.map { String(format: "%02x", $0) }.joined()
// OSC 777 ; notify ; warp://cli-agent ; <json> ST
let sequence = "\u{1B}]777;notify;warp://cli-agent;\(json)\u{07}"
FileHandle.standardOutput.write(sequence.data(using: .utf8)!)
}AGPL note: the JSON schema is observed from Warp's source; we reimplement, do not vendor. The event/v1.rs types are AGPL; our Swift structs are independent.
- Build passes
open_terminal_tabopens a tab withoutosascriptshowing inActivity Monitortracesemit_warp_eventproduces a string starting\x1b]777;notify;warp://cli-agent;and ending\x07- Manual: with Warp running, observe the event in Warp's cli-agent notification UI
feat(tab): use warp:// deeplinks for new tab/window operations
Replaces the AppleScript "click menu item New Tab" path that fought with Warp's
focus and timing. open(1) -> warp://action/new_tab is the documented Warp
entrypoint and avoids the Accessibility-permission requirement for the open
path. AppleScript keystroke fallback retained for send_to_session typing —
Warp has no API for sending input to a specific tab.
feat(events): add emit_warp_event tool for OSC 777 status surfacing
Emits warp://cli-agent JSON events that Warp parses into its notification UI
(see warp/app/src/terminal/cli_agent_sessions/event/v1.rs:14-76). Schema is
reimplemented from the Warp source, not vendored — AGPL hygiene.
Warp expects YAML in ~/.warp/launch_configs/<name>.yaml (verify by inspecting crates/warp_core/src/paths.rs and a sample launch config from a running Warp install). Schema covers:
namewindows[]withtabs[]containinglayout,commands[],cwd,env
Add optional parameter:
{
"include_warp_launch_config": { "type": "boolean", "default": false }
}When true, after writing the existing JSON, also serialize a Warp-compatible YAML to ~/.warp/launch_configs/<sanitized_name>.yaml. On delete_workspace_profile, if the YAML exists, also delete it (with a flag to be safe).
- Existing JSON profile flow unchanged when flag is false
- When true, file appears in
~/.warp/launch_configs/ - Warp's launch UI lists the profile (manual verification)
This is the hardest tier because it requires running both Warp and the MCP and exercising tools through Warp's rmcp client. We have not previously tested against rmcp.
For each of 36 tools:
- Call from Claude Desktop → record exact response (already known to work in v5)
- Call from Warp Agent with the same arguments → record response
- Diff. If different, identify whether:
- Response format mismatch (e.g.
rmcpexpects different content-type) → server-side fix - Behavior mismatch (e.g. tool doesn't make sense in Warp Agent context) → document or skip
- Response format mismatch (e.g.
Expected outcomes (informed prediction, not a guarantee):
- 24 pure server-side tools: identical behavior. ~0 rework.
- 6 Warp-driving tools: identical behavior, since target IS Warp. ~0 rework.
- New
emit_warp_event: works because it's in the same surface Warp parses. - Edge cases: long-output tools may stream differently between TS and Rust MCP clients; structured-content tools may need
outputSchemaaudited.
Budget: assume 1-3 tools need response-format adjustments. Plan for 2 days of test+fix cycle.
To ship in README:
{
"mcpServers": {
"warp-command-runner": {
"command": "/Users/<you>/Github/warp-command-runner/.build/release/WarpCommandRunner",
"args": []
}
}
}- Manual matrix run, results recorded in
verification/v6.0-warp-agent-matrix.md(transient, not checked in to main but kept on the branch during release prep) - All blocking issues fixed; non-blocking ones become release notes
The shim writes its own structured events to a Unix socket the MCP listens on (e.g. /tmp/wcr-shell-shim.sock).
Wire format (line-delimited JSON):
{ "type": "preexec", "block_id": "...", "command": "git status", "ts": "..." }
{ "type": "command_finished", "block_id": "...", "exit_code": 0, "duration_ms": 142, "ts": "..." }The shim parses Warp's DCS payload from the controlling terminal and forwards the relevant fields. It does not vendor Warp's parser; it observes the documented hex-JSON DCS format and re-decodes it.
| File | Purpose |
|---|---|
helper/shell-shim.zsh |
The script users source from ~/.zshrc |
helper/shell-shim.bash |
Bash equivalent |
helper/install-shim.sh |
Idempotent installer that adds a marker block to the user's shell rc |
Sources/WarpCommandRunner/ShimSocket.swift |
NIO Unix-domain-socket listener; consumed by execute_command path |
execute_commandfirst attempts shim; if shim not connected, falls back to/tmp/<id>.jsonpolling (v5 path).- Shim auto-disables itself if the user opens a non-Warp shell (no Warp DCS hooks to observe).
- Marker block in shell rc is single-line bracketed for clean uninstall.
bash helper/install-shim.shadds five clean lines to~/.zshrc; second run is no-op.bash helper/uninstall-shim.shremoves them cleanly.- With shim active,
execute_commandproduces output without/tmp/<id>.jsonfiles appearing. - With shim absent, behavior matches v5.
New top entry, ~50 lines, organized by Tier with user-impact framing.
Two-path narrative:
- Use with Claude Desktop (Journey A) — mostly v5 docs, light updates.
- Use with Warp Agent (Journey B) — new section with
~/.warp/.mcp.jsonsnippet, screenshot of Warp's agent panel calling our tools.
One short GIF (or video) showing a complete Journey B turn: type in Warp, agent calls execute_command, output renders, agent replies. Asset path: assets/demo-warp-agent.gif.
Unknown until tested. Highest-risk unknowns:
- Tools that return large output (e.g.
get_environment_context) - Tools that use MCP
progressnotifications (execute_with_streaming) - Tools whose
outputSchemawe never tightened
Mitigation: dedicate Tier D's first day to running the matrix and adjusting before any other Tier D work.
Documented in §4.2. Decision: ship (1) for v6.0; revisit when shell shim is GA.
Before merging:
- No verbatim copy of any
crates/ipc/*content - No verbatim copy of any
app/assets/bundled/bootstrap/*.sh - No verbatim copy of OSC parser code from Warp
- All schemas (OSC 777 event JSON, DCS hex-JSON) are reimplemented in our types
Warp moves fast. The cli-agent events we use (tool_complete, etc.) might have schema changes by the time we ship. Lock to the v1 schema (event/v1.rs); add version detection if/when Warp introduces v2.
swift test — keep all passing. New tests for Tier A cleanup are not necessary (deletions are verified by build + grep).
OSCEmitterTests.swift— emit known event, verify byte sequenceDeeplinkBuilderTests.swift— verify URL construction with edge cases (paths with spaces, unicode, etc.)ShellShimSocketTests.swift— feed canned DCS payloads, verify parsed events
- Tier D matrix (per §6.1)
- Shell shim install/uninstall (per §7.4)
- Demo recording
-
swift build -c releaseclean (no warnings introduced this branch) -
swift test100% pass - Tier D matrix all-green or documented exceptions
- AGPL checklist complete (§9.3)
- CHANGELOG complete
- All work on
v6.0-warp-pivot - One commit per Tier (or sub-Tier where logical), squashable later if user prefers
- Release: tag
v6.0.0, push toM-Pineapple/warp-command-runner, draft GitHub Release notes from CHANGELOG - Memory file
project_claude_command_runner_v6.mdupdated with final commit SHA at release time
{ "name": "emit_warp_event", "description": "Emit a structured event into Warp's UI via OSC 777. Visible to Warp panes subscribed to warp://cli-agent.", "inputSchema": { "type": "object", "properties": { "event_type": { "type": "string", "enum": ["session_start", "prompt_submit", "tool_complete", "stop", "permission_request", "idle_prompt"] }, "payload": { "type": "object" }, "session_id": { "type": "string" } }, "required": ["event_type", "payload"] } }