From 81f825e868770c6f6ed8309778901f35c220ab2b Mon Sep 17 00:00:00 2001 From: parsakhaz Date: Tue, 21 Jul 2026 23:32:25 -0700 Subject: [PATCH] feat: declarative pane pinning via RunPane CLI (#357) --- contracts/runpane/contract.json | 288 +++++++++++++++++- contracts/runpane/schema.json | 8 + docs/RUNPANE_CLI_CONTRACT.md | 3 + .../database.favorite-pinning.test.ts | 59 ++++ main/src/database/database.ts | 14 + main/src/ipc/runpane.test.ts | 163 ++++++++++ main/src/ipc/runpane.ts | 60 ++++ packages/runpane-py/src/runpane/cli.py | 11 + .../src/runpane/generated_contract.py | 2 +- .../runpane-py/src/runpane/local_control.py | 47 ++- packages/runpane/src/cli.ts | 9 + packages/runpane/src/commands.ts | 7 + packages/runpane/src/generated/contract.ts | 288 +++++++++++++++++- packages/runpane/src/localControl.ts | 71 ++++- scripts/fixtures/runpane-contract.json | 17 ++ scripts/test-runpane-contract.js | 138 +++++++++ shared/types/generatedRunpaneContract.ts | 288 +++++++++++++++++- shared/types/runpaneOrchestration.ts | 17 ++ 18 files changed, 1466 insertions(+), 24 deletions(-) create mode 100644 main/src/database/database.favorite-pinning.test.ts diff --git a/contracts/runpane/contract.json b/contracts/runpane/contract.json index b74303a1..aef912fa 100644 --- a/contracts/runpane/contract.json +++ b/contracts/runpane/contract.json @@ -194,6 +194,30 @@ "paneArchiveResult" ] }, + { + "name": "panes pin", + "summary": "Declaratively pin a Pane; pinned is the Pane UI's favorite/pin star and repeated requests are idempotent.", + "usage": [ + "runpane panes pin --pane --yes [--dry-run] [--json]" + ], + "mutates": true, + "jsonSchemas": [ + "panePinRequest", + "panePinResult" + ] + }, + { + "name": "panes unpin", + "summary": "Declaratively unpin a Pane; pinned is the Pane UI's favorite/pin star and repeated requests are idempotent.", + "usage": [ + "runpane panes unpin --pane --yes [--dry-run] [--json]" + ], + "mutates": true, + "jsonSchemas": [ + "panePinRequest", + "panePinResult" + ] + }, { "name": "panels create", "summary": "Create a terminal-backed tool panel inside an existing Pane session.", @@ -520,6 +544,10 @@ "name": "--focus", "description": "Explicitly focus the created pane or panel." }, + { + "name": "--pinned", + "description": "Create the pane already pinned (the UI's favorite/pin star)." + }, { "name": "--force", "description": "Archive even if the pane's branch has uncommitted, untracked, or unpushed changes." @@ -688,8 +716,10 @@ " runpane panes list [--repo ] [--json]", " runpane panes create --repo --name --agent [options]", " runpane panes archive --pane [--force] [--source user|agent] --yes [--json]", + " runpane panes pin --pane --yes [--dry-run] [--json]", + " runpane panes unpin --pane --yes [--dry-run] [--json]", "", - "Run \"runpane help panes list\", \"runpane help panes create\", or \"runpane help panes archive\" for command-specific options." + "Run \"runpane help panes \" for command-specific options." ], "panes list": [ "Usage:", @@ -728,6 +758,7 @@ " --source Mark mutation source; agent implies background creation", " --no-focus Create in the background without stealing focus", " --focus Explicitly focus the created pane", + " --pinned Create already pinned (the UI's favorite/pin star)", " --pane-dir Connect to a specific Pane data directory", " --json Print machine-readable output", " --dry-run Validate and preview without creating panes", @@ -747,6 +778,32 @@ " --json Print machine-readable output", " --yes Skip confirmation for mutating commands" ], + "panes pin": [ + "Usage:", + " runpane panes pin --pane --yes [--dry-run] [--json]", + "", + "Declaratively pins a Pane using the Pane UI's favorite/pin star. Repeating the command is idempotent and never changes focus. Use --dry-run to validate and preview without mutating.", + "", + "Options:", + " --pane Pane/session id to pin", + " --pane-dir Connect to a specific Pane data directory", + " --json Print machine-readable output", + " --dry-run Validate and preview without pinning the pane", + " --yes Skip confirmation for mutating commands" + ], + "panes unpin": [ + "Usage:", + " runpane panes unpin --pane --yes [--dry-run] [--json]", + "", + "Declaratively unpins a Pane using the Pane UI's favorite/pin star. Repeating the command is idempotent and never changes focus. Use --dry-run to validate and preview without mutating.", + "", + "Options:", + " --pane Pane/session id to unpin", + " --pane-dir Connect to a specific Pane data directory", + " --json Print machine-readable output", + " --dry-run Validate and preview without unpinning the pane", + " --yes Skip confirmation for mutating commands" + ], "panels": [ "Terminal-backed panel commands.", "", @@ -1057,8 +1114,10 @@ " runpane panes list [--repo ] [--json]", " runpane panes create --repo --name --agent [options]", " runpane panes archive --pane [--force] [--source user|agent] --yes [--json]", + " runpane panes pin --pane --yes [--dry-run] [--json]", + " runpane panes unpin --pane --yes [--dry-run] [--json]", "", - "Run \"runpane help panes list\", \"runpane help panes create\", or \"runpane help panes archive\" for command-specific options." + "Run \"runpane help panes \" for command-specific options." ], "panes list": [ "Usage:", @@ -1097,6 +1156,7 @@ " --source Mark mutation source; agent implies background creation", " --no-focus Create in the background without stealing focus", " --focus Explicitly focus the created pane", + " --pinned Create already pinned (the UI's favorite/pin star)", " --pane-dir Connect to a specific Pane data directory", " --json Print machine-readable output", " --dry-run Validate and preview without creating panes", @@ -1116,6 +1176,32 @@ " --json", " --yes" ], + "panes pin": [ + "Usage:", + " runpane panes pin --pane --yes [--dry-run] [--json]", + "", + "Declaratively pins a Pane using the Pane UI's favorite/pin star. Repeating the command is idempotent and never changes focus. Use --dry-run to validate and preview without mutating.", + "", + "Options:", + " --pane ", + " --pane-dir ", + " --json", + " --dry-run", + " --yes" + ], + "panes unpin": [ + "Usage:", + " runpane panes unpin --pane --yes [--dry-run] [--json]", + "", + "Declaratively unpins a Pane using the Pane UI's favorite/pin star. Repeating the command is idempotent and never changes focus. Use --dry-run to validate and preview without mutating.", + "", + "Options:", + " --pane ", + " --pane-dir ", + " --json", + " --dry-run", + " --yes" + ], "panels": [ "Terminal-backed panel commands.", "", @@ -1511,6 +1597,7 @@ "Kick off discussion", "--source", "agent", + "--pinned", "--dry-run", "--yes", "--json" @@ -1534,6 +1621,22 @@ "--yes", "--json" ], + [ + "panes", + "pin", + "--pane", + "session-1", + "--yes", + "--json" + ], + [ + "panes", + "unpin", + "--pane", + "session-1", + "--yes", + "--json" + ], [ "panels", "list", @@ -2119,6 +2222,9 @@ "sessionPrompt": { "type": "string" }, + "pinned": { + "type": "boolean" + }, "tool": { "oneOf": [ { @@ -2219,7 +2325,8 @@ "required": [ "ok", "index", - "name" + "name", + "pinned" ], "properties": { "ok": { @@ -2231,6 +2338,9 @@ "name": { "type": "string" }, + "pinned": { + "type": "boolean" + }, "sessionId": { "type": "string" }, @@ -2519,7 +2629,8 @@ "status", "worktreePath", "repoId", - "panelCount" + "panelCount", + "pinned" ], "properties": { "id": { @@ -2546,6 +2657,9 @@ "panelCount": { "type": "number" }, + "pinned": { + "type": "boolean" + }, "createdAt": { "type": "string" }, @@ -2583,6 +2697,51 @@ }, "additionalProperties": false }, + "panePinRequest": { + "type": "object", + "required": [ + "paneId", + "pinned" + ], + "properties": { + "paneId": { + "type": "string" + }, + "pinned": { + "type": "boolean" + }, + "dryRun": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "panePinResult": { + "type": "object", + "required": [ + "ok", + "paneId", + "pinned" + ], + "properties": { + "ok": { + "const": true + }, + "paneId": { + "type": "string" + }, + "pinned": { + "type": "boolean" + }, + "dryRun": { + "const": true + }, + "favoritePinnedAt": { + "type": "string" + } + }, + "additionalProperties": false + }, "paneArchiveResult": { "oneOf": [ { @@ -3653,14 +3812,14 @@ "rules": [ "Start with `runpane doctor --json` to understand wrapper, platform, daemon reachability, and the next safe commands before mutating Pane state.", "In a Pane repository checkout, if `runpane` is not on PATH, use the built local wrapper with Node 22, for example `PATH=/opt/homebrew/opt/node@22/bin:$PATH node packages/runpane/dist/cli.js doctor --json`.", - "Happy path for any user request to use Pane/RunPane: run `runpane doctor --json`, read `runpane agent-context --json`, resolve or add the saved base repo, create the requested visible Pane with a complete command such as `runpane panes create --repo --name --agent --prompt \"\" --source agent --no-focus --wait-ready --yes --json` (or use `--tool-command ` instead of `--agent `), then validate with `panels wait` or `panels screen`.", + "Happy path for any user request to use Pane/RunPane: run `runpane doctor --json`, read `runpane agent-context --json`, resolve or add the saved base repo, create the requested visible Pane with a complete command such as `runpane panes create --repo --name --agent --prompt \"\" --source agent --pinned --no-focus --wait-ready --yes --json` when it should stay visible (or omit `--pinned`, or use `--tool-command ` instead of `--agent `), then validate with `panels wait` or `panels screen`.", "Treat Pane as the user's visible cockpit for watching/co-driving work. Do not create Panes or panels for private delegation unless the user asked for visible Pane orchestration or the result should appear in the Pane app.", "Register the saved base repository once with `repos add`; do not register a pre-created worktree as a separate repo unless the user explicitly asks.", "Use `panes create` for separate visible Panes (Pane sessions) for feature/PR work. Pane creates and owns the worktree/branch for each new Pane.", "Use `panels create` for reviewer/helper/clean-context tabs that should stay inside an existing Pane and share that Pane's worktree.", "For private background decomposition, use your normal subagent/worktree mechanism instead of Pane.", "Skill routing: when the user says `discussion`, `plan`, `simple-plan`, `create-plan`, or `implement`, or asks for those behaviors, treat three references as peer context: Pane's local skill cache under `/skills/`, the Pane Chat orchestrator handoff at `/skills/pane-chat/runpane-orchestrator.md` when present, and the workflow map at https://github.com/dcouple/skills/raw/main/docs/readme-workflow-map.png. For where the local cache and Pane Chat bootstrap live in Pane, reference https://github.com/dcouple/Pane/pull/291. Use GitHub reads against https://github.com/dcouple/skills/tree/main/parsa only to inspect or refresh referenced skill files; do not clone or install it unless the user asks.", - "When an agent creates Panes or panels, pass `--source agent --no-focus --wait-ready --yes --json` unless the user explicitly wants focus moved.", + "When an agent creates Panes or panels, pass `--source agent --no-focus --wait-ready --yes --json` unless the user explicitly wants focus moved; add `--pinned` when declaring a Pane that should remain in the UI's favorite/pin set.", "Use `runpane agent-context --json` for the full agent-facing CLI context, or `runpane agent-context --command --json` for one detailed command definition.", "For `agent-context --command`, use canonical spaced names like `panes create`; copied forms like `panes.create` or `runpane panes create` are accepted too.", "Use `runpane repos list --json` to find the saved repository when unsure after doctor shows the daemon is reachable.", @@ -3741,6 +3900,7 @@ "--initial-input-file ", "--from-json ", "--source ", + "--pinned", "--no-focus", "--focus", "--wait-ready", @@ -3761,6 +3921,26 @@ "--json" ] }, + { + "name": "panes pin", + "summary": "Declaratively pin a Pane (the Pane UI's favorite/pin star) without changing focus.", + "arguments": [ + "--pane ", + "--yes", + "--dry-run", + "--json" + ] + }, + { + "name": "panes unpin", + "summary": "Declaratively unpin a Pane (the Pane UI's favorite/pin star) without changing focus.", + "arguments": [ + "--pane ", + "--yes", + "--dry-run", + "--json" + ] + }, { "name": "panels create", "summary": "Create reviewer/helper terminal tabs inside an existing Pane; they share that Pane's worktree.", @@ -4248,11 +4428,21 @@ "required": false, "description": "Explicitly focus the created pane." }, + { + "name": "--pinned", + "required": false, + "description": "Create the pane already pinned (the Pane UI's favorite/pin star); does not imply focus." + }, { "name": "--yes", "required": false, "description": "Skip confirmation for mutating commands." }, + { + "name": "--dry-run", + "required": false, + "description": "Validate and preview without mutating." + }, { "name": "--json", "required": false, @@ -4290,7 +4480,7 @@ "`panes create` is for user-visible Pane orchestration, not the agent's default private delegation mechanism.", "Register the saved base repository once. Pane creates and owns the worktree/branch for each new Pane.", "Use `panels create` instead when a reviewer/helper should share an existing Pane's worktree.", - "Agent-created Panes should pass `--source agent --no-focus --wait-ready --yes --json` unless the user explicitly wants focus moved.", + "Agent-created Panes should pass `--source agent --no-focus --wait-ready --yes --json` unless the user explicitly wants focus moved; add `--pinned` when the Pane should remain in the UI's favorite/pin set.", "The built-in agent templates come from the runpane contract; custom terminal commands can pass agent-specific flags when requested by the user.", "Use --initial-input-file for multi-line prompts or shell-sensitive initial input.", "When the JSON result includes nextCommand, run it to validate that the terminal produced output before reporting success.", @@ -4351,6 +4541,90 @@ "Archiving a main-repo Pane (no Pane-managed worktree) always succeeds immediately since nothing is deleted from disk." ] }, + "panes pin": { + "name": "panes pin", + "summary": "Declaratively pin a Pane; pinned is the Pane UI's favorite/pin star.", + "details": "Sets the requested pinned state to true without reading and toggling it. Repeating the command is idempotent and pinning never changes focus.", + "requiresPaneDaemon": true, + "mutates": true, + "arguments": [ + { + "name": "--pane", + "value": "", + "required": true, + "description": "Pane/session id to pin." + }, + { + "name": "--yes", + "required": false, + "description": "Skip confirmation for mutating commands." + }, + { + "name": "--dry-run", + "required": false, + "description": "Validate and preview without mutating." + }, + { + "name": "--json", + "required": false, + "description": "Print machine-readable output." + } + ], + "examples": [ + "runpane panes pin --pane --yes --json", + "runpane panes pin --pane --dry-run --json" + ], + "jsonSchemas": [ + "panePinRequest", + "panePinResult" + ], + "notes": [ + "This is a declarative set, not a toggle: retries leave an already-pinned Pane pinned.", + "Pinning does not focus the Pane." + ] + }, + "panes unpin": { + "name": "panes unpin", + "summary": "Declaratively unpin a Pane; pinned is the Pane UI's favorite/pin star.", + "details": "Sets the requested pinned state to false without reading and toggling it. Repeating the command is idempotent and unpinning never changes focus.", + "requiresPaneDaemon": true, + "mutates": true, + "arguments": [ + { + "name": "--pane", + "value": "", + "required": true, + "description": "Pane/session id to unpin." + }, + { + "name": "--yes", + "required": false, + "description": "Skip confirmation for mutating commands." + }, + { + "name": "--dry-run", + "required": false, + "description": "Validate and preview without mutating." + }, + { + "name": "--json", + "required": false, + "description": "Print machine-readable output." + } + ], + "examples": [ + "runpane panes unpin --pane --yes --json", + "runpane panes unpin --pane --dry-run --json" + ], + "jsonSchemas": [ + "panePinRequest", + "panePinResult" + ], + "notes": [ + "This is a declarative set, not a toggle: retries leave an already-unpinned Pane unpinned.", + "Unpinning does not focus the Pane." + ] + }, "panels list": { "name": "panels list", "summary": "List tool panels inside a Pane session.", diff --git a/contracts/runpane/schema.json b/contracts/runpane/schema.json index 6bd543aa..ce95f20f 100644 --- a/contracts/runpane/schema.json +++ b/contracts/runpane/schema.json @@ -350,6 +350,8 @@ "panes list", "panes create", "panes archive", + "panes pin", + "panes unpin", "panels", "panels create", "panels list", @@ -401,6 +403,12 @@ "panes archive": { "$ref": "#/$defs/helpLines" }, + "panes pin": { + "$ref": "#/$defs/helpLines" + }, + "panes unpin": { + "$ref": "#/$defs/helpLines" + }, "panels": { "$ref": "#/$defs/helpLines" }, diff --git a/docs/RUNPANE_CLI_CONTRACT.md b/docs/RUNPANE_CLI_CONTRACT.md index a348b7ab..641fb638 100644 --- a/docs/RUNPANE_CLI_CONTRACT.md +++ b/docs/RUNPANE_CLI_CONTRACT.md @@ -187,6 +187,8 @@ Brief tools: - `panes list`: List Pane sessions, optionally scoped to a saved repository. - `panes create`: Create user-visible Panes (Pane sessions) backed by Pane-managed worktrees for feature/PR work and open terminal-backed tool tabs. - `panes archive`: Archive a Pane exactly like the UI Archive action, including safe removal of its Pane-managed git worktree. +- `panes pin`: Declaratively pin a Pane (the Pane UI's favorite/pin star) without changing focus. +- `panes unpin`: Declaratively unpin a Pane (the Pane UI's favorite/pin star) without changing focus. - `panels create`: Create reviewer/helper terminal tabs inside an existing Pane; they share that Pane's worktree. - `panels list`: List tool panels inside a Pane session. - `panels output`: Read recent terminal output from a panel. @@ -298,6 +300,7 @@ These flags are consumed by local daemon-control commands: --wait-ready --no-focus --focus +--pinned --force ``` diff --git a/main/src/database/database.favorite-pinning.test.ts b/main/src/database/database.favorite-pinning.test.ts new file mode 100644 index 00000000..9961683a --- /dev/null +++ b/main/src/database/database.favorite-pinning.test.ts @@ -0,0 +1,59 @@ +import fs from 'fs'; +import os from 'os'; +import path from 'path'; +import { afterEach, describe, expect, it } from 'vitest'; +import { DatabaseService } from './database'; + +const tempDirs: string[] = []; + +afterEach(() => { + for (const tempDir of tempDirs.splice(0)) { + fs.rmSync(tempDir, { recursive: true, force: true }); + } +}); + +describe('session favorite pinning persistence', () => { + it('preserves declarative pin state and timestamp across database reopens', () => { + const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pane-favorite-pinning-')); + tempDirs.push(tempDir); + const databasePath = path.join(tempDir, 'sessions.db'); + + const first = new DatabaseService(databasePath); + first.initialize(); + const project = first.createProject('Repo', path.join(tempDir, 'repo')); + first.createSession({ + id: 'pinned-session', + name: 'Pinned Session', + initial_prompt: '', + worktree_name: 'pinned-session', + worktree_path: path.join(tempDir, 'repo-pinned-session'), + project_id: project.id, + tool_type: 'none', + }); + + const pinned = first.setSessionFavorite('pinned-session', true); + const pinnedAgain = first.setSessionFavorite('pinned-session', true); + expect(Boolean(pinned?.is_favorite)).toBe(true); + expect(pinned?.favorite_pinned_at).not.toBeNull(); + expect(pinnedAgain?.favorite_pinned_at).toBe(pinned?.favorite_pinned_at); + first.close(); + + const second = new DatabaseService(databasePath); + second.initialize(); + expect(Boolean(second.getSession('pinned-session')?.is_favorite)).toBe(true); + expect(second.getSession('pinned-session')?.favorite_pinned_at).toBe(pinned?.favorite_pinned_at); + + const unpinned = second.setSessionFavorite('pinned-session', false); + const unpinnedAgain = second.setSessionFavorite('pinned-session', false); + expect(Boolean(unpinned?.is_favorite)).toBe(false); + expect(unpinned?.favorite_pinned_at).toBeNull(); + expect(unpinnedAgain?.favorite_pinned_at).toBeNull(); + second.close(); + + const third = new DatabaseService(databasePath); + third.initialize(); + expect(Boolean(third.getSession('pinned-session')?.is_favorite)).toBe(false); + expect(third.getSession('pinned-session')?.favorite_pinned_at).toBeNull(); + third.close(); + }); +}); diff --git a/main/src/database/database.ts b/main/src/database/database.ts index f51d0bfe..da7f6aeb 100644 --- a/main/src/database/database.ts +++ b/main/src/database/database.ts @@ -3129,6 +3129,20 @@ export class DatabaseService { return this.getSession(id); } + setSessionFavorite(id: string, pinned: boolean): Session | undefined { + this.db.prepare(` + UPDATE sessions SET + is_favorite = @pinned, + favorite_pinned_at = CASE WHEN @pinned + THEN COALESCE(favorite_pinned_at, CURRENT_TIMESTAMP) + ELSE NULL END, + updated_at = CURRENT_TIMESTAMP + WHERE id = @id + `).run({ id, pinned: pinned ? 1 : 0 }); + + return this.getSession(id); + } + markSessionAsViewed(id: string): Session | undefined { this.db .prepare( diff --git a/main/src/ipc/runpane.test.ts b/main/src/ipc/runpane.test.ts index fffd403b..d7996858 100644 --- a/main/src/ipc/runpane.test.ts +++ b/main/src/ipc/runpane.test.ts @@ -197,6 +197,8 @@ function registerSessionsDeleteStub( describe('runpane IPC handlers', () => { beforeEach(() => { + session.isFavorite = undefined; + session.favoritePinnedAt = undefined; vi.mocked(panelManager.createPanel).mockReset(); vi.mocked(panelManager.getPanel).mockReset(); vi.mocked(panelManager.getPanelsForSession).mockReset(); @@ -436,6 +438,7 @@ describe('runpane IPC handlers', () => { repoId: project.id, repoName: project.name, panelCount: 1, + pinned: false, createdAt: '2026-01-01T00:00:00.000Z', }], }); @@ -1152,6 +1155,7 @@ describe('runpane IPC handlers', () => { projectId: project.id, baseBranch: 'main', toolType: 'none', + startPinned: undefined, activateOnCreate: false, }, { timeoutMs: 1234 }); expect(panelManager.createPanel).toHaveBeenCalledWith({ @@ -1187,6 +1191,165 @@ describe('runpane IPC handlers', () => { }); }); + it('creates a pinned pane without focusing it', async () => { + vi.mocked(panelManager.createPanel).mockResolvedValue({ + ...terminalPanel, + state: { ...terminalPanel.state, isActive: false }, + }); + const databaseRow = { + id: session.id, + is_favorite: 0, + favorite_pinned_at: null as string | null, + }; + const services = createServices({ + databaseService: { + ...createServices().databaseService, + getSession: vi.fn(() => databaseRow), + } as never, + taskQueue: { + createSessionAndWait: vi.fn(async (request: { startPinned?: boolean }) => { + databaseRow.is_favorite = request.startPinned ? 1 : 0; + databaseRow.favorite_pinned_at = request.startPinned ? '2026-07-21 12:00:00' : null; + session.isFavorite = request.startPinned; + session.favoritePinnedAt = databaseRow.favorite_pinned_at ?? undefined; + return { sessionId: session.id }; + }), + } as never, + }); + const registry = createRegistry(services); + + const result = await registry.invoke('runpane:panes:create', [{ + repo: 'active', + noFocus: true, + panes: [{ + name: 'pinned-background-pane', + pinned: true, + tool: { agent: 'codex' }, + }], + }]); + + expect(services.taskQueue?.createSessionAndWait).toHaveBeenCalledWith( + expect.objectContaining({ startPinned: true, activateOnCreate: false }), + expect.any(Object), + ); + expect(result).toMatchObject({ + ok: true, + items: [{ pinned: true, focused: false }], + }); + expect(databaseRow.is_favorite).toBe(1); + expect(databaseRow.favorite_pinned_at).not.toBeNull(); + }); + + it('sets pinned state declaratively and idempotently', async () => { + const databaseRow = { + id: session.id, + is_favorite: 0, + favorite_pinned_at: null as string | null, + }; + const setSessionFavorite = vi.fn((_id: string, pinned: boolean) => { + databaseRow.is_favorite = pinned ? 1 : 0; + databaseRow.favorite_pinned_at = pinned + ? databaseRow.favorite_pinned_at ?? '2026-07-21 12:00:00' + : null; + return databaseRow; + }); + const emit = vi.fn(); + const services = createServices({ + databaseService: { + ...createServices().databaseService, + setSessionFavorite, + } as never, + sessionManager: { + ...createServices().sessionManager, + emit, + } as never, + }); + const registry = createRegistry(services); + + const firstPin = await registry.invoke('runpane:panes:pin', [{ paneId: session.id, pinned: true }]); + const pinTimestamp = databaseRow.favorite_pinned_at; + const secondPin = await registry.invoke('runpane:panes:pin', [{ paneId: session.id, pinned: true }]); + const listedPinned = await registry.invoke('runpane:panes:list', [{ repo: 'active' }]); + + expect(firstPin).toEqual({ + ok: true, + paneId: session.id, + pinned: true, + favoritePinnedAt: pinTimestamp, + }); + expect(secondPin).toEqual(firstPin); + expect(databaseRow.favorite_pinned_at).toBe(pinTimestamp); + expect(listedPinned).toMatchObject({ panes: [{ paneId: session.id, pinned: true }] }); + + const firstUnpin = await registry.invoke('runpane:panes:pin', [{ paneId: session.id, pinned: false }]); + const secondUnpin = await registry.invoke('runpane:panes:pin', [{ paneId: session.id, pinned: false }]); + + expect(firstUnpin).toEqual({ ok: true, paneId: session.id, pinned: false, favoritePinnedAt: undefined }); + expect(secondUnpin).toEqual(firstUnpin); + expect(databaseRow).toMatchObject({ is_favorite: 0, favorite_pinned_at: null }); + expect(setSessionFavorite).toHaveBeenCalledTimes(4); + expect(emit).toHaveBeenCalledTimes(4); + expect(emit).toHaveBeenLastCalledWith('session-updated', expect.objectContaining({ + id: session.id, + isFavorite: false, + favoritePinnedAt: undefined, + })); + }); + + it('dry-runs pinned state changes without mutating or emitting', async () => { + session.isFavorite = false; + session.favoritePinnedAt = undefined; + const databaseRow = { + id: session.id, + is_favorite: 0, + favorite_pinned_at: null as string | null, + }; + const setSessionFavorite = vi.fn((_id: string, pinned: boolean) => { + databaseRow.is_favorite = pinned ? 1 : 0; + databaseRow.favorite_pinned_at = pinned ? '2026-07-21 12:00:00' : null; + return databaseRow; + }); + const emit = vi.fn(); + const services = createServices({ + databaseService: { + ...createServices().databaseService, + setSessionFavorite, + } as never, + sessionManager: { + ...createServices().sessionManager, + emit, + } as never, + }); + const registry = createRegistry(services); + + const result = await registry.invoke('runpane:panes:pin', [{ + paneId: session.id, + pinned: true, + dryRun: true, + }]); + + expect(result).toEqual({ + ok: true, + dryRun: true, + paneId: session.id, + pinned: true, + favoritePinnedAt: undefined, + }); + expect(databaseRow).toMatchObject({ is_favorite: 0, favorite_pinned_at: null }); + expect(session.isFavorite).toBe(false); + expect(setSessionFavorite).not.toHaveBeenCalled(); + expect(emit).not.toHaveBeenCalled(); + }); + + it('rejects missing and non-boolean pinned state', async () => { + const registry = createRegistry(); + + await expect(registry.invoke('runpane:panes:pin', [{ paneId: session.id }])) + .rejects.toThrow('pinned as a boolean'); + await expect(registry.invoke('runpane:panes:pin', [{ paneId: session.id, pinned: 'yes' }])) + .rejects.toThrow('pinned as a boolean'); + }); + it('serializes multi-pane session creation before enqueueing the next pane', async () => { vi.mocked(panelManager.createPanel).mockResolvedValue({ id: 'panel-1', diff --git a/main/src/ipc/runpane.ts b/main/src/ipc/runpane.ts index 11ac5ca1..e37080e5 100644 --- a/main/src/ipc/runpane.ts +++ b/main/src/ipc/runpane.ts @@ -29,6 +29,8 @@ import type { RunpanePaneArchiveSuccessResult, RunpanePaneListRequest, RunpanePaneListResult, + RunpanePanePinRequest, + RunpanePanePinResult, RunpanePaneCreateFailureItem, RunpanePaneCreateItem, RunpanePaneCreateRequest, @@ -73,6 +75,7 @@ const RUNPANE_CHANNELS = [ 'runpane:repos:add', 'runpane:panes:list', 'runpane:panes:create', + 'runpane:panes:pin', 'runpane:panes:archive', 'runpane:panels:create', 'runpane:panels:list', @@ -233,6 +236,38 @@ export function registerRunpaneHandlers( }, result => ({ repoId: result.repo?.id, resultCount: result.panes.length })); }); + commandRegistry.register('runpane:panes:pin', async (request: unknown): Promise => { + return withRunpaneAction(services, 'panes:pin', {}, () => { + const normalized = parsePanePinRequest(request); + const pane = resolvePane(sessionManager, normalized.paneId); + if (normalized.dryRun) { + return { + ok: true, + dryRun: true, + paneId: normalized.paneId, + pinned: normalized.pinned, + favoritePinnedAt: pane.favoritePinnedAt, + }; + } + + const updatedSession = databaseService.setSessionFavorite(normalized.paneId, normalized.pinned); + if (!updatedSession) { + throw new Error(`Failed to update pinned state for Pane ${normalized.paneId}`); + } + + pane.isFavorite = Boolean(updatedSession.is_favorite); + pane.favoritePinnedAt = updatedSession.favorite_pinned_at ?? undefined; + sessionManager.emit('session-updated', pane); + + return { + ok: true, + paneId: normalized.paneId, + pinned: Boolean(updatedSession.is_favorite), + favoritePinnedAt: updatedSession.favorite_pinned_at ?? undefined, + }; + }, result => ({ paneId: result.paneId })); + }); + commandRegistry.register('runpane:panes:create', async (request: unknown): Promise => { return withRunpaneAction(services, 'panes:create', {}, async () => { const normalized = parsePaneCreateRequest(request); @@ -247,6 +282,7 @@ export function registerRunpaneHandlers( ok: true, index, name: pane.name, + pinned: Boolean(pane.pinned), tool: describeTool(resolveToolSpec(pane.tool)), })), }; @@ -574,6 +610,7 @@ function sessionToPaneSummary(session: Session, project: Project) { repoId: project.id, repoName: project.name, panelCount: panelManager.getPanelsForSession(session.id).length, + pinned: Boolean(session.isFavorite), createdAt: toIsoString(session.createdAt), lastActivity: toIsoString(session.lastActivity), archived: session.archived || undefined, @@ -751,6 +788,7 @@ async function createPaneItem( projectId: repo.id, baseBranch: item.baseBranch, toolType: 'none', + startPinned: item.pinned, activateOnCreate: options.activate !== false, }, { timeoutMs: options.timeoutMs }); @@ -770,6 +808,7 @@ async function createPaneItem( ok: itemOk, index, name: item.name, + pinned: Boolean(session.isFavorite), sessionId: session.id, paneId: session.id, panelId: panel.id, @@ -1745,6 +1784,26 @@ function parsePaneArchiveRequest(value: unknown): RunpanePaneArchiveRequest { }; } +function parsePanePinRequest(value: unknown): RunpanePanePinRequest { + if (!isRecord(value)) { + throw new Error('Pane pin request must be an object'); + } + + const paneId = optionalString(value.paneId)?.trim(); + if (!paneId) { + throw new Error('Pane pin request must include a paneId'); + } + if (typeof value.pinned !== 'boolean') { + throw new Error('Pane pin request must include pinned as a boolean'); + } + + return { + paneId, + pinned: value.pinned, + dryRun: typeof value.dryRun === 'boolean' ? value.dryRun : undefined, + }; +} + function resolvePanel(panelId: string): ToolPanel { const panel = panelManager.getPanel(panelId); if (!panel) { @@ -1767,6 +1826,7 @@ function parsePaneCreateItem(value: unknown, index: number): RunpanePaneCreateIt worktreeName: optionalString(value.worktreeName), baseBranch: optionalString(value.baseBranch), sessionPrompt: optionalString(value.sessionPrompt), + pinned: typeof value.pinned === 'boolean' ? value.pinned : undefined, tool: parseRunpaneToolSpec(value.tool, `Pane create item ${index}`), }; } diff --git a/packages/runpane-py/src/runpane/cli.py b/packages/runpane-py/src/runpane/cli.py index 6cd21f82..845f3048 100644 --- a/packages/runpane-py/src/runpane/cli.py +++ b/packages/runpane-py/src/runpane/cli.py @@ -30,6 +30,7 @@ run_panes_archive, run_panes_create, run_panes_list, + run_panes_pin, run_repos_add, run_repos_list, ) @@ -114,6 +115,7 @@ class ParsedArgs: source: Optional[str] = None no_focus: bool = False focus: bool = False + pinned: bool = False composer_strategy: Optional[str] = None force: bool = False help_topic: Optional[str] = None @@ -173,6 +175,10 @@ def dispatch_parsed_command(parsed: ParsedArgs, telemetry_context: WrapperTeleme return run_panes_create(parsed) if parsed.command == "panes archive": return run_panes_archive(parsed) + if parsed.command == "panes pin": + return run_panes_pin(parsed, True) + if parsed.command == "panes unpin": + return run_panes_pin(parsed, False) if parsed.command == "panels list": return run_panels_list(parsed) if parsed.command == "panels create": @@ -465,6 +471,9 @@ def parse_local_boolean_flag(parsed: ParsedArgs, flag: str) -> None: if flag == "--focus": parsed.focus = True return + if flag == "--pinned": + parsed.pinned = True + return if flag == "--force": parsed.force = True return @@ -596,6 +605,8 @@ def is_runpane_local_command(command: str) -> bool: "panes list", "panes create", "panes archive", + "panes pin", + "panes unpin", "panels create", "panels list", "panels output", diff --git a/packages/runpane-py/src/runpane/generated_contract.py b/packages/runpane-py/src/runpane/generated_contract.py index 4eb11557..f0878cda 100644 --- a/packages/runpane-py/src/runpane/generated_contract.py +++ b/packages/runpane-py/src/runpane/generated_contract.py @@ -1,4 +1,4 @@ # Generated by scripts/generate-runpane-contract.js. Do not edit by hand. import json -RUNPANE_CONTRACT = json.loads("{\n \"$schema\": \"./schema.json\",\n \"schemaVersion\": 1,\n \"name\": \"runpane\",\n \"description\": \"Thin installer and local control CLI for Pane\",\n \"packageInstallPolicy\": [\n \"The packages must not download, install, or configure Pane during package installation.\",\n \"Work starts only when a user runs `runpane ...`.\"\n ],\n \"compatibility\": {\n \"node\": \">=18.17.0\",\n \"python\": \">=3.8\"\n },\n \"terminology\": {\n \"repo\": \"Saved Pane repository/project record\",\n \"pane\": \"User-visible Pane session\",\n \"tool\": \"Terminal-backed tab\",\n \"agent\": \"Built-in agent command template\"\n },\n \"defaults\": {\n \"target\": \"client\",\n \"paneVersion\": \"latest\",\n \"channel\": \"stable\",\n \"format\": \"auto\",\n \"dryRun\": false,\n \"yes\": false,\n \"verbose\": false\n },\n \"enums\": {\n \"installTargets\": [\n \"client\",\n \"daemon\"\n ],\n \"artifactFormats\": [\n \"auto\",\n \"appimage\",\n \"deb\",\n \"dmg\",\n \"zip\",\n \"exe\"\n ],\n \"channels\": [\n \"stable\",\n \"nightly\"\n ],\n \"agents\": [\n \"codex\",\n \"claude\"\n ]\n },\n \"agentTemplates\": {\n \"codex\": {\n \"title\": \"Codex\",\n \"command\": \"codex --yolo\",\n \"description\": \"Open a Codex terminal tab and allow the initial input to drive the agent.\"\n },\n \"claude\": {\n \"title\": \"Claude Code\",\n \"command\": \"claude --dangerously-skip-permissions\",\n \"description\": \"Open a Claude Code terminal tab and allow the initial input to drive the agent.\"\n }\n },\n \"commands\": [\n {\n \"name\": \"help\",\n \"summary\": \"Show help for runpane or a specific command.\",\n \"usage\": [\n \"runpane help [command]\"\n ]\n },\n {\n \"name\": \"setup\",\n \"summary\": \"Open the guided setup wizard for install, remote host setup, update, and diagnostics.\",\n \"usage\": [\n \"runpane setup\"\n ],\n \"interactiveEntrypoint\": true\n },\n {\n \"name\": \"install\",\n \"summary\": \"Install Pane on this machine or configure this machine as a remote daemon host.\",\n \"usage\": [\n \"runpane install [client|daemon] [options]\"\n ],\n \"defaultTarget\": \"client\",\n \"targets\": [\n \"client\",\n \"daemon\"\n ],\n \"unknownDaemonFlagsForwarded\": true\n },\n {\n \"name\": \"update\",\n \"summary\": \"Update the Pane desktop app using the same artifact path as install client.\",\n \"usage\": [\n \"runpane update [options]\"\n ],\n \"target\": \"client\"\n },\n {\n \"name\": \"version\",\n \"summary\": \"Print the runpane wrapper version without contacting, launching, or focusing Pane.\",\n \"usage\": [\n \"runpane version\",\n \"runpane --version\"\n ]\n },\n {\n \"name\": \"doctor\",\n \"summary\": \"Run platform, release, installed Pane, daemon reachability, and remote setup diagnostics.\",\n \"usage\": [\n \"runpane doctor [--json] [--pane-dir ] [--pane-path ] [--format ] [--verbose]\"\n ],\n \"jsonSchemas\": [\n \"doctorResult\"\n ]\n },\n {\n \"name\": \"agents doctor\",\n \"summary\": \"Diagnose whether a built-in agent command is available in a Pane repository environment.\",\n \"usage\": [\n \"runpane agents doctor --agent [--repo ] [--json]\"\n ],\n \"jsonSchemas\": [\n \"agentDoctorResult\"\n ]\n },\n {\n \"name\": \"agent-context\",\n \"summary\": \"Print token-efficient Pane command context for coding agents.\",\n \"usage\": [\n \"runpane agent-context [--json]\",\n \"runpane agent-context --command [--json]\"\n ],\n \"jsonSchemas\": [\n \"agentContextBriefResult\",\n \"agentContextCommandResult\"\n ]\n },\n {\n \"name\": \"repos list\",\n \"summary\": \"List repositories saved in the running Pane app.\",\n \"usage\": [\n \"runpane repos list [--json] [--pane-dir ]\"\n ],\n \"jsonSchemas\": [\n \"repoListResult\"\n ]\n },\n {\n \"name\": \"repos add\",\n \"summary\": \"Register an existing git repository with the running Pane app.\",\n \"usage\": [\n \"runpane repos add --path [--name ] [--json] [--yes]\"\n ],\n \"mutates\": true,\n \"jsonSchemas\": [\n \"repoAddRequest\",\n \"repoAddResult\"\n ]\n },\n {\n \"name\": \"panes list\",\n \"summary\": \"List Pane sessions in a saved repository.\",\n \"usage\": [\n \"runpane panes list [--repo ] [--json]\"\n ],\n \"jsonSchemas\": [\n \"paneListResult\"\n ]\n },\n {\n \"name\": \"panes create\",\n \"summary\": \"Create user-visible Panes (Pane sessions) backed by Pane-managed worktrees for feature/PR work and open terminal-backed tool tabs.\",\n \"usage\": [\n \"runpane panes create --repo --name --agent [--source user|agent] [--focus|--no-focus] [options]\",\n \"runpane panes create --from-json [--yes] [--json]\"\n ],\n \"mutates\": true,\n \"jsonSchemas\": [\n \"paneCreateRequest\",\n \"paneCreateResult\"\n ]\n },\n {\n \"name\": \"panes archive\",\n \"summary\": \"Archive a Pane (session) exactly like the UI Archive action, including safe removal of its Pane-managed git worktree.\",\n \"usage\": [\n \"runpane panes archive --pane [--source user|agent] [--force] --yes [--json]\"\n ],\n \"mutates\": true,\n \"jsonSchemas\": [\n \"paneArchiveRequest\",\n \"paneArchiveResult\"\n ]\n },\n {\n \"name\": \"panels create\",\n \"summary\": \"Create a terminal-backed tool panel inside an existing Pane session.\",\n \"usage\": [\n \"runpane panels create --pane --agent [--source user|agent] [--focus|--no-focus] [--wait-ready] --yes [--json]\",\n \"runpane panels create --pane --tool-command [--title ] [--focus|--no-focus] --yes [--json]\"\n ],\n \"mutates\": true,\n \"jsonSchemas\": [\n \"panelCreateRequest\",\n \"panelCreateResult\"\n ]\n },\n {\n \"name\": \"panels list\",\n \"summary\": \"List tool panels inside a Pane session.\",\n \"usage\": [\n \"runpane panels list --pane <pane-id> [--json]\"\n ],\n \"jsonSchemas\": [\n \"panelListResult\"\n ]\n },\n {\n \"name\": \"panels output\",\n \"summary\": \"Read recent terminal output from a panel.\",\n \"usage\": [\n \"runpane panels output --panel <panel-id> [--limit <count>] [--json]\"\n ],\n \"jsonSchemas\": [\n \"panelOutputResult\"\n ]\n },\n {\n \"name\": \"panels screen\",\n \"summary\": \"Read a compact current-screen view from a terminal panel.\",\n \"usage\": [\n \"runpane panels screen --panel <panel-id> [--limit <count>] [--json]\"\n ],\n \"jsonSchemas\": [\n \"panelScreenResult\"\n ]\n },\n {\n \"name\": \"panels input\",\n \"summary\": \"Send input bytes to a terminal panel.\",\n \"usage\": [\n \"runpane panels input --panel <panel-id> (--text <text>|--input-file <path|->) --yes [--json]\"\n ],\n \"mutates\": true,\n \"jsonSchemas\": [\n \"panelInputRequest\",\n \"panelInputResult\"\n ]\n },\n {\n \"name\": \"panels submit\",\n \"summary\": \"Send text to a terminal panel and append a terminal Enter byte.\",\n \"usage\": [\n \"runpane panels submit --panel <panel-id> (--text <text>|--input-file <path|->) --yes [--json]\"\n ],\n \"mutates\": true,\n \"jsonSchemas\": [\n \"panelSubmitRequest\",\n \"panelSubmitResult\"\n ]\n },\n {\n \"name\": \"panels submit-composer\",\n \"summary\": \"Submit an agent composer using the panel-appropriate key sequence.\",\n \"usage\": [\n \"runpane panels submit-composer --panel <panel-id> [--strategy auto|codex-ctrl-enter|enter] --yes [--json]\"\n ],\n \"mutates\": true,\n \"jsonSchemas\": [\n \"panelSubmitComposerRequest\",\n \"panelSubmitComposerResult\"\n ]\n },\n {\n \"name\": \"panels wait\",\n \"summary\": \"Wait for a terminal panel to initialize, become ready/idle, or contain text.\",\n \"usage\": [\n \"runpane panels wait --panel <panel-id> [--for initialized|ready|idle|text] [--contains <text>] [--timeout-ms <ms>] [--interval-ms <ms>] [--json]\"\n ],\n \"jsonSchemas\": [\n \"panelWaitResult\"\n ]\n }\n ],\n \"flags\": {\n \"wrapper\": [\n {\n \"name\": \"--version\",\n \"value\": \"<latest|vX.Y.Z>\",\n \"description\": \"Pane release to install or inspect.\"\n },\n {\n \"name\": \"--download-dir\",\n \"value\": \"<path>\",\n \"description\": \"Directory for downloaded Pane release artifacts.\"\n },\n {\n \"name\": \"--pane-path\",\n \"value\": \"<path>\",\n \"description\": \"Use or inspect an existing Pane executable.\"\n },\n {\n \"name\": \"--format\",\n \"value\": \"<auto|appimage|deb|dmg|zip|exe>\",\n \"description\": \"Pane release artifact format.\"\n },\n {\n \"name\": \"--dry-run\",\n \"description\": \"Print the plan without downloading or installing.\"\n },\n {\n \"name\": \"--yes\",\n \"aliases\": [\n \"-y\"\n ],\n \"description\": \"Skip interactive prompts where possible.\"\n },\n {\n \"name\": \"--verbose\",\n \"description\": \"Print extra diagnostics.\"\n }\n ],\n \"remoteValue\": [\n {\n \"name\": \"--label\",\n \"value\": \"<name>\"\n },\n {\n \"name\": \"--prefer-tunnel\",\n \"value\": \"<tailscale|ssh|manual|auto>\"\n },\n {\n \"name\": \"--channel\",\n \"value\": \"<stable|nightly>\"\n },\n {\n \"name\": \"--base-url\",\n \"value\": \"<url>\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\"\n },\n {\n \"name\": \"--listen-port\",\n \"value\": \"<port>\"\n },\n {\n \"name\": \"--port\",\n \"value\": \"<port>\"\n },\n {\n \"name\": \"--repo-ref\",\n \"value\": \"<ref>\"\n }\n ],\n \"remoteBoolean\": [\n {\n \"name\": \"--auto-listen-port\"\n },\n {\n \"name\": \"--interactive-tailscale-setup\"\n },\n {\n \"name\": \"--no-install-service\"\n },\n {\n \"name\": \"--no-tailscale-serve\"\n },\n {\n \"name\": \"--print-only\"\n }\n ],\n \"localValue\": [\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"description\": \"Connect to a Pane daemon using this Pane data directory.\"\n },\n {\n \"name\": \"--repo\",\n \"value\": \"<selector>\",\n \"description\": \"Repository selector: active, id, exact path, or saved repository name.\"\n },\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"description\": \"Pane/session id to inspect.\"\n },\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"description\": \"Tool panel id to inspect or control.\"\n },\n {\n \"name\": \"--path\",\n \"value\": \"<path>\",\n \"description\": \"Existing git repository path to register with Pane.\"\n },\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"description\": \"Name for the registered repository or created pane/session.\"\n },\n {\n \"name\": \"--worktree-name\",\n \"value\": \"<name>\",\n \"description\": \"Worktree name to request. Defaults to --name.\"\n },\n {\n \"name\": \"--base-branch\",\n \"value\": \"<branch>\",\n \"description\": \"Base branch for the created worktree.\"\n },\n {\n \"name\": \"--agent\",\n \"value\": \"<codex|claude>\",\n \"description\": \"Built-in agent terminal template to open.\"\n },\n {\n \"name\": \"--tool-command\",\n \"value\": \"<command>\",\n \"description\": \"Custom terminal command to run instead of a built-in agent.\"\n },\n {\n \"name\": \"--title\",\n \"value\": \"<title>\",\n \"description\": \"Terminal tab title. Defaults to the selected agent title or Terminal.\"\n },\n {\n \"name\": \"--initial-input\",\n \"value\": \"<text>\",\n \"aliases\": [\n \"--prompt\"\n ],\n \"description\": \"Text to send to the terminal after the command is ready. --prompt is an alias.\"\n },\n {\n \"name\": \"--initial-input-file\",\n \"value\": \"<path|->\",\n \"description\": \"Read initial input from a file or stdin.\"\n },\n {\n \"name\": \"--from-json\",\n \"value\": \"<path|->\",\n \"description\": \"Read a full panes.create request JSON payload from a file or stdin.\"\n },\n {\n \"name\": \"--timeout-ms\",\n \"value\": \"<milliseconds>\",\n \"description\": \"Maximum time to wait for each pane creation job.\"\n },\n {\n \"name\": \"--ready-timeout-ms\",\n \"value\": \"<milliseconds>\",\n \"description\": \"Readiness wait timeout for panes create --wait-ready.\"\n },\n {\n \"name\": \"--concurrency\",\n \"value\": \"<count>\",\n \"description\": \"Accepted for forward compatibility. Pane currently serializes multi-pane session creation so queued jobs do not time out before starting.\"\n },\n {\n \"name\": \"--limit\",\n \"value\": \"<count>\",\n \"description\": \"Maximum recent output lines or records to read.\"\n },\n {\n \"name\": \"--for\",\n \"value\": \"<initialized|ready|idle|text>\",\n \"description\": \"Panel wait condition.\"\n },\n {\n \"name\": \"--contains\",\n \"value\": \"<text>\",\n \"description\": \"Text to wait for with panels wait --for text.\"\n },\n {\n \"name\": \"--interval-ms\",\n \"value\": \"<milliseconds>\",\n \"description\": \"Polling interval for panels wait.\"\n },\n {\n \"name\": \"--text\",\n \"value\": \"<text>\",\n \"description\": \"Text bytes to send to a terminal panel.\"\n },\n {\n \"name\": \"--input-file\",\n \"value\": \"<path|->\",\n \"description\": \"Read panel input from a file or stdin.\"\n },\n {\n \"name\": \"--source\",\n \"value\": \"<user|agent>\",\n \"description\": \"Identify whether a local-control mutation is user-initiated or agent/orchestrator-initiated.\"\n },\n {\n \"name\": \"--strategy\",\n \"value\": \"<auto|codex-ctrl-enter|enter>\",\n \"description\": \"Composer submit key sequence strategy for panels submit-composer.\"\n }\n ],\n \"localBoolean\": [\n {\n \"name\": \"--json\",\n \"description\": \"Print machine-readable JSON output.\"\n },\n {\n \"name\": \"--wait-ready\",\n \"description\": \"Wait for created terminal panels to be ready before returning.\"\n },\n {\n \"name\": \"--no-focus\",\n \"description\": \"Create the pane or panel in the background without changing focus.\"\n },\n {\n \"name\": \"--focus\",\n \"description\": \"Explicitly focus the created pane or panel.\"\n },\n {\n \"name\": \"--force\",\n \"description\": \"Archive even if the pane's branch has uncommitted, untracked, or unpushed changes.\"\n }\n ]\n },\n \"help\": {\n \"npm\": {\n \"default\": [\n \"Usage:\",\n \" runpane\",\n \" runpane setup\",\n \" runpane install [client|daemon] [options]\",\n \" runpane update [options]\",\n \" runpane version\",\n \" runpane doctor\",\n \" runpane agent-context [--json]\",\n \" runpane agents doctor --agent <codex|claude> [--repo <selector>] [--json]\",\n \" runpane repos list [--json]\",\n \" runpane repos add --path <path> [--name <name>]\",\n \" runpane panes list [--repo <selector>] [--json]\",\n \" runpane panes create --repo <selector> --name <name> --agent <codex|claude> [--source user|agent] [--focus|--no-focus] [--wait-ready]\",\n \" runpane panes archive --pane <pane-id> [--source user|agent] [--force] --yes\",\n \" runpane panels create --pane <pane-id> --agent <codex|claude> [--source user|agent] [--focus|--no-focus] --yes\",\n \" runpane panels list --pane <pane-id> [--json]\",\n \" runpane panels output --panel <panel-id> [--limit <count>] [--json]\",\n \" runpane panels screen --panel <panel-id> [--limit <count>] [--json]\",\n \" runpane panels input --panel <panel-id> (--text <text>|--input-file <path|->) --yes [--json]\",\n \" runpane panels submit --panel <panel-id> (--text <text>|--input-file <path|->) --yes [--json]\",\n \" runpane panels submit-composer --panel <panel-id> [--strategy auto|codex-ctrl-enter|enter] --yes [--json]\",\n \" runpane panels wait --panel <panel-id> [--for initialized|ready|idle|text] [--json]\",\n \" runpane help [command]\",\n \"\",\n \"Quick start:\",\n \" npx --yes runpane@latest\",\n \" npm i -g runpane && runpane setup\",\n \"\",\n \"Advanced examples:\",\n \" npx --yes runpane@latest install client\",\n \" npx --yes runpane@latest install daemon --label \\\"My Server\\\"\",\n \" pnpm dlx runpane@latest\",\n \"\",\n \"Common commands:\",\n \" runpane help\",\n \" runpane setup\",\n \" runpane install\",\n \" runpane doctor\",\n \"\",\n \"Agent discovery:\",\n \" runpane doctor --json\",\n \" runpane agent-context --json\",\n \" runpane agent-context --command \\\"<command>\\\" --json\",\n \"\",\n \"Run \\\"runpane help panes create\\\" for pane orchestration options.\"\n ],\n \"install\": [\n \"Usage:\",\n \" runpane install [client|daemon] [options]\",\n \"\",\n \"Examples:\",\n \" npx --yes runpane@latest install client\",\n \" npx --yes runpane@latest install daemon --label \\\"My Server\\\"\",\n \" pnpm dlx runpane@latest install daemon --prefer-tunnel ssh --label \\\"VM\\\"\",\n \"\",\n \"Wrapper options:\",\n \" --version <latest|vX.Y.Z> Pane release to install\",\n \" --format <auto|appimage|deb|dmg|zip|exe>\",\n \" --download-dir <path>\",\n \" --pane-path <path> Use an existing Pane executable\",\n \" --dry-run Print the plan without downloading\",\n \" --yes Skip interactive prompts where possible\",\n \" --verbose\",\n \"\",\n \"Daemon passthrough options:\",\n \" --label <name>\",\n \" --prefer-tunnel <tailscale|ssh|manual|auto>\",\n \" --channel <stable|nightly>\",\n \" --base-url <url>\",\n \" --pane-dir <path>\",\n \" --listen-port <port> / --port <port>\",\n \" --auto-listen-port\",\n \" --interactive-tailscale-setup\",\n \" --no-install-service\",\n \" --no-tailscale-serve\",\n \" --print-only\",\n \" --repo-ref <ref>\"\n ],\n \"setup\": [\n \"Usage:\",\n \" runpane setup\",\n \"\",\n \"Opens the guided setup for desktop install, remote host setup, update, and diagnostics.\",\n \"\",\n \"Quick start:\",\n \" npx --yes runpane@latest\",\n \" npm i -g runpane && runpane setup\"\n ],\n \"update\": [\n \"Usage:\",\n \" runpane update [options]\",\n \"\",\n \"Updates Pane using the same artifact selection as \\\"runpane install client\\\".\",\n \"\",\n \"Options:\",\n \" --version <latest|vX.Y.Z>\",\n \" --format <auto|appimage|deb|dmg|zip|exe>\",\n \" --download-dir <path>\",\n \" --pane-path <path>\",\n \" --dry-run\",\n \" --yes\",\n \" --verbose\"\n ],\n \"version\": [\n \"Usage:\",\n \" runpane version\",\n \" runpane --version\"\n ],\n \"doctor\": [\n \"Usage:\",\n \" runpane doctor [--json] [--pane-dir <path>] [--pane-path <path>] [--format <format>] [--verbose]\",\n \"\",\n \"Checks wrapper/runtime details, release metadata, installed Pane detection, and Pane daemon reachability.\",\n \"\",\n \"Options:\",\n \" --json Print a machine-readable environment report\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --pane-path <path> Inspect a specific Pane executable\",\n \" --format <format> Release artifact format to inspect\",\n \" --verbose Print extra diagnostics\",\n \"\",\n \"Agent discovery:\",\n \" runpane doctor --json\",\n \" runpane agent-context --json\"\n ],\n \"repos list\": [\n \"Usage:\",\n \" runpane repos list [--json] [--pane-dir <path>]\",\n \"\",\n \"Lists repositories saved in the running Pane app.\",\n \"\",\n \"Options:\",\n \" --json Print machine-readable output\",\n \" --pane-dir <path> Connect to a specific Pane data directory\"\n ],\n \"repos add\": [\n \"Usage:\",\n \" runpane repos add --path <path> [--name <name>] [--json] [--yes]\",\n \"\",\n \"Registers an existing git repository with the running Pane app.\",\n \"\",\n \"Options:\",\n \" --path <path> Existing git repository path\",\n \" --name <name> Saved repository name; defaults to the directory name\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --dry-run Validate and preview without adding the repo\",\n \" --yes Skip confirmation for mutating commands\"\n ],\n \"panes\": [\n \"Pane session commands.\",\n \"\",\n \"Usage:\",\n \" runpane panes <command> [options]\",\n \"\",\n \"Commands:\",\n \" runpane panes list [--repo <selector>] [--json]\",\n \" runpane panes create --repo <selector> --name <name> --agent <codex|claude> [options]\",\n \" runpane panes archive --pane <pane-id> [--force] [--source user|agent] --yes [--json]\",\n \"\",\n \"Run \\\"runpane help panes list\\\", \\\"runpane help panes create\\\", or \\\"runpane help panes archive\\\" for command-specific options.\"\n ],\n \"panes list\": [\n \"Usage:\",\n \" runpane panes list [--repo <selector>] [--json]\",\n \"\",\n \"Lists Pane sessions. Pass --repo to limit results to one saved repository.\",\n \"\",\n \"Options:\",\n \" --repo <selector> active, id, exact path, or saved repository name\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panes create\": [\n \"Usage:\",\n \" runpane panes create --repo <selector> --name <name> --agent <codex|claude> [options]\",\n \" runpane panes create --from-json <path|-> [--yes] [--json]\",\n \"\",\n \"Creates user-visible Panes (Pane sessions) for feature/PR work in a saved repository and opens a terminal-backed tool tab. Pane creates and owns the worktree/branch for each new Pane.\",\n \"\",\n \"Options:\",\n \" --repo <selector> active, id, exact path, or saved repository name\",\n \" --name <name> Pane/session name\",\n \" --worktree-name <name> Worktree name; defaults to --name\",\n \" --base-branch <branch> Base branch for the worktree\",\n \" --agent <codex|claude> Built-in terminal template\",\n \" --tool-command <command> Custom terminal command\",\n \" --title <title> Terminal tab title\",\n \" --initial-input <text> Text sent after the command is ready\",\n \" --prompt <text> Alias for --initial-input\",\n \" --initial-input-file <path|-> Read initial input from a file or stdin\",\n \" --from-json <path|-> Read a full request payload\",\n \" --timeout-ms <milliseconds> Pane creation timeout\",\n \" --wait-ready Wait for terminal readiness before returning\",\n \" --ready-timeout-ms <ms> Readiness wait timeout; defaults to 30000\",\n \" --concurrency <count> Accepted; creation is currently serialized\",\n \" --source <user|agent> Mark mutation source; agent implies background creation\",\n \" --no-focus Create in the background without stealing focus\",\n \" --focus Explicitly focus the created pane\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --dry-run Validate and preview without creating panes\",\n \" --yes Skip confirmation for mutating commands\"\n ],\n \"panes archive\": [\n \"Usage:\",\n \" runpane panes archive --pane <pane-id> [--source user|agent] [--force] --yes [--json]\",\n \"\",\n \"Archives a Pane (session) exactly like the UI Archive action, including removal of its Pane-managed git worktree. Refuses to archive a pane with uncommitted, untracked, or unpushed-to-remote changes unless --force is passed. Waits for worktree removal to finish before returning.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane/session id to archive\",\n \" --source <user|agent> Mark mutation source\",\n \" --force Archive even if the pane has uncommitted, untracked, or unpushed changes\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --yes Skip confirmation for mutating commands\"\n ],\n \"panels\": [\n \"Terminal-backed panel commands.\",\n \"\",\n \"Usage:\",\n \" runpane panels <command> [options]\",\n \"\",\n \"Commands:\",\n \" runpane panels create --pane <pane-id> --agent <codex|claude> [options]\",\n \" runpane panels list --pane <pane-id> [--json]\",\n \" runpane panels screen --panel <panel-id> [--limit <count>] [--json]\",\n \" runpane panels output --panel <panel-id> [--limit <count>] [--json]\",\n \" runpane panels input --panel <panel-id> (--text <text>|--input-file <path|->) --yes [--json]\",\n \" runpane panels submit --panel <panel-id> (--text <text>|--input-file <path|->) --yes [--json]\",\n \" runpane panels submit-composer --panel <panel-id> [--strategy auto|codex-ctrl-enter|enter] --yes [--json]\",\n \" runpane panels wait --panel <panel-id> [--for initialized|ready|idle|text] [--json]\",\n \"\",\n \"Run \\\"runpane help panels create\\\" or another command-specific topic for options.\"\n ],\n \"panels list\": [\n \"Usage:\",\n \" runpane panels list --pane <pane-id> [--json]\",\n \"\",\n \"Lists tool panels in a Pane session.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane/session id\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panels output\": [\n \"Usage:\",\n \" runpane panels output --panel <panel-id> [--limit <count>] [--json]\",\n \"\",\n \"Reads bounded recent terminal output from a panel.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Tool panel id\",\n \" --limit <count> Maximum recent output lines or records to read\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panels input\": [\n \"Usage:\",\n \" runpane panels input --panel <panel-id> (--text <text>|--input-file <path|->) --yes [--json]\",\n \"\",\n \"Sends exact input bytes to a terminal panel. Include a newline in the input when you mean Enter.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id\",\n \" --text <text> Text bytes to send\",\n \" --input-file <path|-> Read input from a file or stdin\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --yes Skip confirmation for mutating commands\"\n ],\n \"agent-context\": [\n \"Usage:\",\n \" runpane agent-context [--json]\",\n \" runpane agent-context --command <command> [--json]\",\n \"\",\n \"Prints Pane command context for coding agents without requiring a running Pane app.\",\n \"\",\n \"Options:\",\n \" --command <command> Print the full definition for one runpane command\",\n \" --json Print machine-readable output\",\n \"\",\n \"Examples:\",\n \" runpane agent-context\",\n \" runpane agent-context --json\",\n \" runpane agent-context --command \\\"panes create\\\"\",\n \" runpane agent-context --command \\\"panes create\\\" --json\"\n ],\n \"agents doctor\": [\n \"Usage:\",\n \" runpane agents doctor --agent <codex|claude> [--repo <selector>] [--json]\",\n \"\",\n \"Diagnoses whether Codex or Claude is available in the same repository environment Pane will use.\",\n \"\",\n \"Options:\",\n \" --agent <codex|claude> Built-in agent command to diagnose\",\n \" --repo <selector> active, id, exact path, or saved repository name; defaults to active\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panels screen\": [\n \"Usage:\",\n \" runpane panels screen --panel <panel-id> [--limit <count>] [--json]\",\n \"\",\n \"Reads a compact current-screen view from a terminal panel. Prefers alternate-screen/TUI output and falls back to recent scrollback.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id\",\n \" --limit <count> Maximum lines to return; defaults to 80\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panels submit\": [\n \"Usage:\",\n \" runpane panels submit --panel <panel-id> (--text <text>|--input-file <path|->) --yes [--json]\",\n \"\",\n \"Sends text to a terminal panel and normalizes the final terminal Enter to CR. Use this for ordinary prompt answers and shell commands.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id\",\n \" --text <text> Text to submit before Enter\",\n \" --input-file <path|-> Read text from a file or stdin before Enter\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --yes Skip confirmation for this mutating command\"\n ],\n \"panels wait\": [\n \"Usage:\",\n \" runpane panels wait --panel <panel-id> [--for initialized|ready|idle|text] [--contains <text>] [--timeout-ms <ms>] [--interval-ms <ms>] [--json]\",\n \"\",\n \"Polls a terminal panel until it is initialized, ready, idle, or contains text. Output is intentionally small and includes next-step guidance.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id\",\n \" --for <condition> initialized, ready, idle, or text; defaults to ready for CLI panels and idle otherwise\",\n \" --contains <text> Text required for --for text; implies --for text when omitted\",\n \" --timeout-ms <milliseconds> Wait timeout; defaults to 30000\",\n \" --interval-ms <milliseconds> Poll interval; defaults to 500\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panels create\": [\n \"Create a terminal-backed tool panel inside an existing Pane session.\",\n \"\",\n \"Usage:\",\n \" runpane panels create --pane <pane-id> --agent <codex|claude> [--title <title>] [--initial-input <text>] [--no-focus] [--wait-ready] --yes [--json]\",\n \" runpane panels create --pane <pane-id> --tool-command <command> [--title <title>] [--no-focus] --yes [--json]\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Existing Pane session to add the panel to.\",\n \" --agent <codex|claude> Built-in agent command template to launch.\",\n \" --tool-command <command> Custom terminal command to launch.\",\n \" --title <title> Panel title override.\",\n \" --initial-input, --prompt Initial input to send after the tool starts.\",\n \" --initial-input-file <path|-> Read initial input from a file or stdin.\",\n \" --no-focus Create the panel in the background.\",\n \" --focus Explicitly focus the created panel.\",\n \" --source <user|agent> Mark the mutation source; agent implies background creation.\",\n \" --wait-ready Wait until the terminal tool is ready.\",\n \" --ready-timeout-ms <ms> Readiness wait timeout.\",\n \" --yes Skip confirmation prompts.\",\n \" --json Print JSON output.\"\n ],\n \"panels submit-composer\": [\n \"Submit an agent composer using the panel-appropriate key sequence.\",\n \"\",\n \"Usage:\",\n \" runpane panels submit-composer --panel <panel-id> [--strategy auto|codex-ctrl-enter|enter] --yes [--json]\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id.\",\n \" --strategy <strategy> Defaults to auto; Codex sends Ctrl+Enter and other panels send Enter.\",\n \" --yes Skip confirmation prompts.\",\n \" --json Print JSON output.\"\n ]\n },\n \"pip\": {\n \"default\": [\n \"Usage:\",\n \" runpane\",\n \" runpane setup\",\n \" runpane install [client|daemon] [options]\",\n \" runpane update [options]\",\n \" runpane version\",\n \" runpane doctor\",\n \" runpane agent-context [--json]\",\n \" runpane agents doctor --agent <codex|claude> [--repo <selector>] [--json]\",\n \" runpane repos list [--json]\",\n \" runpane repos add --path <path> [--name <name>]\",\n \" runpane panes list [--repo <selector>] [--json]\",\n \" runpane panes create --repo <selector> --name <name> --agent <codex|claude> [--source user|agent] [--focus|--no-focus] [--wait-ready]\",\n \" runpane panes archive --pane <pane-id> [--source user|agent] [--force] --yes\",\n \" python -m runpane panels create --pane <pane-id> --agent <codex|claude> [--source user|agent] [--focus|--no-focus] --yes\",\n \" runpane panels list --pane <pane-id> [--json]\",\n \" runpane panels output --panel <panel-id> [--limit <count>] [--json]\",\n \" runpane panels screen --panel <panel-id> [--limit <count>] [--json]\",\n \" runpane panels input --panel <panel-id> (--text <text>|--input-file <path|->) --yes [--json]\",\n \" runpane panels submit --panel <panel-id> (--text <text>|--input-file <path|->) --yes [--json]\",\n \" runpane panels submit-composer --panel <panel-id> [--strategy auto|codex-ctrl-enter|enter] --yes [--json]\",\n \" runpane panels wait --panel <panel-id> [--for initialized|ready|idle|text] [--json]\",\n \" runpane help [command]\",\n \"\",\n \"Quick start:\",\n \" pipx run runpane\",\n \" python -m pip install runpane && python -m runpane setup\",\n \"\",\n \"Advanced examples:\",\n \" pipx run runpane install client\",\n \" pipx run runpane install daemon --label \\\"My Server\\\"\",\n \" uvx runpane@latest\",\n \"\",\n \"Common commands:\",\n \" runpane help\",\n \" runpane setup\",\n \" runpane install\",\n \" runpane doctor\",\n \"\",\n \"Agent discovery:\",\n \" runpane doctor --json\",\n \" runpane agent-context --json\",\n \" runpane agent-context --command \\\"<command>\\\" --json\",\n \"\",\n \"Run \\\"runpane help panes create\\\" for pane orchestration options.\"\n ],\n \"install\": [\n \"Usage:\",\n \" runpane install [client|daemon] [options]\",\n \"\",\n \"Examples:\",\n \" npx --yes runpane@latest install daemon --label \\\"My Server\\\"\",\n \" pnpm dlx runpane@latest install daemon --prefer-tunnel ssh --label \\\"VM\\\"\",\n \" pipx run runpane install daemon --label \\\"My Server\\\"\",\n \"\",\n \"Wrapper options:\",\n \" --version <latest|vX.Y.Z>\",\n \" --format <auto|appimage|deb|dmg|zip|exe>\",\n \" --download-dir <path>\",\n \" --pane-path <path>\",\n \" --dry-run\",\n \" --yes\",\n \" --verbose\",\n \"\",\n \"Daemon passthrough options:\",\n \" --label <name>\",\n \" --prefer-tunnel <tailscale|ssh|manual|auto>\",\n \" --channel <stable|nightly>\",\n \" --base-url <url>\",\n \" --pane-dir <path>\",\n \" --listen-port <port> / --port <port>\",\n \" --auto-listen-port\",\n \" --interactive-tailscale-setup\",\n \" --no-install-service\",\n \" --no-tailscale-serve\",\n \" --print-only\",\n \" --repo-ref <ref>\"\n ],\n \"setup\": [\n \"Usage:\",\n \" runpane setup\",\n \"\",\n \"Opens the guided setup for desktop install, remote host setup, update, and diagnostics.\",\n \"\",\n \"Quick start:\",\n \" pipx run runpane\",\n \" python -m pip install runpane && python -m runpane setup\"\n ],\n \"update\": [\n \"Usage:\",\n \" runpane update [--version <latest|vX.Y.Z>] [--dry-run] [--yes]\"\n ],\n \"version\": [\n \"Usage:\",\n \" runpane version\",\n \" runpane --version\"\n ],\n \"doctor\": [\n \"Usage:\",\n \" runpane doctor [--json] [--pane-dir <path>] [--pane-path <path>] [--format <format>] [--verbose]\",\n \"\",\n \"Checks wrapper/runtime details, release metadata, installed Pane detection, and Pane daemon reachability.\",\n \"\",\n \"Options:\",\n \" --json\",\n \" --pane-dir <path>\",\n \" --pane-path <path>\",\n \" --format <format>\",\n \" --verbose\",\n \"\",\n \"Agent discovery:\",\n \" runpane doctor --json\",\n \" runpane agent-context --json\"\n ],\n \"repos list\": [\n \"Usage:\",\n \" runpane repos list [--json] [--pane-dir <path>]\",\n \"\",\n \"Lists repositories saved in the running Pane app.\",\n \"\",\n \"Options:\",\n \" --json\",\n \" --pane-dir <path>\"\n ],\n \"repos add\": [\n \"Usage:\",\n \" runpane repos add --path <path> [--name <name>] [--json] [--yes]\",\n \"\",\n \"Registers an existing git repository with the running Pane app.\",\n \"\",\n \"Options:\",\n \" --path <path>\",\n \" --name <name>\",\n \" --pane-dir <path>\",\n \" --json\",\n \" --dry-run\",\n \" --yes\"\n ],\n \"panes\": [\n \"Pane session commands.\",\n \"\",\n \"Usage:\",\n \" runpane panes <command> [options]\",\n \"\",\n \"Commands:\",\n \" runpane panes list [--repo <selector>] [--json]\",\n \" runpane panes create --repo <selector> --name <name> --agent <codex|claude> [options]\",\n \" runpane panes archive --pane <pane-id> [--force] [--source user|agent] --yes [--json]\",\n \"\",\n \"Run \\\"runpane help panes list\\\", \\\"runpane help panes create\\\", or \\\"runpane help panes archive\\\" for command-specific options.\"\n ],\n \"panes list\": [\n \"Usage:\",\n \" runpane panes list [--repo <selector>] [--json]\",\n \"\",\n \"Lists Pane sessions. Pass --repo to limit results to one saved repository.\",\n \"\",\n \"Options:\",\n \" --repo <selector>\",\n \" --pane-dir <path>\",\n \" --json\"\n ],\n \"panes create\": [\n \"Usage:\",\n \" runpane panes create --repo <selector> --name <name> --agent <codex|claude> [options]\",\n \" runpane panes create --from-json <path|-> [--yes] [--json]\",\n \"\",\n \"Creates user-visible Panes (Pane sessions) for feature/PR work in a saved repository and opens a terminal-backed tool tab. Pane creates and owns the worktree/branch for each new Pane.\",\n \"\",\n \"Options:\",\n \" --repo <selector> active, id, exact path, or saved repository name\",\n \" --name <name> Pane/session name\",\n \" --worktree-name <name> Worktree name; defaults to --name\",\n \" --base-branch <branch> Base branch for the worktree\",\n \" --agent <codex|claude> Built-in terminal template\",\n \" --tool-command <command> Custom terminal command\",\n \" --title <title> Terminal tab title\",\n \" --initial-input <text> Text sent after the command is ready\",\n \" --prompt <text> Alias for --initial-input\",\n \" --initial-input-file <path|-> Read initial input from a file or stdin\",\n \" --from-json <path|-> Read a full request payload\",\n \" --timeout-ms <milliseconds> Pane creation timeout\",\n \" --wait-ready Wait for terminal readiness before returning\",\n \" --ready-timeout-ms <ms> Readiness wait timeout; defaults to 30000\",\n \" --concurrency <count> Accepted; creation is currently serialized\",\n \" --source <user|agent> Mark mutation source; agent implies background creation\",\n \" --no-focus Create in the background without stealing focus\",\n \" --focus Explicitly focus the created pane\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --dry-run Validate and preview without creating panes\",\n \" --yes Skip confirmation for mutating commands\"\n ],\n \"panes archive\": [\n \"Usage:\",\n \" runpane panes archive --pane <pane-id> [--source user|agent] [--force] --yes [--json]\",\n \"\",\n \"Archives a Pane (session) exactly like the UI Archive action, including removal of its Pane-managed git worktree. Refuses to archive a pane with uncommitted, untracked, or unpushed-to-remote changes unless --force is passed. Waits for worktree removal to finish before returning.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id>\",\n \" --source <user|agent>\",\n \" --force\",\n \" --pane-dir <path>\",\n \" --json\",\n \" --yes\"\n ],\n \"panels\": [\n \"Terminal-backed panel commands.\",\n \"\",\n \"Usage:\",\n \" runpane panels <command> [options]\",\n \"\",\n \"Commands:\",\n \" runpane panels create --pane <pane-id> --agent <codex|claude> [options]\",\n \" runpane panels list --pane <pane-id> [--json]\",\n \" runpane panels screen --panel <panel-id> [--limit <count>] [--json]\",\n \" runpane panels output --panel <panel-id> [--limit <count>] [--json]\",\n \" runpane panels input --panel <panel-id> (--text <text>|--input-file <path|->) --yes [--json]\",\n \" runpane panels submit --panel <panel-id> (--text <text>|--input-file <path|->) --yes [--json]\",\n \" runpane panels submit-composer --panel <panel-id> [--strategy auto|codex-ctrl-enter|enter] --yes [--json]\",\n \" runpane panels wait --panel <panel-id> [--for initialized|ready|idle|text] [--json]\",\n \"\",\n \"Run \\\"runpane help panels create\\\" or another command-specific topic for options.\"\n ],\n \"panels list\": [\n \"Usage:\",\n \" runpane panels list --pane <pane-id> [--json]\",\n \"\",\n \"Lists tool panels in a Pane session.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id>\",\n \" --pane-dir <path>\",\n \" --json\"\n ],\n \"panels output\": [\n \"Usage:\",\n \" runpane panels output --panel <panel-id> [--limit <count>] [--json]\",\n \"\",\n \"Reads recent terminal output records from a panel.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id>\",\n \" --limit <count>\",\n \" --pane-dir <path>\",\n \" --json\"\n ],\n \"panels input\": [\n \"Usage:\",\n \" runpane panels input --panel <panel-id> (--text <text>|--input-file <path|->) --yes [--json]\",\n \"\",\n \"Sends exact input bytes to a terminal panel. Include a newline in the input when you mean Enter.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id>\",\n \" --text <text>\",\n \" --input-file <path|->\",\n \" --pane-dir <path>\",\n \" --json\",\n \" --yes\"\n ],\n \"agent-context\": [\n \"Usage:\",\n \" runpane agent-context [--json]\",\n \" runpane agent-context --command <command> [--json]\",\n \"\",\n \"Prints Pane command context for coding agents without requiring a running Pane app.\",\n \"\",\n \"Options:\",\n \" --command <command>\",\n \" --json\",\n \"\",\n \"Examples:\",\n \" runpane agent-context\",\n \" runpane agent-context --json\",\n \" runpane agent-context --command \\\"panes create\\\"\",\n \" runpane agent-context --command \\\"panes create\\\" --json\"\n ],\n \"agents doctor\": [\n \"Usage:\",\n \" runpane agents doctor --agent <codex|claude> [--repo <selector>] [--json]\",\n \"\",\n \"Diagnoses whether Codex or Claude is available in the same repository environment Pane will use.\",\n \"\",\n \"Options:\",\n \" --agent <codex|claude> Built-in agent command to diagnose\",\n \" --repo <selector> active, id, exact path, or saved repository name; defaults to active\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panels screen\": [\n \"Usage:\",\n \" runpane panels screen --panel <panel-id> [--limit <count>] [--json]\",\n \"\",\n \"Reads a compact current-screen view from a terminal panel. Prefers alternate-screen/TUI output and falls back to recent scrollback.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id\",\n \" --limit <count> Maximum lines to return; defaults to 80\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panels submit\": [\n \"Usage:\",\n \" runpane panels submit --panel <panel-id> (--text <text>|--input-file <path|->) --yes [--json]\",\n \"\",\n \"Sends text to a terminal panel and normalizes the final terminal Enter to CR. Use this for ordinary prompt answers and shell commands.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id\",\n \" --text <text> Text to submit before Enter\",\n \" --input-file <path|-> Read text from a file or stdin before Enter\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --yes Skip confirmation for this mutating command\"\n ],\n \"panels wait\": [\n \"Usage:\",\n \" runpane panels wait --panel <panel-id> [--for initialized|ready|idle|text] [--contains <text>] [--timeout-ms <ms>] [--interval-ms <ms>] [--json]\",\n \"\",\n \"Polls a terminal panel until it is initialized, ready, idle, or contains text. Output is intentionally small and includes next-step guidance.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id\",\n \" --for <condition> initialized, ready, idle, or text; defaults to ready for CLI panels and idle otherwise\",\n \" --contains <text> Text required for --for text; implies --for text when omitted\",\n \" --timeout-ms <milliseconds> Wait timeout; defaults to 30000\",\n \" --interval-ms <milliseconds> Poll interval; defaults to 500\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panels create\": [\n \"Create a terminal-backed tool panel inside an existing Pane session.\",\n \"\",\n \"Usage:\",\n \" python -m runpane panels create --pane <pane-id> --agent <codex|claude> [--title <title>] [--initial-input <text>] [--no-focus] [--wait-ready] --yes [--json]\",\n \" python -m runpane panels create --pane <pane-id> --tool-command <command> [--title <title>] [--no-focus] --yes [--json]\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Existing Pane session to add the panel to.\",\n \" --agent <codex|claude> Built-in agent command template to launch.\",\n \" --tool-command <command> Custom terminal command to launch.\",\n \" --title <title> Panel title override.\",\n \" --initial-input, --prompt Initial input to send after the tool starts.\",\n \" --initial-input-file <path|-> Read initial input from a file or stdin.\",\n \" --no-focus Create the panel in the background.\",\n \" --focus Explicitly focus the created panel.\",\n \" --source <user|agent> Mark the mutation source; agent implies background creation.\",\n \" --wait-ready Wait until the terminal tool is ready.\",\n \" --ready-timeout-ms <ms> Readiness wait timeout.\",\n \" --yes Skip confirmation prompts.\",\n \" --json Print JSON output.\"\n ],\n \"panels submit-composer\": [\n \"Submit an agent composer using the panel-appropriate key sequence.\",\n \"\",\n \"Usage:\",\n \" python -m runpane panels submit-composer --panel <panel-id> [--strategy auto|codex-ctrl-enter|enter] --yes [--json]\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id.\",\n \" --strategy <strategy> Defaults to auto; Codex sends Ctrl+Enter and other panels send Enter.\",\n \" --yes Skip confirmation prompts.\",\n \" --json Print JSON output.\"\n ]\n }\n },\n \"docs\": {\n \"maintainerRules\": [\n \"Treat `contracts/runpane/contract.json` as the source of truth for both wrapper packages and generated docs.\",\n \"Every command, flag, platform default, artifact-selection rule, and attribution rule change must be reflected in the contract.\",\n \"The npm and PyPI wrappers must expose the same command behavior unless the contract explicitly documents a package-manager-specific difference.\",\n \"Root `README.md` and package READMEs should lead with one guided quick-start command. Explicit commands, package-manager variants, and flags belong in an Advanced section.\",\n \"Release version bumps must keep root `package.json`, `packages/runpane`, and `packages/runpane-py` versions in sync. Run `pnpm run check:runpane-package-versions` before release.\",\n \"`pnpm run test:runpane-contract` must pass before changing wrapper command parsing, help output, platform defaults, release asset selection, or generated contract artifacts.\",\n \"Token-based npm or PyPI publishing is a temporary fallback. Prefer trusted publishing once the package names are reserved and trusted publishers are configured.\"\n ],\n \"recommendedQuickStarts\": [\n \"npx --yes runpane@latest\",\n \"npm i -g runpane && runpane setup\",\n \"pipx run runpane\",\n \"python -m pip install runpane && python -m runpane setup\"\n ],\n \"npmCommands\": [\n \"npx --yes runpane@latest\",\n \"npx --yes runpane@latest setup\",\n \"npx --yes runpane@latest install client\",\n \"npx --yes runpane@latest install daemon --label \\\"My Server\\\"\",\n \"pnpm dlx runpane@latest\",\n \"pnpm dlx runpane@latest install daemon --label \\\"My Server\\\"\",\n \"npm i -g runpane && runpane\",\n \"npm i -g runpane && runpane setup\",\n \"npm i -g runpane && runpane install daemon --label \\\"My Server\\\"\",\n \"pnpm add -g runpane && runpane\",\n \"pnpm add -g runpane && runpane setup\",\n \"pnpm add -g runpane && runpane install daemon --label \\\"My Server\\\"\",\n \"yarn dlx runpane@latest install daemon --label \\\"My Server\\\"\",\n \"bunx runpane@latest install daemon --label \\\"My Server\\\"\"\n ],\n \"pythonCommands\": [\n \"pipx run runpane\",\n \"pipx run runpane setup\",\n \"python -m pip install runpane\",\n \"runpane\",\n \"runpane setup\",\n \"python -m runpane setup\",\n \"runpane install daemon --label \\\"My Server\\\"\",\n \"pipx install runpane\",\n \"runpane\",\n \"runpane setup\",\n \"pipx run runpane install daemon --label \\\"My Server\\\"\",\n \"uvx runpane@latest\",\n \"uvx runpane@latest setup\",\n \"uvx runpane@latest install daemon --label \\\"My Server\\\"\",\n \"python -m runpane install daemon --label \\\"My Server\\\"\"\n ],\n \"packageManagerNotes\": [\n \"Use `pnpm dlx` for one-shot pnpm execution and `pnpm add -g` for persistent CLI installation.\",\n \"Do not document `pnpm install runpane` as the public CLI install path.\"\n ],\n \"commandUsages\": [\n \"runpane\",\n \"runpane setup\",\n \"runpane install\",\n \"runpane install client\",\n \"runpane install daemon\",\n \"runpane update\",\n \"runpane version\",\n \"runpane doctor\",\n \"runpane doctor --json\",\n \"runpane agent-context\",\n \"runpane agent-context --command \\\"panes create\\\" --json\",\n \"runpane repos list --json\",\n \"runpane repos add --path /path/to/repo --name Pane --yes --json\",\n \"runpane panes list --repo active --json\",\n \"runpane panes create --repo active --name issue-252 --agent <agent> --prompt \\\"Kick off the discussion skill for issue 252\\\" --source agent --no-focus --wait-ready --yes --json\",\n \"runpane panes create --from-json panes.json --yes --json\",\n \"runpane panes archive --pane <pane-id> --source agent --yes --json\",\n \"runpane panels list --pane <pane-id> --json\",\n \"runpane panels output --panel <panel-id> --limit 200 --json\",\n \"printf 'Continue\\\\n' | runpane panels input --panel <panel-id> --input-file - --yes --json\",\n \"runpane help\",\n \"runpane <command> --help\"\n ],\n \"commandDescriptions\": [\n \"`runpane` with no arguments and `runpane setup` open an interactive wizard when stdin and stdout are TTYs. In non-interactive shells or CI, both forms must print help, common commands, and agent discovery hints, then exit successfully instead of waiting for input.\",\n \"`runpane install` is an alias for `runpane install client`.\",\n \"`runpane install client` downloads the selected Pane desktop artifact and installs, opens, or launches it for the current platform.\",\n \"`runpane install daemon` downloads or installs Pane, resolves a stable Pane executable path, and spawns `<pane executable> --remote-setup <forwarded remote setup args>`.\",\n \"The wrapper must stream Pane stdout/stderr without reformatting because `pane --remote-setup` prints the one-time `pane-remote://...` connection code.\",\n \"`runpane update` uses the same release resolution and installer path as `install client`.\",\n \"`runpane version` prints only wrapper package metadata and does not contact, launch, or focus the Pane app or daemon.\",\n \"`runpane doctor` checks platform support, release metadata reachability, download URL selection, installed Pane detection, daemon reachability, and remote-daemon hints. Add `--json` for a machine-readable report that agents should run before mutating Pane state. Installed app version detection must be best-effort and must not launch, focus, or configure Pane; macOS wrappers read app bundle metadata instead of executing `Pane --version`.\",\n \"`runpane agent-context` prints a brief, token-efficient command schema for coding agents without connecting to the Pane daemon.\",\n \"`runpane agent-context --command \\\"panes create\\\"` prints the detailed definition for one command. Add `--json` for machine-readable output.\",\n \"`runpane repos list` connects to the running local Pane daemon and prints saved repository records.\",\n \"`runpane repos add` registers an existing git repository with the running local Pane daemon. It does not create directories or initialize git repositories by default.\",\n \"`runpane panes list` lists Pane sessions, optionally scoped to one saved repository.\",\n \"`runpane panes create` connects to the running local Pane daemon, resolves the requested saved base repository, creates user-visible Pane sessions backed by Pane-managed worktrees/branches, opens terminal-backed tool tabs, and optionally sends initial input to the started tool. Built-in agent panes and `--source agent` default to background/no-focus unless `--focus` is passed.\",\n \"`runpane panes archive` archives a Pane exactly like the UI Archive action, including removal of its Pane-managed git worktree, and refuses (unless `--force`) when the pane's branch has uncommitted, untracked, or unpushed-to-remote changes. It waits for worktree removal to finish before returning and reports the outcome in `worktreeCleanup`.\",\n \"`runpane panels list` lists tool panels inside one Pane session.\",\n \"`runpane panels output` reads bounded recent terminal output from one panel and strips common terminal control noise for agent use.\",\n \"`runpane panels input` sends exact input bytes to one terminal panel. Prefer `--input-file` for newlines, Ctrl-C, quotes, or shell-sensitive text.\",\n \"`runpane panes create --prompt` is an alias for `--initial-input`; request JSON and daemon payloads should use the canonical `initialInput` field.\",\n \"When running from WSL while Pane is installed on Windows, the Linux wrapper may look for a missing `/tmp/pane-daemon.../daemon.sock` or resolve to a Windows shim such as Volta. In that case invoke the Windows wrapper through PowerShell from a Windows cwd, for example `powershell.exe -NoProfile -Command 'Set-Location $env:TEMP; runpane repos list --json'`.\"\n ],\n \"wrapperFlagNote\": \"The top-level `runpane --version` form prints the wrapper version. The install subcommand form `runpane install --version vX.Y.Z` selects a Pane release.\",\n \"localControlFlagNote\": \"`runpane doctor --json`, `runpane repos list`, `runpane panes list`, `runpane panes create`, and `runpane panels ...` commands use or describe the local framed daemon socket/pipe for a running Pane app. `--pane-dir` points the wrapper at a non-default Pane data directory, such as `PANE_DIR=~/.pane_test` in development. `runpane agent-context` is local/offline and can be used before Pane is running. In a Pane repository checkout, if `runpane` is not on PATH, use the built local wrapper with Node 22, for example `PATH=/opt/homebrew/opt/node@22/bin:$PATH node packages/runpane/dist/cli.js doctor --json`. From WSL, if the user runs Windows Pane, call the Windows wrapper through `powershell.exe -NoProfile -Command 'Set-Location $env:TEMP; runpane ...'` so the command can reach the Windows named-pipe daemon and avoid UNC cwd issues.\",\n \"daemonFlagNote\": \"Unknown daemon flags should be forwarded rather than dropped so newer Pane versions can extend `--remote-setup` without requiring an immediate wrapper release. Unknown flags for non-daemon commands should fail clearly.\",\n \"downloadAttribution\": [\n \"The npm package uses `source=npm` for all npm-registry consumers, including `npx`, `pnpm dlx`, `yarn dlx`, `bunx`, and global npm/pnpm installs.\",\n \"The PyPI package uses `source=pip` for all Python consumers, including pip, pipx, uvx, and `python -m runpane`.\",\n \"Wrappers should prefer `https://runpane.com/api/download?platform=<platform>&arch=<arch>&format=<format>&version=<version>&channel=<channel>&source=<npm|pip>`.\",\n \"If the website route cannot satisfy the download, wrappers may fall back to the matching GitHub release asset and print a warning that website attribution may be incomplete for that run.\",\n \"Wrappers also emit best-effort lifecycle telemetry to `https://runpane.com/api/runpane/telemetry` for command start/success/failure, download request/success/failure, and GitHub fallback usage.\",\n \"Wrapper telemetry uses a persisted anonymous `install_id` in the form `install_<uuid>` and PostHog `distinct_id = install:<install_id>` so distinct wrapper users can be counted with `count(DISTINCT properties.install_id)`.\",\n \"Wrapper telemetry must be disabled in CI and when `RUNPANE_TELEMETRY_DISABLED` is set, and must not include raw paths, labels, prompts, raw error text, or environment values.\"\n ],\n \"publishingCredentials\": [\n \"Local implementation, build, and dry-run validation do not need npm or PyPI API tokens.\",\n \"Release publishing should prefer npm Trusted Publishing and PyPI Trusted Publishing from GitHub Actions.\",\n \"Fallback `NPM_TOKEN` or `PYPI_API_TOKEN` credentials may be used for first package reservation or manual publication only. They must be supplied through local environment variables or GitHub Actions secrets, never committed, and revoked or rotated after use.\"\n ]\n },\n \"testFixtures\": {\n \"parserSamples\": [\n [\n \"setup\"\n ],\n [\n \"install\"\n ],\n [\n \"install\",\n \"client\",\n \"--version\",\n \"v2.2.8\",\n \"--format\",\n \"dmg\",\n \"--download-dir\",\n \"/tmp/pane-downloads\",\n \"--dry-run\",\n \"--yes\"\n ],\n [\n \"install\",\n \"daemon\",\n \"--label\",\n \"VM\",\n \"--prefer-tunnel\",\n \"ssh\",\n \"--channel\",\n \"nightly\",\n \"--base-url\",\n \"https://example.test\",\n \"--pane-dir\",\n \"/tmp/pane\",\n \"--listen-port\",\n \"4555\",\n \"--auto-listen-port\",\n \"--print-only\",\n \"--repo-ref\",\n \"main\",\n \"--unknown-future-flag\",\n \"future-value\",\n \"--dry-run\",\n \"--verbose\"\n ],\n [\n \"update\",\n \"--version\",\n \"latest\",\n \"--format\",\n \"appimage\",\n \"--pane-path\",\n \"/usr/bin/pane\",\n \"--dry-run\"\n ],\n [\n \"doctor\",\n \"--pane-path\",\n \"/usr/bin/pane\",\n \"--format\",\n \"zip\",\n \"--verbose\"\n ],\n [\n \"doctor\",\n \"--json\",\n \"--pane-dir\",\n \"/tmp/pane\"\n ],\n [\n \"agent-context\"\n ],\n [\n \"agent-context\",\n \"--command\",\n \"panes create\",\n \"--json\"\n ],\n [\n \"repos\",\n \"list\",\n \"--json\",\n \"--pane-dir\",\n \"/tmp/pane\"\n ],\n [\n \"repos\",\n \"add\",\n \"--path\",\n \"/tmp/repo\",\n \"--name\",\n \"Repo\",\n \"--dry-run\",\n \"--yes\",\n \"--json\",\n \"--pane-dir\",\n \"/tmp/pane\"\n ],\n [\n \"panes\",\n \"list\",\n \"--repo\",\n \"active\",\n \"--json\"\n ],\n [\n \"panes\",\n \"create\",\n \"--repo\",\n \"active\",\n \"--name\",\n \"issue-252\",\n \"--agent\",\n \"codex\",\n \"--prompt\",\n \"Kick off discussion\",\n \"--source\",\n \"agent\",\n \"--dry-run\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"create\",\n \"--from-json\",\n \"-\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"archive\",\n \"--pane\",\n \"session-1\",\n \"--force\",\n \"--source\",\n \"agent\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panels\",\n \"list\",\n \"--pane\",\n \"session-1\",\n \"--json\"\n ],\n [\n \"panels\",\n \"output\",\n \"--panel\",\n \"panel-1\",\n \"--limit\",\n \"200\",\n \"--json\"\n ],\n [\n \"panels\",\n \"input\",\n \"--panel\",\n \"panel-1\",\n \"--text\",\n \"Continue\\\\n\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"--version\"\n ],\n [\n \"agents\",\n \"doctor\",\n \"--agent\",\n \"codex\",\n \"--repo\",\n \"active\",\n \"--json\"\n ],\n [\n \"panes\",\n \"create\",\n \"--from-json\",\n \"-\",\n \"--source\",\n \"agent\",\n \"--focus\",\n \"--wait-ready\",\n \"--ready-timeout-ms\",\n \"45000\",\n \"--concurrency\",\n \"2\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panels\",\n \"screen\",\n \"--panel\",\n \"panel-1\",\n \"--limit\",\n \"80\",\n \"--json\"\n ],\n [\n \"panels\",\n \"submit\",\n \"--panel\",\n \"panel-1\",\n \"--text\",\n \"2\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panels\",\n \"wait\",\n \"--panel\",\n \"panel-1\",\n \"--for\",\n \"text\",\n \"--contains\",\n \"ready\",\n \"--timeout-ms\",\n \"30000\",\n \"--interval-ms\",\n \"500\",\n \"--json\"\n ],\n [\n \"panels\",\n \"create\",\n \"--pane\",\n \"pane-1\",\n \"--agent\",\n \"codex\",\n \"--source\",\n \"agent\",\n \"--no-focus\",\n \"--wait-ready\",\n \"--ready-timeout-ms\",\n \"15000\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panels\",\n \"create\",\n \"--pane\",\n \"pane-1\",\n \"--agent\",\n \"codex\",\n \"--focus\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panels\",\n \"submit-composer\",\n \"--panel\",\n \"panel-1\",\n \"--strategy\",\n \"auto\",\n \"--yes\",\n \"--json\"\n ]\n ],\n \"topLevelHelpIncludes\": [\n \"runpane setup\",\n \"runpane install\",\n \"runpane update\",\n \"runpane version\",\n \"runpane doctor\",\n \"runpane agent-context\",\n \"runpane repos list\",\n \"runpane repos add\",\n \"runpane panes list\",\n \"runpane panes create\",\n \"runpane panes archive\",\n \"runpane panels create\",\n \"runpane panels list\",\n \"runpane panels output\",\n \"runpane panels input\",\n \"runpane agents doctor\",\n \"runpane panels screen\",\n \"runpane panels submit\",\n \"runpane panels submit-composer\",\n \"runpane panels wait\",\n \"runpane doctor --json\",\n \"runpane agent-context --json\",\n \"Agent discovery:\",\n \"Common commands:\"\n ],\n \"npmHelpIncludes\": [\n \"pnpm dlx runpane@latest\",\n \"npx --yes runpane@latest\"\n ],\n \"pipHelpIncludes\": [\n \"pipx run runpane\",\n \"python -m pip install runpane && python -m runpane setup\"\n ],\n \"installHelpIncludes\": [\n \"--version <latest|vX.Y.Z>\",\n \"--format <auto|appimage|deb|dmg|zip|exe>\",\n \"--download-dir <path>\",\n \"--pane-path <path>\",\n \"--label <name>\",\n \"--prefer-tunnel <tailscale|ssh|manual|auto>\",\n \"--repo-ref <ref>\"\n ]\n },\n \"jsonSchemas\": {\n \"error\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"error\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": false\n },\n \"error\": {\n \"type\": \"object\",\n \"required\": [\n \"message\"\n ],\n \"properties\": {\n \"message\": {\n \"type\": \"string\"\n },\n \"code\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"doctorResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"source\",\n \"wrapper\",\n \"release\",\n \"installedPane\",\n \"daemon\",\n \"remoteSetup\",\n \"nextCommands\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"source\": {\n \"enum\": [\n \"npm\",\n \"pip\"\n ]\n },\n \"wrapper\": {\n \"type\": \"object\",\n \"required\": [\n \"runtime\",\n \"version\",\n \"paneDir\",\n \"endpoint\"\n ],\n \"properties\": {\n \"runtime\": {\n \"enum\": [\n \"node\",\n \"python\"\n ]\n },\n \"version\": {\n \"type\": \"string\"\n },\n \"paneDir\": {\n \"type\": \"string\"\n },\n \"endpoint\": {\n \"type\": \"object\",\n \"required\": [\n \"transport\",\n \"path\"\n ],\n \"properties\": {\n \"transport\": {\n \"enum\": [\n \"pipe\",\n \"unix\"\n ]\n },\n \"path\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"platform\": {\n \"type\": \"object\",\n \"properties\": {\n \"os\": {\n \"type\": \"string\"\n },\n \"arch\": {\n \"type\": \"string\"\n },\n \"error\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": true\n },\n \"release\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"error\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": true\n },\n \"installedPane\": {\n \"type\": \"object\",\n \"required\": [\n \"found\"\n ],\n \"properties\": {\n \"found\": {\n \"type\": \"boolean\"\n },\n \"path\": {\n \"type\": \"string\"\n },\n \"version\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"daemon\": {\n \"type\": \"object\",\n \"required\": [\n \"reachable\",\n \"endpoint\"\n ],\n \"properties\": {\n \"reachable\": {\n \"type\": \"boolean\"\n },\n \"endpoint\": {\n \"type\": \"object\",\n \"required\": [\n \"transport\",\n \"path\"\n ],\n \"properties\": {\n \"transport\": {\n \"enum\": [\n \"pipe\",\n \"unix\"\n ]\n },\n \"path\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"result\": {\n \"type\": \"object\"\n },\n \"error\": {\n \"type\": \"string\"\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"remoteSetup\": {\n \"type\": \"object\",\n \"required\": [\n \"ready\",\n \"displayAvailable\",\n \"headlessEnvironmentApplied\",\n \"diagnostics\"\n ],\n \"properties\": {\n \"ready\": {\n \"type\": \"boolean\"\n },\n \"displayAvailable\": {\n \"type\": \"boolean\"\n },\n \"headlessEnvironmentApplied\": {\n \"type\": \"boolean\"\n },\n \"diagnostics\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"code\",\n \"severity\",\n \"message\"\n ],\n \"properties\": {\n \"code\": {\n \"enum\": [\n \"PANE_APPIMAGE_FUSE_MISSING\",\n \"PANE_ELECTRON_SANDBOX_ROOT\",\n \"PANE_ELECTRON_SANDBOX_UNAVAILABLE\",\n \"PANE_USER_SERVICE_UNAVAILABLE\"\n ]\n },\n \"severity\": {\n \"enum\": [\n \"warning\",\n \"error\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"recoveryCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"additionalProperties\": false\n },\n \"nextCommands\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n },\n \"additionalProperties\": false\n },\n \"repoListResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"repos\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"repos\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"id\",\n \"name\",\n \"path\",\n \"active\",\n \"sessionCount\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"number\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"path\": {\n \"type\": \"string\"\n },\n \"active\": {\n \"type\": \"boolean\"\n },\n \"environment\": {\n \"type\": \"string\"\n },\n \"sessionCount\": {\n \"type\": \"number\"\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"additionalProperties\": false\n },\n \"repoAddRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"path\"\n ],\n \"properties\": {\n \"path\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"dryRun\": {\n \"type\": \"boolean\"\n }\n },\n \"additionalProperties\": false\n },\n \"repoAddResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"created\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"created\": {\n \"type\": \"boolean\"\n },\n \"dryRun\": {\n \"type\": \"boolean\"\n },\n \"repo\": {\n \"$ref\": \"#/jsonSchemas/repoListResult/properties/repos/items\"\n },\n \"preview\": {\n \"type\": \"object\",\n \"required\": [\n \"name\",\n \"path\",\n \"alreadyExists\",\n \"wouldCreate\"\n ],\n \"properties\": {\n \"name\": {\n \"type\": \"string\"\n },\n \"path\": {\n \"type\": \"string\"\n },\n \"alreadyExists\": {\n \"type\": \"boolean\"\n },\n \"wouldCreate\": {\n \"type\": \"boolean\"\n },\n \"environment\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"paneCreateRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"repo\",\n \"panes\"\n ],\n \"properties\": {\n \"repo\": {\n \"oneOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"type\": \"object\",\n \"properties\": {\n \"id\": {\n \"type\": \"number\"\n },\n \"path\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"active\": {\n \"const\": true\n }\n },\n \"additionalProperties\": false\n }\n ]\n },\n \"panes\": {\n \"type\": \"array\",\n \"minItems\": 1,\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"name\",\n \"tool\"\n ],\n \"properties\": {\n \"name\": {\n \"type\": \"string\"\n },\n \"worktreeName\": {\n \"type\": \"string\"\n },\n \"baseBranch\": {\n \"type\": \"string\"\n },\n \"sessionPrompt\": {\n \"type\": \"string\"\n },\n \"tool\": {\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"required\": [\n \"agent\"\n ],\n \"properties\": {\n \"agent\": {\n \"enum\": [\n \"codex\",\n \"claude\"\n ]\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"initialInput\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"command\"\n ],\n \"properties\": {\n \"command\": {\n \"type\": \"string\"\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"initialInput\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n ]\n }\n },\n \"additionalProperties\": false\n }\n },\n \"dryRun\": {\n \"type\": \"boolean\"\n },\n \"timeoutMs\": {\n \"type\": \"number\"\n },\n \"waitReady\": {\n \"type\": \"boolean\"\n },\n \"readyTimeoutMs\": {\n \"type\": \"number\"\n },\n \"concurrency\": {\n \"type\": \"number\"\n },\n \"noFocus\": {\n \"type\": \"boolean\"\n },\n \"focus\": {\n \"type\": \"boolean\"\n },\n \"source\": {\n \"enum\": [\n \"user\",\n \"agent\"\n ]\n }\n },\n \"additionalProperties\": false\n },\n \"paneCreateResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"repo\",\n \"items\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"repo\": {\n \"$ref\": \"#/jsonSchemas/repoListResult/properties/repos/items\"\n },\n \"items\": {\n \"type\": \"array\",\n \"items\": {\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"index\",\n \"name\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"index\": {\n \"type\": \"number\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"sessionId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"worktreePath\": {\n \"type\": \"string\"\n },\n \"nextCommand\": {\n \"type\": \"string\"\n },\n \"tool\": {\n \"type\": \"object\",\n \"required\": [\n \"title\",\n \"command\"\n ],\n \"properties\": {\n \"title\": {\n \"type\": \"string\"\n },\n \"command\": {\n \"type\": \"string\"\n },\n \"agent\": {\n \"enum\": [\n \"codex\",\n \"claude\"\n ]\n }\n },\n \"additionalProperties\": false\n },\n \"active\": {\n \"type\": \"boolean\"\n },\n \"focused\": {\n \"type\": \"boolean\"\n },\n \"readiness\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"condition\",\n \"matched\",\n \"timedOut\",\n \"elapsedMs\",\n \"state\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"condition\": {\n \"enum\": [\n \"initialized\",\n \"ready\",\n \"idle\",\n \"text\"\n ]\n },\n \"matched\": {\n \"type\": \"boolean\"\n },\n \"timedOut\": {\n \"type\": \"boolean\"\n },\n \"elapsedMs\": {\n \"type\": \"number\"\n },\n \"state\": {\n \"type\": \"object\",\n \"required\": [\n \"initialized\"\n ],\n \"properties\": {\n \"initialized\": {\n \"type\": \"boolean\"\n },\n \"isAlternateScreen\": {\n \"type\": \"boolean\"\n },\n \"activityStatus\": {\n \"enum\": [\n \"active\",\n \"idle\"\n ]\n },\n \"isCliReady\": {\n \"type\": \"boolean\"\n },\n \"isCliPanel\": {\n \"type\": \"boolean\"\n },\n \"agentType\": {\n \"enum\": [\n \"codex\",\n \"claude\"\n ]\n },\n \"lastActivity\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"blocked\": {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"message\"\n ],\n \"properties\": {\n \"kind\": {\n \"enum\": [\n \"codex-update\",\n \"agent-prompt\",\n \"unknown\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"suggestedCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"initialInput\": {\n \"type\": \"object\",\n \"required\": [\n \"delivered\",\n \"submitted\",\n \"inputBytes\"\n ],\n \"properties\": {\n \"delivered\": {\n \"type\": \"boolean\"\n },\n \"submitted\": {\n \"type\": \"boolean\"\n },\n \"inputBytes\": {\n \"type\": \"number\"\n },\n \"strategy\": {\n \"enum\": [\n \"codex-ctrl-enter\",\n \"enter\",\n \"argument\"\n ]\n },\n \"sequenceName\": {\n \"enum\": [\n \"codex-ctrl-enter-cr\",\n \"enter-cr\",\n \"argument\"\n ]\n },\n \"verifiedSubmitted\": {\n \"type\": \"boolean\"\n },\n \"sentAt\": {\n \"type\": \"string\"\n },\n \"blocked\": {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"message\"\n ],\n \"properties\": {\n \"kind\": {\n \"enum\": [\n \"codex-update\",\n \"agent-prompt\",\n \"unknown\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"suggestedCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"error\": {\n \"type\": \"object\",\n \"required\": [\n \"message\"\n ],\n \"properties\": {\n \"message\": {\n \"type\": \"string\"\n },\n \"code\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"index\",\n \"error\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": false\n },\n \"index\": {\n \"type\": \"number\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"error\": {\n \"type\": \"object\",\n \"required\": [\n \"message\"\n ],\n \"properties\": {\n \"message\": {\n \"type\": \"string\"\n },\n \"code\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n }\n ]\n }\n }\n },\n \"additionalProperties\": false\n },\n \"paneListResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"panes\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"repo\": {\n \"$ref\": \"#/jsonSchemas/repoListResult/properties/repos/items\"\n },\n \"panes\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"id\",\n \"paneId\",\n \"name\",\n \"status\",\n \"worktreePath\",\n \"repoId\",\n \"panelCount\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"status\": {\n \"type\": \"string\"\n },\n \"worktreePath\": {\n \"type\": \"string\"\n },\n \"repoId\": {\n \"type\": \"number\"\n },\n \"repoName\": {\n \"type\": \"string\"\n },\n \"panelCount\": {\n \"type\": \"number\"\n },\n \"createdAt\": {\n \"type\": \"string\"\n },\n \"lastActivity\": {\n \"type\": \"string\"\n },\n \"archived\": {\n \"type\": \"boolean\"\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"additionalProperties\": false\n },\n \"paneArchiveRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"paneId\"\n ],\n \"properties\": {\n \"paneId\": {\n \"type\": \"string\"\n },\n \"force\": {\n \"type\": \"boolean\"\n },\n \"source\": {\n \"enum\": [\n \"user\",\n \"agent\"\n ]\n }\n },\n \"additionalProperties\": false\n },\n \"paneArchiveResult\": {\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"paneId\",\n \"archived\",\n \"forced\",\n \"worktreeCleanup\",\n \"safetyCheck\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"archived\": {\n \"const\": true\n },\n \"forced\": {\n \"type\": \"boolean\"\n },\n \"worktreeCleanup\": {\n \"enum\": [\n \"completed\",\n \"failed\",\n \"timeout\",\n \"not-applicable\"\n ]\n },\n \"worktreePath\": {\n \"type\": \"string\"\n },\n \"safetyCheck\": {\n \"type\": \"object\",\n \"required\": [\n \"performed\"\n ],\n \"properties\": {\n \"performed\": {\n \"type\": \"boolean\"\n },\n \"hasUncommittedChanges\": {\n \"type\": \"boolean\"\n },\n \"hasUntrackedFiles\": {\n \"type\": \"boolean\"\n },\n \"hasUpstream\": {\n \"type\": \"boolean\"\n },\n \"unpushedCommits\": {\n \"type\": \"number\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"paneId\",\n \"blocked\",\n \"nextCommand\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": false\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"blocked\": {\n \"type\": \"object\",\n \"required\": [\n \"code\",\n \"message\",\n \"safetyCheck\"\n ],\n \"properties\": {\n \"code\": {\n \"enum\": [\n \"uncommitted-changes\",\n \"unpushed-commits\",\n \"uncommitted-and-unpushed\",\n \"status-unknown\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"safetyCheck\": {\n \"type\": \"object\",\n \"required\": [\n \"performed\"\n ],\n \"properties\": {\n \"performed\": {\n \"type\": \"boolean\"\n },\n \"hasUncommittedChanges\": {\n \"type\": \"boolean\"\n },\n \"hasUntrackedFiles\": {\n \"type\": \"boolean\"\n },\n \"hasUpstream\": {\n \"type\": \"boolean\"\n },\n \"unpushedCommits\": {\n \"type\": \"number\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n ]\n },\n \"panelListResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"paneId\",\n \"panels\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panels\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"id\",\n \"panelId\",\n \"paneId\",\n \"type\",\n \"title\",\n \"active\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"type\": {\n \"type\": \"string\"\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"active\": {\n \"type\": \"boolean\"\n },\n \"initialized\": {\n \"type\": \"boolean\"\n },\n \"agentType\": {\n \"type\": \"string\"\n },\n \"isCliPanel\": {\n \"type\": \"boolean\"\n },\n \"position\": {\n \"type\": \"number\"\n },\n \"createdAt\": {\n \"type\": \"string\"\n },\n \"lastActiveAt\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"additionalProperties\": false\n },\n \"panelOutputResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"panelId\",\n \"limit\",\n \"returnedCount\",\n \"hasMore\",\n \"outputs\",\n \"text\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"limit\": {\n \"type\": \"number\"\n },\n \"returnedCount\": {\n \"type\": \"number\"\n },\n \"hasMore\": {\n \"type\": \"boolean\"\n },\n \"outputs\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"type\",\n \"data\",\n \"timestamp\"\n ],\n \"properties\": {\n \"type\": {\n \"type\": \"string\"\n },\n \"data\": {},\n \"timestamp\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"text\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelInputRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"panelId\",\n \"input\"\n ],\n \"properties\": {\n \"panelId\": {\n \"type\": \"string\"\n },\n \"input\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelInputResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"panelId\",\n \"inputBytes\",\n \"sentAt\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"inputBytes\": {\n \"type\": \"number\"\n },\n \"sentAt\": {\n \"type\": \"string\"\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"agentContextBriefResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"mode\",\n \"source\",\n \"summary\",\n \"rules\",\n \"tools\",\n \"detailCommand\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"mode\": {\n \"const\": \"brief\"\n },\n \"source\": {\n \"const\": \"runpane-contract\"\n },\n \"summary\": {\n \"type\": \"string\"\n },\n \"rules\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"tools\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"name\",\n \"summary\",\n \"arguments\"\n ],\n \"properties\": {\n \"name\": {\n \"type\": \"string\"\n },\n \"summary\": {\n \"type\": \"string\"\n },\n \"arguments\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n },\n \"additionalProperties\": false\n }\n },\n \"detailCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"agentContextCommandResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"mode\",\n \"source\",\n \"command\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"mode\": {\n \"const\": \"command\"\n },\n \"source\": {\n \"const\": \"runpane-contract\"\n },\n \"command\": {\n \"type\": \"object\",\n \"required\": [\n \"name\",\n \"summary\",\n \"details\",\n \"arguments\",\n \"examples\"\n ],\n \"properties\": {\n \"name\": {\n \"type\": \"string\"\n },\n \"summary\": {\n \"type\": \"string\"\n },\n \"details\": {\n \"type\": \"string\"\n },\n \"requiresPaneDaemon\": {\n \"type\": \"boolean\"\n },\n \"mutates\": {\n \"type\": \"boolean\"\n },\n \"arguments\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\"\n }\n },\n \"examples\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"jsonSchemas\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"notes\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"panelScreenResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"panelId\",\n \"source\",\n \"limit\",\n \"returnedLineCount\",\n \"hasMore\",\n \"text\",\n \"state\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"source\": {\n \"enum\": [\n \"alternateScreen\",\n \"scrollback\",\n \"persistedOutput\",\n \"empty\"\n ]\n },\n \"limit\": {\n \"type\": \"number\"\n },\n \"returnedLineCount\": {\n \"type\": \"number\"\n },\n \"hasMore\": {\n \"type\": \"boolean\"\n },\n \"text\": {\n \"type\": \"string\"\n },\n \"state\": {\n \"type\": \"object\",\n \"required\": [\n \"initialized\"\n ],\n \"properties\": {\n \"initialized\": {\n \"type\": \"boolean\"\n },\n \"isAlternateScreen\": {\n \"type\": \"boolean\"\n },\n \"activityStatus\": {\n \"enum\": [\n \"active\",\n \"idle\"\n ]\n },\n \"isCliReady\": {\n \"type\": \"boolean\"\n },\n \"isCliPanel\": {\n \"type\": \"boolean\"\n },\n \"agentType\": {\n \"enum\": [\n \"codex\",\n \"claude\"\n ]\n },\n \"lastActivity\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"initialInput\": {\n \"$ref\": \"#/jsonSchemas/paneCreateResult/properties/items/items/oneOf/0/properties/initialInput\"\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelSubmitRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"panelId\",\n \"input\"\n ],\n \"properties\": {\n \"panelId\": {\n \"type\": \"string\"\n },\n \"input\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelSubmitResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"panelId\",\n \"inputBytes\",\n \"enter\",\n \"sentAt\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"inputBytes\": {\n \"type\": \"number\"\n },\n \"enter\": {\n \"const\": \"cr\"\n },\n \"sentAt\": {\n \"type\": \"string\"\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelWaitResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"panelId\",\n \"condition\",\n \"matched\",\n \"timedOut\",\n \"elapsedMs\",\n \"state\",\n \"screen\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"condition\": {\n \"enum\": [\n \"initialized\",\n \"ready\",\n \"idle\",\n \"text\"\n ]\n },\n \"matched\": {\n \"type\": \"boolean\"\n },\n \"timedOut\": {\n \"type\": \"boolean\"\n },\n \"elapsedMs\": {\n \"type\": \"number\"\n },\n \"state\": {\n \"type\": \"object\",\n \"required\": [\n \"initialized\"\n ],\n \"properties\": {\n \"initialized\": {\n \"type\": \"boolean\"\n },\n \"isAlternateScreen\": {\n \"type\": \"boolean\"\n },\n \"activityStatus\": {\n \"enum\": [\n \"active\",\n \"idle\"\n ]\n },\n \"isCliReady\": {\n \"type\": \"boolean\"\n },\n \"isCliPanel\": {\n \"type\": \"boolean\"\n },\n \"agentType\": {\n \"enum\": [\n \"codex\",\n \"claude\"\n ]\n },\n \"lastActivity\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"blocked\": {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"message\"\n ],\n \"properties\": {\n \"kind\": {\n \"enum\": [\n \"codex-update\",\n \"agent-prompt\",\n \"unknown\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"suggestedCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"screen\": {\n \"type\": \"object\",\n \"required\": [\n \"source\",\n \"text\",\n \"hasMore\"\n ],\n \"properties\": {\n \"source\": {\n \"enum\": [\n \"alternateScreen\",\n \"scrollback\",\n \"persistedOutput\",\n \"empty\"\n ]\n },\n \"text\": {\n \"type\": \"string\"\n },\n \"hasMore\": {\n \"type\": \"boolean\"\n }\n },\n \"additionalProperties\": false\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"agentDoctorResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"agent\",\n \"command\",\n \"available\",\n \"checks\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"agent\": {\n \"enum\": [\n \"codex\",\n \"claude\"\n ]\n },\n \"command\": {\n \"type\": \"string\"\n },\n \"repo\": {\n \"$ref\": \"#/jsonSchemas/repoListResult/properties/repos/items\"\n },\n \"environment\": {\n \"enum\": [\n \"wsl\",\n \"windows\",\n \"linux\",\n \"macos\"\n ]\n },\n \"available\": {\n \"type\": \"boolean\"\n },\n \"executablePath\": {\n \"type\": \"string\"\n },\n \"version\": {\n \"type\": \"string\"\n },\n \"checks\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"name\",\n \"ok\",\n \"message\"\n ],\n \"properties\": {\n \"name\": {\n \"type\": \"string\"\n },\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"message\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"warnings\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n },\n \"additionalProperties\": false\n },\n \"panelCreateRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"paneId\",\n \"tool\"\n ],\n \"properties\": {\n \"paneId\": {\n \"type\": \"string\"\n },\n \"type\": {\n \"const\": \"terminal\"\n },\n \"tool\": {\n \"type\": \"object\",\n \"additionalProperties\": true\n },\n \"noFocus\": {\n \"type\": \"boolean\"\n },\n \"focus\": {\n \"type\": \"boolean\"\n },\n \"source\": {\n \"enum\": [\n \"user\",\n \"agent\"\n ]\n },\n \"waitReady\": {\n \"type\": \"boolean\"\n },\n \"readyTimeoutMs\": {\n \"type\": \"number\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelCreateResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"paneId\",\n \"panelId\",\n \"title\",\n \"active\",\n \"focused\",\n \"tool\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"active\": {\n \"type\": \"boolean\"\n },\n \"focused\": {\n \"type\": \"boolean\"\n },\n \"tool\": {\n \"type\": \"object\",\n \"required\": [\n \"title\",\n \"command\"\n ],\n \"properties\": {\n \"title\": {\n \"type\": \"string\"\n },\n \"command\": {\n \"type\": \"string\"\n },\n \"agent\": {\n \"enum\": [\n \"codex\",\n \"claude\"\n ]\n }\n },\n \"additionalProperties\": false\n },\n \"readiness\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"condition\",\n \"matched\",\n \"timedOut\",\n \"elapsedMs\",\n \"state\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"condition\": {\n \"enum\": [\n \"initialized\",\n \"ready\",\n \"idle\",\n \"text\"\n ]\n },\n \"matched\": {\n \"type\": \"boolean\"\n },\n \"timedOut\": {\n \"type\": \"boolean\"\n },\n \"elapsedMs\": {\n \"type\": \"number\"\n },\n \"state\": {\n \"type\": \"object\",\n \"required\": [\n \"initialized\"\n ],\n \"properties\": {\n \"initialized\": {\n \"type\": \"boolean\"\n },\n \"isAlternateScreen\": {\n \"type\": \"boolean\"\n },\n \"activityStatus\": {\n \"enum\": [\n \"active\",\n \"idle\"\n ]\n },\n \"isCliReady\": {\n \"type\": \"boolean\"\n },\n \"isCliPanel\": {\n \"type\": \"boolean\"\n },\n \"agentType\": {\n \"enum\": [\n \"codex\",\n \"claude\"\n ]\n },\n \"lastActivity\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"blocked\": {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"message\"\n ],\n \"properties\": {\n \"kind\": {\n \"enum\": [\n \"codex-update\",\n \"agent-prompt\",\n \"unknown\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"suggestedCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelSubmitComposerRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"panelId\"\n ],\n \"properties\": {\n \"panelId\": {\n \"type\": \"string\"\n },\n \"strategy\": {\n \"enum\": [\n \"auto\",\n \"codex-ctrl-enter\",\n \"enter\"\n ]\n }\n },\n \"additionalProperties\": false\n },\n \"panelSubmitComposerResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"panelId\",\n \"inputBytes\",\n \"strategy\",\n \"sequenceName\",\n \"verifiedSubmitted\",\n \"sentAt\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"inputBytes\": {\n \"type\": \"number\"\n },\n \"strategy\": {\n \"enum\": [\n \"codex-ctrl-enter\",\n \"enter\"\n ]\n },\n \"sequenceName\": {\n \"enum\": [\n \"codex-ctrl-enter-cr\",\n \"enter-cr\"\n ]\n },\n \"verifiedSubmitted\": {\n \"type\": \"boolean\"\n },\n \"sentAt\": {\n \"type\": \"string\"\n },\n \"blocked\": {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"message\"\n ],\n \"properties\": {\n \"kind\": {\n \"enum\": [\n \"codex-update\",\n \"agent-prompt\",\n \"unknown\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"suggestedCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"agentContext\": {\n \"brief\": {\n \"title\": \"Pane agent context\",\n \"summary\": \"Pane lets a developer manage saved base repositories, user-visible Panes (Pane sessions) for feature/PR work, and terminal-backed panel tabs. A Pane is a visible workspace that normally maps to one Pane-managed git worktree and branch; a panel is a terminal tab inside one Pane and shares that Pane's worktree; an agent is a CLI process running inside a panel.\",\n \"rules\": [\n \"Start with `runpane doctor --json` to understand wrapper, platform, daemon reachability, and the next safe commands before mutating Pane state.\",\n \"In a Pane repository checkout, if `runpane` is not on PATH, use the built local wrapper with Node 22, for example `PATH=/opt/homebrew/opt/node@22/bin:$PATH node packages/runpane/dist/cli.js doctor --json`.\",\n \"Happy path for any user request to use Pane/RunPane: run `runpane doctor --json`, read `runpane agent-context --json`, resolve or add the saved base repo, create the requested visible Pane with a complete command such as `runpane panes create --repo <repo> --name <name> --agent <agent> --prompt \\\"<task>\\\" --source agent --no-focus --wait-ready --yes --json` (or use `--tool-command <command>` instead of `--agent <agent>`), then validate with `panels wait` or `panels screen`.\",\n \"Treat Pane as the user's visible cockpit for watching/co-driving work. Do not create Panes or panels for private delegation unless the user asked for visible Pane orchestration or the result should appear in the Pane app.\",\n \"Register the saved base repository once with `repos add`; do not register a pre-created worktree as a separate repo unless the user explicitly asks.\",\n \"Use `panes create` for separate visible Panes (Pane sessions) for feature/PR work. Pane creates and owns the worktree/branch for each new Pane.\",\n \"Use `panels create` for reviewer/helper/clean-context tabs that should stay inside an existing Pane and share that Pane's worktree.\",\n \"For private background decomposition, use your normal subagent/worktree mechanism instead of Pane.\",\n \"Skill routing: when the user says `discussion`, `plan`, `simple-plan`, `create-plan`, or `implement`, or asks for those behaviors, treat three references as peer context: Pane's local skill cache under `<PANE_DIR>/skills/`, the Pane Chat orchestrator handoff at `<PANE_DIR>/skills/pane-chat/runpane-orchestrator.md` when present, and the workflow map at https://github.com/dcouple/skills/raw/main/docs/readme-workflow-map.png. For where the local cache and Pane Chat bootstrap live in Pane, reference https://github.com/dcouple/Pane/pull/291. Use GitHub reads against https://github.com/dcouple/skills/tree/main/parsa only to inspect or refresh referenced skill files; do not clone or install it unless the user asks.\",\n \"When an agent creates Panes or panels, pass `--source agent --no-focus --wait-ready --yes --json` unless the user explicitly wants focus moved.\",\n \"Use `runpane agent-context --json` for the full agent-facing CLI context, or `runpane agent-context --command <command> --json` for one detailed command definition.\",\n \"For `agent-context --command`, use canonical spaced names like `panes create`; copied forms like `panes.create` or `runpane panes create` are accepted too.\",\n \"Use `runpane repos list --json` to find the saved repository when unsure after doctor shows the daemon is reachable.\",\n \"If WSL cannot reach Pane or `runpane` resolves to a broken Windows shim, the user may be running Windows Pane; try `powershell.exe -NoProfile -Command 'Set-Location $env:TEMP; runpane doctor --json'`.\",\n \"If the repository exists on disk but is not saved in Pane, use `runpane repos add --path <repo> --yes --json` before creating panes.\",\n \"Use `runpane agents doctor --agent <codex|claude> --repo <selector> --json` when agent availability differs across host, Windows, WSL, or repo environments.\",\n \"Use `runpane panes create --wait-ready` to create Panes and validate initial terminal readiness in one call.\",\n \"Use `runpane panels screen` for compact current state, `panels wait` for readiness/text checks, `panels submit` for ordinary Enter-submitted input, and `panels submit-composer --strategy auto` for agent composers.\",\n \"Use `runpane panels input` only when exact bytes are required, such as Ctrl-C or handcrafted terminal input.\",\n \"After creating Panes or sending terminal input, validate with `panels wait` or bounded `panels screen` before reporting success.\"\n ],\n \"detailCommand\": \"runpane agent-context --command <command> [--json]\",\n \"tools\": [\n {\n \"name\": \"doctor\",\n \"summary\": \"Report wrapper, platform, installed Pane, and daemon reachability before an agent mutates Pane state.\",\n \"arguments\": [\n \"--json\",\n \"--pane-dir <path>\",\n \"--pane-path <path>\"\n ]\n },\n {\n \"name\": \"agent-context\",\n \"summary\": \"Print token-efficient Pane command context for coding agents.\",\n \"arguments\": [\n \"--command <command>\",\n \"--json\"\n ]\n },\n {\n \"name\": \"agents doctor\",\n \"summary\": \"Check whether Codex or Claude is available in the repo environment Pane will use.\",\n \"arguments\": [\n \"--agent <codex|claude>\",\n \"--repo <selector>\",\n \"--json\"\n ]\n },\n {\n \"name\": \"repos list\",\n \"summary\": \"List repositories saved in the running Pane app.\",\n \"arguments\": [\n \"--json\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"repos add\",\n \"summary\": \"Register an existing git repository with the running Pane app.\",\n \"arguments\": [\n \"--path <path>\",\n \"--name <name>\",\n \"--yes\",\n \"--json\",\n \"--dry-run\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"panes list\",\n \"summary\": \"List Pane sessions, optionally scoped to a saved repository.\",\n \"arguments\": [\n \"--repo <selector>\",\n \"--json\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"panes create\",\n \"summary\": \"Create user-visible Panes (Pane sessions) backed by Pane-managed worktrees for feature/PR work and open terminal-backed tool tabs.\",\n \"arguments\": [\n \"--repo <selector>\",\n \"--name <name>\",\n \"--agent <codex|claude>\",\n \"--tool-command <command>\",\n \"--prompt <text>\",\n \"--initial-input-file <path|->\",\n \"--from-json <path|->\",\n \"--source <user|agent>\",\n \"--no-focus\",\n \"--focus\",\n \"--wait-ready\",\n \"--ready-timeout-ms <ms>\",\n \"--concurrency <count>\",\n \"--yes\",\n \"--json\"\n ]\n },\n {\n \"name\": \"panes archive\",\n \"summary\": \"Archive a Pane exactly like the UI Archive action, including safe removal of its Pane-managed git worktree.\",\n \"arguments\": [\n \"--pane <pane-id>\",\n \"--source <user|agent>\",\n \"--force\",\n \"--yes\",\n \"--json\"\n ]\n },\n {\n \"name\": \"panels create\",\n \"summary\": \"Create reviewer/helper terminal tabs inside an existing Pane; they share that Pane's worktree.\",\n \"arguments\": [\n \"--pane <pane-id>\",\n \"--agent <codex|claude>\",\n \"--tool-command <command>\",\n \"--source <user|agent>\",\n \"--no-focus\",\n \"--focus\",\n \"--wait-ready\",\n \"--yes\",\n \"--json\"\n ]\n },\n {\n \"name\": \"panels list\",\n \"summary\": \"List tool panels inside a Pane session.\",\n \"arguments\": [\n \"--pane <pane-id>\",\n \"--json\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"panels output\",\n \"summary\": \"Read recent terminal output from a panel.\",\n \"arguments\": [\n \"--panel <panel-id>\",\n \"--limit <count>\",\n \"--json\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"panels screen\",\n \"summary\": \"Read a compact current-screen view from a terminal panel.\",\n \"arguments\": [\n \"--panel <panel-id>\",\n \"--limit <count>\",\n \"--json\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"panels input\",\n \"summary\": \"Send input bytes to a terminal panel.\",\n \"arguments\": [\n \"--panel <panel-id>\",\n \"--text <text>\",\n \"--input-file <path|->\",\n \"--yes\",\n \"--json\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"panels submit\",\n \"summary\": \"Send text plus terminal Enter to a terminal panel.\",\n \"arguments\": [\n \"--panel <panel-id>\",\n \"--text <text>\",\n \"--input-file <path|->\",\n \"--yes\",\n \"--json\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"panels submit-composer\",\n \"summary\": \"Submit an agent composer with the correct key sequence, including Ctrl+Enter for Codex.\",\n \"arguments\": [\n \"--panel <panel-id>\",\n \"--strategy <auto|codex-ctrl-enter|enter>\",\n \"--yes\",\n \"--json\"\n ]\n },\n {\n \"name\": \"panels wait\",\n \"summary\": \"Wait for terminal initialized, ready, idle, or text state with compact output.\",\n \"arguments\": [\n \"--panel <panel-id>\",\n \"--for <condition>\",\n \"--contains <text>\",\n \"--timeout-ms <ms>\",\n \"--interval-ms <ms>\",\n \"--json\",\n \"--pane-dir <path>\"\n ]\n }\n ]\n },\n \"commands\": {\n \"help\": {\n \"name\": \"help\",\n \"summary\": \"Show help for runpane or a specific command.\",\n \"details\": \"Use this when you need human-readable usage text for a command.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"command\",\n \"value\": \"<command>\",\n \"required\": false,\n \"description\": \"Optional command topic such as \\\"panes create\\\".\"\n }\n ],\n \"examples\": [\n \"runpane help\",\n \"runpane help panes create\"\n ],\n \"notes\": [\n \"Help text is generated from the runpane contract.\"\n ]\n },\n \"setup\": {\n \"name\": \"setup\",\n \"summary\": \"Open the guided setup wizard for install, remote host setup, update, and diagnostics.\",\n \"details\": \"Use this for a human-driven Pane setup flow, not for unattended agent orchestration.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": false,\n \"arguments\": [],\n \"examples\": [\n \"runpane setup\"\n ],\n \"notes\": [\n \"In non-interactive shells, setup prints help and exits successfully.\"\n ]\n },\n \"install\": {\n \"name\": \"install\",\n \"summary\": \"Install Pane on this machine or configure this machine as a remote daemon host.\",\n \"details\": \"Installs or launches Pane release artifacts at command runtime. `install daemon` forwards remote setup flags to Pane.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"target\",\n \"value\": \"<client|daemon>\",\n \"required\": false,\n \"description\": \"Install the desktop client or configure a remote daemon host.\"\n },\n {\n \"name\": \"--version\",\n \"value\": \"<latest|vX.Y.Z>\",\n \"required\": false,\n \"description\": \"Pane release to install.\"\n },\n {\n \"name\": \"--format\",\n \"value\": \"<auto|appimage|deb|dmg|zip|exe>\",\n \"required\": false,\n \"description\": \"Release artifact format.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip interactive prompts where possible.\"\n }\n ],\n \"examples\": [\n \"runpane install client\",\n \"runpane install daemon --label \\\"My Server\\\"\"\n ],\n \"notes\": [\n \"Package install itself is inert; work begins only when runpane is executed.\"\n ]\n },\n \"update\": {\n \"name\": \"update\",\n \"summary\": \"Update the Pane desktop app using the same artifact path as install client.\",\n \"details\": \"Resolves and installs the selected Pane client release.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--version\",\n \"value\": \"<latest|vX.Y.Z>\",\n \"required\": false,\n \"description\": \"Pane release to update to.\"\n },\n {\n \"name\": \"--format\",\n \"value\": \"<auto|appimage|deb|dmg|zip|exe>\",\n \"required\": false,\n \"description\": \"Release artifact format.\"\n },\n {\n \"name\": \"--dry-run\",\n \"required\": false,\n \"description\": \"Print the update plan without downloading.\"\n }\n ],\n \"examples\": [\n \"runpane update\",\n \"runpane update --version latest\"\n ],\n \"notes\": [\n \"Equivalent artifact selection to `runpane install client`.\"\n ]\n },\n \"version\": {\n \"name\": \"version\",\n \"summary\": \"Print the runpane wrapper version without contacting, launching, or focusing Pane.\",\n \"details\": \"Use this to confirm which wrapper is available. App, daemon, and release diagnostics belong in doctor.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--pane-path\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Ignored for version; retained only for parser compatibility.\"\n }\n ],\n \"examples\": [\n \"runpane version\",\n \"runpane --version\"\n ],\n \"notes\": []\n },\n \"doctor\": {\n \"name\": \"doctor\",\n \"summary\": \"Run platform, release, installed Pane, daemon reachability, and remote setup diagnostics.\",\n \"details\": \"Use this first when an agent needs to understand whether Pane is installed, which wrapper/runtime is running, what daemon endpoint is expected, and whether the running Pane app is reachable. JSON mode should return a report even when the daemon is unreachable.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print a machine-readable environment report.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n },\n {\n \"name\": \"--pane-path\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Inspect a specific Pane executable.\"\n },\n {\n \"name\": \"--format\",\n \"value\": \"<format>\",\n \"required\": false,\n \"description\": \"Release artifact format to inspect.\"\n },\n {\n \"name\": \"--verbose\",\n \"required\": false,\n \"description\": \"Print extra diagnostics.\"\n }\n ],\n \"examples\": [\n \"runpane doctor --json\",\n \"runpane doctor\"\n ],\n \"jsonSchemas\": [\n \"doctorResult\"\n ],\n \"notes\": [\n \"Agents should run `runpane doctor --json` before mutating Pane state.\",\n \"The JSON report includes daemon reachability as data; an unreachable daemon is not a reason to skip the rest of the report.\",\n \"Use `runpane agent-context --json` after doctor when full CLI context is needed.\"\n ]\n },\n \"agent-context\": {\n \"name\": \"agent-context\",\n \"summary\": \"Print token-efficient Pane command context for coding agents.\",\n \"details\": \"Use this first when an agent needs to discover Pane primitives. It is local/offline and does not require a running Pane app.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--command\",\n \"value\": \"<command>\",\n \"required\": false,\n \"description\": \"Print the detailed definition for one command, for example \\\"panes create\\\".\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n }\n ],\n \"examples\": [\n \"runpane agent-context\",\n \"runpane agent-context --command \\\"panes create\\\" --json\"\n ],\n \"jsonSchemas\": [\n \"agentContextBriefResult\",\n \"agentContextCommandResult\"\n ],\n \"notes\": [\n \"Default output is brief so AGENTS.md can point here without bloating context.\",\n \"`--command` accepts canonical spaced names and common copied forms, including `panes.create` and `runpane panes create`.\"\n ]\n },\n \"repos list\": {\n \"name\": \"repos list\",\n \"summary\": \"List repositories saved in the running Pane app.\",\n \"details\": \"Use this to find the right Pane-managed repository before creating panes. If the repo is missing, use `repos add` first.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable repository records.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane repos list --json\"\n ],\n \"jsonSchemas\": [\n \"repoListResult\"\n ],\n \"notes\": [\n \"Requires a running Pane app or daemon for the selected Pane data directory.\",\n \"If this fails from WSL with a missing `/tmp/pane-daemon.../daemon.sock`, `volta: command not found`, or a Windows shim error, the user may be running Windows Pane. Retry via `powershell.exe -NoProfile -Command 'Set-Location $env:TEMP; runpane repos list --json'`.\",\n \"When using PowerShell from WSL, set a Windows cwd such as `$env:TEMP` or `$env:USERPROFILE` before running runpane to avoid UNC cwd issues.\"\n ]\n },\n \"repos add\": {\n \"name\": \"repos add\",\n \"summary\": \"Register an existing git repository with the running Pane app.\",\n \"details\": \"Use this when the repository exists on disk but is not saved in Pane yet. It validates the path as a git repository.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--path\",\n \"value\": \"<path>\",\n \"required\": true,\n \"description\": \"Existing git repository path to register with Pane.\"\n },\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"required\": false,\n \"description\": \"Saved repository name; defaults to the directory name.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for mutating commands.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--dry-run\",\n \"required\": false,\n \"description\": \"Validate and preview without adding the repo.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane repos add --path /path/to/repo --yes --json\"\n ],\n \"jsonSchemas\": [\n \"repoAddRequest\",\n \"repoAddResult\"\n ],\n \"notes\": [\n \"It does not create directories or initialize git repositories by default.\",\n \"For a Windows Pane app managing WSL repositories, prefer a saved WSL repo from `repos list` when possible. Adding a brand-new WSL repo from Windows may require WSL-aware path handling.\"\n ]\n },\n \"panes list\": {\n \"name\": \"panes list\",\n \"summary\": \"List Pane sessions, optionally scoped to a saved repository.\",\n \"details\": \"Use this after selecting a repository to find existing Pane sessions and their ids before inspecting panels.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--repo\",\n \"value\": \"<selector>\",\n \"required\": false,\n \"description\": \"Optional repository selector: active, id, exact path, or saved repository name.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes list --repo active --json\"\n ],\n \"jsonSchemas\": [\n \"paneListResult\"\n ],\n \"notes\": [\n \"Without --repo, this lists sessions across saved Pane repositories.\"\n ]\n },\n \"panes create\": {\n \"name\": \"panes create\",\n \"summary\": \"Create user-visible Panes (Pane sessions) backed by Pane-managed worktrees for feature/PR work and open terminal-backed tool tabs.\",\n \"details\": \"Use this when the user wants separate visible Panes (Pane sessions) for feature or PR work. Select the saved base repository, choose a built-in agent or custom terminal command, and optionally send initial input after the tool starts. Pane creates and owns a git worktree/branch for each new Pane; do not pre-create a git worktree and register it as a separate repo unless the user explicitly asks. For private background decomposition, use your normal subagent/worktree mechanism instead of Pane.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--repo\",\n \"value\": \"<selector>\",\n \"required\": true,\n \"description\": \"Repository selector: active, id, exact path, or saved repository name.\"\n },\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"required\": true,\n \"description\": \"Pane/session name.\"\n },\n {\n \"name\": \"--agent\",\n \"value\": \"<codex|claude>\",\n \"required\": false,\n \"description\": \"Built-in agent terminal template to open.\"\n },\n {\n \"name\": \"--tool-command\",\n \"value\": \"<command>\",\n \"required\": false,\n \"description\": \"Custom terminal command to run instead of a built-in agent.\"\n },\n {\n \"name\": \"--prompt\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Alias for --initial-input; sends text after the command is ready.\"\n },\n {\n \"name\": \"--initial-input-file\",\n \"value\": \"<path|->\",\n \"required\": false,\n \"description\": \"Read initial input from a file or stdin.\"\n },\n {\n \"name\": \"--from-json\",\n \"value\": \"<path|->\",\n \"required\": false,\n \"description\": \"Read a full panes.create request JSON payload.\"\n },\n {\n \"name\": \"--source\",\n \"value\": \"<user|agent>\",\n \"required\": false,\n \"description\": \"Mutation source; agent implies background/no-focus creation.\"\n },\n {\n \"name\": \"--no-focus\",\n \"required\": false,\n \"description\": \"Create the pane in the background without stealing focus.\"\n },\n {\n \"name\": \"--focus\",\n \"required\": false,\n \"description\": \"Explicitly focus the created pane.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for mutating commands.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--wait-ready\",\n \"required\": false,\n \"description\": \"Wait for each created terminal panel to be ready before returning.\"\n },\n {\n \"name\": \"--ready-timeout-ms\",\n \"value\": \"<ms>\",\n \"required\": false,\n \"description\": \"Readiness timeout per pane; defaults to 30000.\"\n },\n {\n \"name\": \"--concurrency\",\n \"value\": \"<count>\",\n \"required\": false,\n \"description\": \"Accepted for compatibility. Pane currently serializes multi-pane session creation so queued jobs do not time out before starting.\"\n }\n ],\n \"examples\": [\n \"runpane panes create --repo active --name issue-257 --agent <agent> --prompt \\\"Plan this issue\\\" --source agent --no-focus --wait-ready --yes --json\",\n \"runpane panes create --from-json panes.json --yes --json\",\n \"runpane panes create --repo active --name issue-123 --agent <agent> --prompt \\\"Plan this issue\\\" --source agent --no-focus --wait-ready --yes --json\"\n ],\n \"jsonSchemas\": [\n \"paneCreateRequest\",\n \"paneCreateResult\"\n ],\n \"notes\": [\n \"At least one of --agent or --tool-command is required unless --from-json is used.\",\n \"`panes create` is for user-visible Pane orchestration, not the agent's default private delegation mechanism.\",\n \"Register the saved base repository once. Pane creates and owns the worktree/branch for each new Pane.\",\n \"Use `panels create` instead when a reviewer/helper should share an existing Pane's worktree.\",\n \"Agent-created Panes should pass `--source agent --no-focus --wait-ready --yes --json` unless the user explicitly wants focus moved.\",\n \"The built-in agent templates come from the runpane contract; custom terminal commands can pass agent-specific flags when requested by the user.\",\n \"Use --initial-input-file for multi-line prompts or shell-sensitive initial input.\",\n \"When the JSON result includes nextCommand, run it to validate that the terminal produced output before reporting success.\",\n \"Multi-pane requests are created sequentially today. The --concurrency flag is accepted for compatibility, but agents should not rely on parallel creation.\",\n \"For POSIX or WSL command chaining, use a custom terminal command like `bash -lc 'cmd1 && cmd2 && cmd3'`.\",\n \"For Windows PowerShell command chaining, use a custom terminal command like `powershell -NoProfile -Command \\\"cmd1; if ($LASTEXITCODE) { exit $LASTEXITCODE }; cmd2\\\"`.\",\n \"From WSL with Windows Pane, invoke through PowerShell and select the saved WSL repo by name or id, for example `powershell.exe -NoProfile -Command 'Set-Location $env:TEMP; runpane panes create --repo \\\"WSL Pane\\\" --name issue-123 --agent <agent> --prompt \\\"Plan this issue\\\" --source agent --no-focus --wait-ready --yes --json'`.\",\n \"Use --wait-ready when an agent needs to verify that an agent terminal started instead of only creating a pane.\",\n \"If readiness returns blocked, inspect blocked.suggestedCommand rather than guessing which prompt to answer.\"\n ]\n },\n \"panes archive\": {\n \"name\": \"panes archive\",\n \"summary\": \"Archive a Pane (session) exactly like the UI Archive action, including safe removal of its Pane-managed git worktree.\",\n \"details\": \"Use this to close out a Pane once its PR has merged. Refuses to archive (unless --force) when the pane's branch has uncommitted, untracked, or unpushed-to-remote changes, so an agent cannot silently discard work. A merged and pushed branch has no unpushed commits, so it archives cleanly. Waits for worktree removal to finish before returning.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane/session id to archive.\"\n },\n {\n \"name\": \"--source\",\n \"value\": \"<user|agent>\",\n \"required\": false,\n \"description\": \"Mutation source; does not change archive safety behavior.\"\n },\n {\n \"name\": \"--force\",\n \"required\": false,\n \"description\": \"Archive even if the pane's branch has uncommitted, untracked, or unpushed changes.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for mutating commands.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n }\n ],\n \"examples\": [\n \"runpane panes archive --pane <pane-id> --source agent --yes --json\",\n \"runpane panes archive --pane <pane-id> --force --yes --json\"\n ],\n \"jsonSchemas\": [\n \"paneArchiveRequest\",\n \"paneArchiveResult\"\n ],\n \"notes\": [\n \"If the result has ok:false with a blocked field, the pane was NOT archived; inspect blocked.code and rerun with --force if discarding the flagged work is intentional.\",\n \"A successful archive waits for the Pane-managed worktree to be removed before returning; check worktreeCleanup in the result for the final outcome.\",\n \"Archiving a main-repo Pane (no Pane-managed worktree) always succeeds immediately since nothing is deleted from disk.\"\n ]\n },\n \"panels list\": {\n \"name\": \"panels list\",\n \"summary\": \"List tool panels inside a Pane session.\",\n \"details\": \"Use this to find the terminal panel id to inspect or control after creating or selecting a Pane session.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane/session id.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panels list --pane <pane-id> --json\"\n ],\n \"jsonSchemas\": [\n \"panelListResult\"\n ],\n \"notes\": [\n \"The ids returned here are stable inputs for `panels output` and `panels input`.\"\n ]\n },\n \"panels output\": {\n \"name\": \"panels output\",\n \"summary\": \"Read recent terminal output from a panel.\",\n \"details\": \"Use this to inspect recent terminal output from a terminal-backed panel without loading the full history by default. Live terminal scrollback is returned as bounded recent lines; persisted output fallback is returned as bounded records. Terminal control noise is stripped before returning text.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": true,\n \"description\": \"Tool panel id.\"\n },\n {\n \"name\": \"--limit\",\n \"value\": \"<count>\",\n \"required\": false,\n \"description\": \"Maximum recent output lines or records to read. Defaults to 200.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panels output --panel <panel-id> --limit 200 --json\"\n ],\n \"jsonSchemas\": [\n \"panelOutputResult\"\n ],\n \"notes\": [\n \"Default output is bounded to the latest 200 lines or records. Use a larger --limit only when hasMore is true and more history is needed.\",\n \"Use --json when an agent needs timestamps, record types, returnedCount, or hasMore.\",\n \"The output is intended to be agent-readable, but terminal prompts and shell echoes may still appear; use the newest relevant lines before concluding success.\",\n \"Prefer `panels screen` for a smaller current-state read before increasing output limits.\"\n ]\n },\n \"panels input\": {\n \"name\": \"panels input\",\n \"summary\": \"Send input bytes to a terminal panel.\",\n \"details\": \"Use this to answer prompts or continue an agent inside an existing Pane terminal panel. Input is byte-oriented; choose --input-file for exact control over newlines and control characters.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": true,\n \"description\": \"Terminal panel id.\"\n },\n {\n \"name\": \"--text\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Text bytes to send.\"\n },\n {\n \"name\": \"--input-file\",\n \"value\": \"<path|->\",\n \"required\": false,\n \"description\": \"Read input from a file or stdin.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for this mutating command.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"printf 'Continue\\\\n' | runpane panels input --panel <panel-id> --input-file - --yes\",\n \"printf '\\\\003' | runpane panels input --panel <panel-id> --input-file - --yes\",\n \"runpane panels input --panel <panel-id> --text \\\"simple text\\\" --yes --json\"\n ],\n \"jsonSchemas\": [\n \"panelInputRequest\",\n \"panelInputResult\"\n ],\n \"notes\": [\n \"Input is sent exactly as provided. Include a real newline byte when the terminal should receive Enter; across shells, `--input-file` is safer than `--text \\\"...\\\\n\\\"`.\",\n \"Use `--input-file -` or a temp file for multi-line input, quotes, Ctrl-C, or shell-sensitive text.\",\n \"If interrupting a running process, send Ctrl-C first, validate/read output, then send the next command in a separate `panels input` call so bytes are not dropped.\",\n \"After sending input, validate with `runpane panels output --panel <panel-id> --json` before reporting success.\",\n \"Runpane records action metadata and errors for observability, but should not log full input text by default.\",\n \"For ordinary text plus Enter, prefer `panels submit` so the terminal receives a CR Enter byte.\"\n ]\n },\n \"agents doctor\": {\n \"name\": \"agents doctor\",\n \"summary\": \"Diagnose whether a built-in agent command is available in a Pane repository environment.\",\n \"details\": \"Use this before creating Codex or Claude panes when PATH may differ between macOS/Linux/Windows/WSL or between the wrapper shell and Pane. The check runs through Pane project context, not the wrapper process.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--agent\",\n \"value\": \"<codex|claude>\",\n \"required\": true,\n \"description\": \"Built-in agent command to diagnose.\"\n },\n {\n \"name\": \"--repo\",\n \"value\": \"<selector>\",\n \"required\": false,\n \"description\": \"Repository selector; defaults to the active Pane repo.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane agents doctor --agent <agent> --repo active --json\"\n ],\n \"jsonSchemas\": [\n \"agentDoctorResult\"\n ],\n \"notes\": [\n \"For WSL repos, install the agent inside the WSL distro Pane uses, not only on Windows.\",\n \"This diagnoses built-in agent templates only; custom commands should be validated by creating a pane and reading its screen.\"\n ]\n },\n \"panels screen\": {\n \"name\": \"panels screen\",\n \"summary\": \"Read a compact current-screen view from a terminal panel.\",\n \"details\": \"Use this for token-safe current terminal state. It prefers active alternate-screen/TUI output, then live scrollback, then persisted output. Default output is bounded to 80 lines.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": true,\n \"description\": \"Terminal panel id.\"\n },\n {\n \"name\": \"--limit\",\n \"value\": \"<count>\",\n \"required\": false,\n \"description\": \"Maximum lines to return; defaults to 80.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panels screen --panel <panel-id> --limit 80 --json\"\n ],\n \"jsonSchemas\": [\n \"panelScreenResult\"\n ],\n \"notes\": [\n \"Use this before `panels output` when an agent only needs the latest visible/current state.\",\n \"If hasMore is true and context is missing, rerun with a larger --limit or use `panels output`.\"\n ]\n },\n \"panels submit\": {\n \"name\": \"panels submit\",\n \"summary\": \"Send text to a terminal panel and append a terminal Enter byte.\",\n \"details\": \"Use this for ordinary interactive submissions. The daemon normalizes a final LF or CRLF to CR, or appends CR if no newline is present. Exact byte workflows remain on `panels input`.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": true,\n \"description\": \"Terminal panel id.\"\n },\n {\n \"name\": \"--text\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Text to submit before Enter.\"\n },\n {\n \"name\": \"--input-file\",\n \"value\": \"<path|->\",\n \"required\": false,\n \"description\": \"Read text from a file or stdin before Enter.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for this mutating command.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panels submit --panel <panel-id> --text \\\"2\\\" --yes --json\",\n \"printf \\\"echo hello\\\" | runpane panels submit --panel <panel-id> --input-file - --yes --json\"\n ],\n \"jsonSchemas\": [\n \"panelSubmitRequest\",\n \"panelSubmitResult\"\n ],\n \"notes\": [\n \"The response includes nextCommand for validation. Run it before reporting that the input worked.\",\n \"Use `panels input` for Ctrl-C, escape sequences, or any workflow requiring exact bytes.\"\n ]\n },\n \"panels wait\": {\n \"name\": \"panels wait\",\n \"summary\": \"Wait for a terminal panel to initialize, become ready/idle, or contain text.\",\n \"details\": \"Use this to validate asynchronous terminal behavior without pulling large scrollback. It returns brief readiness state, blocker hints, a compact screen, and a next command.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": true,\n \"description\": \"Terminal panel id.\"\n },\n {\n \"name\": \"--for\",\n \"value\": \"<initialized|ready|idle|text>\",\n \"required\": false,\n \"description\": \"Condition to wait for. Defaults to ready for CLI panels and idle otherwise.\"\n },\n {\n \"name\": \"--contains\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Text required for --for text; implies --for text when --for is omitted.\"\n },\n {\n \"name\": \"--timeout-ms\",\n \"value\": \"<ms>\",\n \"required\": false,\n \"description\": \"Wait timeout; defaults to 30000.\"\n },\n {\n \"name\": \"--interval-ms\",\n \"value\": \"<ms>\",\n \"required\": false,\n \"description\": \"Polling interval; defaults to 500.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panels wait --panel <panel-id> --for ready --timeout-ms 30000 --json\",\n \"runpane panels wait --panel <panel-id> --contains \\\"Ready\\\" --json\"\n ],\n \"jsonSchemas\": [\n \"panelWaitResult\"\n ],\n \"notes\": [\n \"If blocked is present, do not assume success. Use blocked.suggestedCommand or inspect `panels screen`.\",\n \"The default timeout and screen are intentionally small for agent context safety.\"\n ]\n },\n \"panels create\": {\n \"name\": \"panels create\",\n \"summary\": \"Create a reviewer/helper terminal tab inside an existing Pane.\",\n \"details\": \"Use this to add reviewer, helper, or clean-context tabs to an existing Pane. The new panel shares the existing Pane's worktree and branch; it does not create a separate Pane session. Use `panes create` instead when the user wants a separate visible Pane (Pane session) for feature/PR work. Agent/orchestrator-created panels should pass --source agent or --no-focus so the user stays in the implementation tab. The daemon initializes the terminal immediately and can wait for CLI readiness.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Existing Pane session id to add the panel to.\"\n },\n {\n \"name\": \"--agent\",\n \"value\": \"<codex|claude>\",\n \"required\": false,\n \"description\": \"Built-in agent command template to launch.\"\n },\n {\n \"name\": \"--tool-command\",\n \"value\": \"<command>\",\n \"required\": false,\n \"description\": \"Custom terminal command to launch instead of a built-in agent.\"\n },\n {\n \"name\": \"--title\",\n \"value\": \"<title>\",\n \"required\": false,\n \"description\": \"Panel title override.\"\n },\n {\n \"name\": \"--initial-input\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Initial input to send after the tool starts.\"\n },\n {\n \"name\": \"--initial-input-file\",\n \"value\": \"<path|->\",\n \"required\": false,\n \"description\": \"Read initial input from a file or stdin.\"\n },\n {\n \"name\": \"--source\",\n \"value\": \"<user|agent>\",\n \"required\": false,\n \"description\": \"Mutation source; agent implies background creation.\"\n },\n {\n \"name\": \"--no-focus\",\n \"required\": false,\n \"description\": \"Create the panel in the background without changing active panel.\"\n },\n {\n \"name\": \"--focus\",\n \"required\": false,\n \"description\": \"Explicitly focus the created panel.\"\n },\n {\n \"name\": \"--wait-ready\",\n \"required\": false,\n \"description\": \"Wait until the terminal tool is ready.\"\n },\n {\n \"name\": \"--ready-timeout-ms\",\n \"value\": \"<ms>\",\n \"required\": false,\n \"description\": \"Readiness wait timeout.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for this mutating command.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n }\n ],\n \"examples\": [\n \"runpane panels create --pane <pane-id> --agent <agent> --source agent --no-focus --wait-ready --yes --json\",\n \"runpane panels create --pane <pane-id> --tool-command <command> --title <title> --source agent --no-focus --wait-ready --yes --json\"\n ],\n \"jsonSchemas\": [\n \"panelCreateRequest\",\n \"panelCreateResult\"\n ],\n \"notes\": [\n \"Use this for same-pane reviewer loops after PR creation/testing.\",\n \"Panels share the existing Pane's worktree and branch.\",\n \"`panels create` is for visible helper/reviewer tabs, not the agent's default private delegation mechanism.\",\n \"For agent-created panels, prefer --source agent or --no-focus to avoid stealing focus from the user or implementation tab.\"\n ]\n },\n \"panels submit-composer\": {\n \"name\": \"panels submit-composer\",\n \"summary\": \"Submit an agent composer using the panel-appropriate key sequence.\",\n \"details\": \"Use this after sending text into an agent composer when plain Enter is not the right submit action. Agents should use --strategy auto; Pane owns per-agent submit sequences internally and verifies visible composer state when possible.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": true,\n \"description\": \"Terminal panel id.\"\n },\n {\n \"name\": \"--strategy\",\n \"value\": \"<auto|codex-ctrl-enter|enter>\",\n \"required\": false,\n \"description\": \"Composer submit key sequence strategy.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for this mutating command.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n }\n ],\n \"examples\": [\n \"runpane panels submit-composer --panel <codex-panel-id> --strategy auto --yes --json\",\n \"runpane panels submit-composer --panel <panel-id> --strategy enter --yes --json\"\n ],\n \"jsonSchemas\": [\n \"panelSubmitComposerRequest\",\n \"panelSubmitComposerResult\"\n ],\n \"notes\": [\n \"Use `panels input` or `panels submit` to write prompt text first; this command only submits the current composer.\",\n \"Use --strategy auto for agent workflows; explicit strategies are diagnostic escape hatches.\",\n \"The JSON result includes sequenceName and verifiedSubmitted. If ok is false, follow blocked.suggestedCommand instead of assuming submission happened.\"\n ]\n }\n },\n \"managedBlock\": [\n \"## Pane\",\n \"\",\n \"The developer is using Pane for this repository. Pane can manage saved repositories and create user-visible Panes with terminal-backed tools for planning, discussion, implementation, and review work.\",\n \"\",\n \"This managed guidance was created by [runpane.com](https://runpane.com) for the [Pane repository](https://github.com/dcouple/Pane). Do not delete or overwrite this block; the developer uses it for their workflow and it should remain committed to the main repository unless they explicitly ask to remove it.\",\n \"\",\n \"Pane mental model: a repository is the saved base repo; a Pane is a user-visible feature/PR workspace (Pane session) that normally maps to one Pane-managed git worktree and branch; a panel/tab is a terminal inside one Pane and shares that Pane's worktree; an agent is the CLI process running in a panel.\",\n \"\",\n \"Default happy path when the user asks you to use Pane or RunPane: run `runpane doctor --json`; read `runpane agent-context --json`; resolve the saved base repository with `runpane repos list --json` or add it once with `runpane repos add --path <repo> --yes --json`; create one visible Pane (Pane session) for the requested feature/PR with a complete command such as `runpane panes create --repo <repo> --name <name> --agent <agent> --prompt \\\"<task>\\\" --source agent --no-focus --wait-ready --yes --json` or the equivalent `--tool-command <command>` form; then validate with `runpane panels wait` or `runpane panels screen` before reporting progress.\",\n \"\",\n \"Use Pane when the user wants visible Panes or co-drivable parallel feature/PR workspaces. Do not use Pane as your default private delegation mechanism; for private background decomposition, use your normal subagent/worktree workflow.\",\n \"\",\n \"Register the main/base repository once. Do not register pre-created git worktrees as separate Pane repositories unless the user explicitly asks.\",\n \"\",\n \"Use `runpane panes create` for separate visible Panes (Pane sessions) for feature/PR work. Use `runpane panels create` for reviewer/helper tabs inside an existing Pane that should share that Pane's worktree.\",\n \"\",\n \"Typical workflow: register the saved base repository once; create one Pane (Pane session) per feature/PR; use panels/tabs inside that Pane for helper or reviewer agents that should share the worktree; archive the Pane after the PR is done to remove it from active Panes and clean up its managed worktree when applicable.\",\n \"\",\n \"Skill routing reference: when the user says `discussion`, `plan`, `simple-plan`, `create-plan`, or `implement`, or asks for the behavior those words imply, treat three references as peer context: Pane's local skill cache under `<PANE_DIR>/skills/`, the Pane Chat orchestrator handoff at `<PANE_DIR>/skills/pane-chat/runpane-orchestrator.md` when present, and the [workflow map](https://github.com/dcouple/skills/raw/main/docs/readme-workflow-map.png).\",\n \"Use those peer references together to choose the phase: discuss/investigate until the work is clear enough to delegate, then ticket/plan/implement/review/PR-test/teach-back as appropriate. The orchestrator and workflow map may point to different skills; reconcile them with the user's request instead of hardcoding a skill list or treating one reference as subordinate.\",\n \"For the Pane implementation source of truth for where the skill cache, cached workflow assets, and Pane Chat bootstrap live, reference [PR #291](https://github.com/dcouple/Pane/pull/291): `main/src/services/skillCacheManager.ts` owns `<PANE_DIR>/skills/`, `.sources/dcouple-skills`, and `pane-chat/runpane-orchestrator.md`; `main/src/services/paneChatManager.ts` owns the tiny bootstrap prompt that tells the selected Pane Chat agent to read that guide.\",\n \"Use GitHub reads against the [Parsa skills folder](https://github.com/dcouple/skills/tree/main/parsa) only to inspect or refresh referenced skill files; do not clone/install the repo unless the user asks.\",\n \"Do not hardcode a specific assistant brand in workflow guidance. Use the Pane agent or custom tool command the user selected, and use `runpane agents doctor --agent <agent> --repo <selector> --json` only when checking a built-in agent template.\",\n \"\",\n \"Start with `runpane doctor --json` before taking Pane actions. Use it to understand wrapper/runtime details, daemon reachability, and the next safe commands.\",\n \"\",\n \"In a Pane repository checkout, if `runpane` is not on PATH, use the built local wrapper with Node 22: `PATH=/opt/homebrew/opt/node@22/bin:$PATH node packages/runpane/dist/cli.js doctor --json`.\",\n \"\",\n \"Use `runpane agent-context --json` for full Pane CLI context. Use `runpane agent-context --command \\\"panels wait\\\" --json` or another command name for detailed schema only when needed.\",\n \"\",\n \"Default to context-safe validation: after creating Panes or sending terminal input, run `runpane panels wait` or `runpane panels screen` before reporting success. Prefer `runpane panels submit` for normal text plus Enter; use `runpane panels input` only for exact bytes such as Ctrl-C or escape sequences.\",\n \"\",\n \"Common commands:\",\n \"- `runpane doctor --json`\",\n \"- `runpane agent-context --json`\",\n \"- `runpane repos list --json`\",\n \"- `runpane repos add --path <repo> --yes --json`\",\n \"- `runpane agents doctor --agent <agent> --repo active --json`\",\n \"- `runpane panes create --repo active --name <name> --agent <agent> --prompt \\\"<task>\\\" --source agent --no-focus --wait-ready --yes --json`\",\n \"- `runpane panels create --pane <pane-id> --agent <agent> --source agent --no-focus --wait-ready --yes --json`\",\n \"- `runpane panels list --pane <pane-id> --json`\",\n \"- `runpane panels screen --panel <panel-id> --limit 80 --json`\",\n \"- `runpane panels wait --panel <panel-id> --for ready --timeout-ms 30000 --json`\",\n \"- `runpane panels submit --panel <panel-id> --text \\\"<answer>\\\" --yes --json`\",\n \"- `runpane panels input --panel <panel-id> --input-file <path|-> --yes --json`\",\n \"\",\n \"WSL note: if `runpane doctor --json` cannot find `/tmp/pane-daemon.../daemon.sock` or `runpane` resolves to a broken Windows shim, Pane may be running on Windows. Try `powershell.exe -NoProfile -Command 'Set-Location $env:TEMP; runpane doctor --json'`, then create Panes through the same PowerShell form using the saved WSL repo name or id. Use `runpane agents doctor --agent <agent> --repo <selector> --json` to diagnose the repo environment Pane will actually use.\"\n ]\n }\n}") +RUNPANE_CONTRACT = json.loads("{\n \"$schema\": \"./schema.json\",\n \"schemaVersion\": 1,\n \"name\": \"runpane\",\n \"description\": \"Thin installer and local control CLI for Pane\",\n \"packageInstallPolicy\": [\n \"The packages must not download, install, or configure Pane during package installation.\",\n \"Work starts only when a user runs `runpane ...`.\"\n ],\n \"compatibility\": {\n \"node\": \">=18.17.0\",\n \"python\": \">=3.8\"\n },\n \"terminology\": {\n \"repo\": \"Saved Pane repository/project record\",\n \"pane\": \"User-visible Pane session\",\n \"tool\": \"Terminal-backed tab\",\n \"agent\": \"Built-in agent command template\"\n },\n \"defaults\": {\n \"target\": \"client\",\n \"paneVersion\": \"latest\",\n \"channel\": \"stable\",\n \"format\": \"auto\",\n \"dryRun\": false,\n \"yes\": false,\n \"verbose\": false\n },\n \"enums\": {\n \"installTargets\": [\n \"client\",\n \"daemon\"\n ],\n \"artifactFormats\": [\n \"auto\",\n \"appimage\",\n \"deb\",\n \"dmg\",\n \"zip\",\n \"exe\"\n ],\n \"channels\": [\n \"stable\",\n \"nightly\"\n ],\n \"agents\": [\n \"codex\",\n \"claude\"\n ]\n },\n \"agentTemplates\": {\n \"codex\": {\n \"title\": \"Codex\",\n \"command\": \"codex --yolo\",\n \"description\": \"Open a Codex terminal tab and allow the initial input to drive the agent.\"\n },\n \"claude\": {\n \"title\": \"Claude Code\",\n \"command\": \"claude --dangerously-skip-permissions\",\n \"description\": \"Open a Claude Code terminal tab and allow the initial input to drive the agent.\"\n }\n },\n \"commands\": [\n {\n \"name\": \"help\",\n \"summary\": \"Show help for runpane or a specific command.\",\n \"usage\": [\n \"runpane help [command]\"\n ]\n },\n {\n \"name\": \"setup\",\n \"summary\": \"Open the guided setup wizard for install, remote host setup, update, and diagnostics.\",\n \"usage\": [\n \"runpane setup\"\n ],\n \"interactiveEntrypoint\": true\n },\n {\n \"name\": \"install\",\n \"summary\": \"Install Pane on this machine or configure this machine as a remote daemon host.\",\n \"usage\": [\n \"runpane install [client|daemon] [options]\"\n ],\n \"defaultTarget\": \"client\",\n \"targets\": [\n \"client\",\n \"daemon\"\n ],\n \"unknownDaemonFlagsForwarded\": true\n },\n {\n \"name\": \"update\",\n \"summary\": \"Update the Pane desktop app using the same artifact path as install client.\",\n \"usage\": [\n \"runpane update [options]\"\n ],\n \"target\": \"client\"\n },\n {\n \"name\": \"version\",\n \"summary\": \"Print the runpane wrapper version without contacting, launching, or focusing Pane.\",\n \"usage\": [\n \"runpane version\",\n \"runpane --version\"\n ]\n },\n {\n \"name\": \"doctor\",\n \"summary\": \"Run platform, release, installed Pane, daemon reachability, and remote setup diagnostics.\",\n \"usage\": [\n \"runpane doctor [--json] [--pane-dir <path>] [--pane-path <path>] [--format <format>] [--verbose]\"\n ],\n \"jsonSchemas\": [\n \"doctorResult\"\n ]\n },\n {\n \"name\": \"agents doctor\",\n \"summary\": \"Diagnose whether a built-in agent command is available in a Pane repository environment.\",\n \"usage\": [\n \"runpane agents doctor --agent <codex|claude> [--repo <selector>] [--json]\"\n ],\n \"jsonSchemas\": [\n \"agentDoctorResult\"\n ]\n },\n {\n \"name\": \"agent-context\",\n \"summary\": \"Print token-efficient Pane command context for coding agents.\",\n \"usage\": [\n \"runpane agent-context [--json]\",\n \"runpane agent-context --command <command> [--json]\"\n ],\n \"jsonSchemas\": [\n \"agentContextBriefResult\",\n \"agentContextCommandResult\"\n ]\n },\n {\n \"name\": \"repos list\",\n \"summary\": \"List repositories saved in the running Pane app.\",\n \"usage\": [\n \"runpane repos list [--json] [--pane-dir <path>]\"\n ],\n \"jsonSchemas\": [\n \"repoListResult\"\n ]\n },\n {\n \"name\": \"repos add\",\n \"summary\": \"Register an existing git repository with the running Pane app.\",\n \"usage\": [\n \"runpane repos add --path <path> [--name <name>] [--json] [--yes]\"\n ],\n \"mutates\": true,\n \"jsonSchemas\": [\n \"repoAddRequest\",\n \"repoAddResult\"\n ]\n },\n {\n \"name\": \"panes list\",\n \"summary\": \"List Pane sessions in a saved repository.\",\n \"usage\": [\n \"runpane panes list [--repo <selector>] [--json]\"\n ],\n \"jsonSchemas\": [\n \"paneListResult\"\n ]\n },\n {\n \"name\": \"panes create\",\n \"summary\": \"Create user-visible Panes (Pane sessions) backed by Pane-managed worktrees for feature/PR work and open terminal-backed tool tabs.\",\n \"usage\": [\n \"runpane panes create --repo <selector> --name <name> --agent <codex|claude> [--source user|agent] [--focus|--no-focus] [options]\",\n \"runpane panes create --from-json <path|-> [--yes] [--json]\"\n ],\n \"mutates\": true,\n \"jsonSchemas\": [\n \"paneCreateRequest\",\n \"paneCreateResult\"\n ]\n },\n {\n \"name\": \"panes archive\",\n \"summary\": \"Archive a Pane (session) exactly like the UI Archive action, including safe removal of its Pane-managed git worktree.\",\n \"usage\": [\n \"runpane panes archive --pane <pane-id> [--source user|agent] [--force] --yes [--json]\"\n ],\n \"mutates\": true,\n \"jsonSchemas\": [\n \"paneArchiveRequest\",\n \"paneArchiveResult\"\n ]\n },\n {\n \"name\": \"panes pin\",\n \"summary\": \"Declaratively pin a Pane; pinned is the Pane UI's favorite/pin star and repeated requests are idempotent.\",\n \"usage\": [\n \"runpane panes pin --pane <pane-id> --yes [--dry-run] [--json]\"\n ],\n \"mutates\": true,\n \"jsonSchemas\": [\n \"panePinRequest\",\n \"panePinResult\"\n ]\n },\n {\n \"name\": \"panes unpin\",\n \"summary\": \"Declaratively unpin a Pane; pinned is the Pane UI's favorite/pin star and repeated requests are idempotent.\",\n \"usage\": [\n \"runpane panes unpin --pane <pane-id> --yes [--dry-run] [--json]\"\n ],\n \"mutates\": true,\n \"jsonSchemas\": [\n \"panePinRequest\",\n \"panePinResult\"\n ]\n },\n {\n \"name\": \"panels create\",\n \"summary\": \"Create a terminal-backed tool panel inside an existing Pane session.\",\n \"usage\": [\n \"runpane panels create --pane <pane-id> --agent <codex|claude> [--source user|agent] [--focus|--no-focus] [--wait-ready] --yes [--json]\",\n \"runpane panels create --pane <pane-id> --tool-command <command> [--title <title>] [--focus|--no-focus] --yes [--json]\"\n ],\n \"mutates\": true,\n \"jsonSchemas\": [\n \"panelCreateRequest\",\n \"panelCreateResult\"\n ]\n },\n {\n \"name\": \"panels list\",\n \"summary\": \"List tool panels inside a Pane session.\",\n \"usage\": [\n \"runpane panels list --pane <pane-id> [--json]\"\n ],\n \"jsonSchemas\": [\n \"panelListResult\"\n ]\n },\n {\n \"name\": \"panels output\",\n \"summary\": \"Read recent terminal output from a panel.\",\n \"usage\": [\n \"runpane panels output --panel <panel-id> [--limit <count>] [--json]\"\n ],\n \"jsonSchemas\": [\n \"panelOutputResult\"\n ]\n },\n {\n \"name\": \"panels screen\",\n \"summary\": \"Read a compact current-screen view from a terminal panel.\",\n \"usage\": [\n \"runpane panels screen --panel <panel-id> [--limit <count>] [--json]\"\n ],\n \"jsonSchemas\": [\n \"panelScreenResult\"\n ]\n },\n {\n \"name\": \"panels input\",\n \"summary\": \"Send input bytes to a terminal panel.\",\n \"usage\": [\n \"runpane panels input --panel <panel-id> (--text <text>|--input-file <path|->) --yes [--json]\"\n ],\n \"mutates\": true,\n \"jsonSchemas\": [\n \"panelInputRequest\",\n \"panelInputResult\"\n ]\n },\n {\n \"name\": \"panels submit\",\n \"summary\": \"Send text to a terminal panel and append a terminal Enter byte.\",\n \"usage\": [\n \"runpane panels submit --panel <panel-id> (--text <text>|--input-file <path|->) --yes [--json]\"\n ],\n \"mutates\": true,\n \"jsonSchemas\": [\n \"panelSubmitRequest\",\n \"panelSubmitResult\"\n ]\n },\n {\n \"name\": \"panels submit-composer\",\n \"summary\": \"Submit an agent composer using the panel-appropriate key sequence.\",\n \"usage\": [\n \"runpane panels submit-composer --panel <panel-id> [--strategy auto|codex-ctrl-enter|enter] --yes [--json]\"\n ],\n \"mutates\": true,\n \"jsonSchemas\": [\n \"panelSubmitComposerRequest\",\n \"panelSubmitComposerResult\"\n ]\n },\n {\n \"name\": \"panels wait\",\n \"summary\": \"Wait for a terminal panel to initialize, become ready/idle, or contain text.\",\n \"usage\": [\n \"runpane panels wait --panel <panel-id> [--for initialized|ready|idle|text] [--contains <text>] [--timeout-ms <ms>] [--interval-ms <ms>] [--json]\"\n ],\n \"jsonSchemas\": [\n \"panelWaitResult\"\n ]\n }\n ],\n \"flags\": {\n \"wrapper\": [\n {\n \"name\": \"--version\",\n \"value\": \"<latest|vX.Y.Z>\",\n \"description\": \"Pane release to install or inspect.\"\n },\n {\n \"name\": \"--download-dir\",\n \"value\": \"<path>\",\n \"description\": \"Directory for downloaded Pane release artifacts.\"\n },\n {\n \"name\": \"--pane-path\",\n \"value\": \"<path>\",\n \"description\": \"Use or inspect an existing Pane executable.\"\n },\n {\n \"name\": \"--format\",\n \"value\": \"<auto|appimage|deb|dmg|zip|exe>\",\n \"description\": \"Pane release artifact format.\"\n },\n {\n \"name\": \"--dry-run\",\n \"description\": \"Print the plan without downloading or installing.\"\n },\n {\n \"name\": \"--yes\",\n \"aliases\": [\n \"-y\"\n ],\n \"description\": \"Skip interactive prompts where possible.\"\n },\n {\n \"name\": \"--verbose\",\n \"description\": \"Print extra diagnostics.\"\n }\n ],\n \"remoteValue\": [\n {\n \"name\": \"--label\",\n \"value\": \"<name>\"\n },\n {\n \"name\": \"--prefer-tunnel\",\n \"value\": \"<tailscale|ssh|manual|auto>\"\n },\n {\n \"name\": \"--channel\",\n \"value\": \"<stable|nightly>\"\n },\n {\n \"name\": \"--base-url\",\n \"value\": \"<url>\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\"\n },\n {\n \"name\": \"--listen-port\",\n \"value\": \"<port>\"\n },\n {\n \"name\": \"--port\",\n \"value\": \"<port>\"\n },\n {\n \"name\": \"--repo-ref\",\n \"value\": \"<ref>\"\n }\n ],\n \"remoteBoolean\": [\n {\n \"name\": \"--auto-listen-port\"\n },\n {\n \"name\": \"--interactive-tailscale-setup\"\n },\n {\n \"name\": \"--no-install-service\"\n },\n {\n \"name\": \"--no-tailscale-serve\"\n },\n {\n \"name\": \"--print-only\"\n }\n ],\n \"localValue\": [\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"description\": \"Connect to a Pane daemon using this Pane data directory.\"\n },\n {\n \"name\": \"--repo\",\n \"value\": \"<selector>\",\n \"description\": \"Repository selector: active, id, exact path, or saved repository name.\"\n },\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"description\": \"Pane/session id to inspect.\"\n },\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"description\": \"Tool panel id to inspect or control.\"\n },\n {\n \"name\": \"--path\",\n \"value\": \"<path>\",\n \"description\": \"Existing git repository path to register with Pane.\"\n },\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"description\": \"Name for the registered repository or created pane/session.\"\n },\n {\n \"name\": \"--worktree-name\",\n \"value\": \"<name>\",\n \"description\": \"Worktree name to request. Defaults to --name.\"\n },\n {\n \"name\": \"--base-branch\",\n \"value\": \"<branch>\",\n \"description\": \"Base branch for the created worktree.\"\n },\n {\n \"name\": \"--agent\",\n \"value\": \"<codex|claude>\",\n \"description\": \"Built-in agent terminal template to open.\"\n },\n {\n \"name\": \"--tool-command\",\n \"value\": \"<command>\",\n \"description\": \"Custom terminal command to run instead of a built-in agent.\"\n },\n {\n \"name\": \"--title\",\n \"value\": \"<title>\",\n \"description\": \"Terminal tab title. Defaults to the selected agent title or Terminal.\"\n },\n {\n \"name\": \"--initial-input\",\n \"value\": \"<text>\",\n \"aliases\": [\n \"--prompt\"\n ],\n \"description\": \"Text to send to the terminal after the command is ready. --prompt is an alias.\"\n },\n {\n \"name\": \"--initial-input-file\",\n \"value\": \"<path|->\",\n \"description\": \"Read initial input from a file or stdin.\"\n },\n {\n \"name\": \"--from-json\",\n \"value\": \"<path|->\",\n \"description\": \"Read a full panes.create request JSON payload from a file or stdin.\"\n },\n {\n \"name\": \"--timeout-ms\",\n \"value\": \"<milliseconds>\",\n \"description\": \"Maximum time to wait for each pane creation job.\"\n },\n {\n \"name\": \"--ready-timeout-ms\",\n \"value\": \"<milliseconds>\",\n \"description\": \"Readiness wait timeout for panes create --wait-ready.\"\n },\n {\n \"name\": \"--concurrency\",\n \"value\": \"<count>\",\n \"description\": \"Accepted for forward compatibility. Pane currently serializes multi-pane session creation so queued jobs do not time out before starting.\"\n },\n {\n \"name\": \"--limit\",\n \"value\": \"<count>\",\n \"description\": \"Maximum recent output lines or records to read.\"\n },\n {\n \"name\": \"--for\",\n \"value\": \"<initialized|ready|idle|text>\",\n \"description\": \"Panel wait condition.\"\n },\n {\n \"name\": \"--contains\",\n \"value\": \"<text>\",\n \"description\": \"Text to wait for with panels wait --for text.\"\n },\n {\n \"name\": \"--interval-ms\",\n \"value\": \"<milliseconds>\",\n \"description\": \"Polling interval for panels wait.\"\n },\n {\n \"name\": \"--text\",\n \"value\": \"<text>\",\n \"description\": \"Text bytes to send to a terminal panel.\"\n },\n {\n \"name\": \"--input-file\",\n \"value\": \"<path|->\",\n \"description\": \"Read panel input from a file or stdin.\"\n },\n {\n \"name\": \"--source\",\n \"value\": \"<user|agent>\",\n \"description\": \"Identify whether a local-control mutation is user-initiated or agent/orchestrator-initiated.\"\n },\n {\n \"name\": \"--strategy\",\n \"value\": \"<auto|codex-ctrl-enter|enter>\",\n \"description\": \"Composer submit key sequence strategy for panels submit-composer.\"\n }\n ],\n \"localBoolean\": [\n {\n \"name\": \"--json\",\n \"description\": \"Print machine-readable JSON output.\"\n },\n {\n \"name\": \"--wait-ready\",\n \"description\": \"Wait for created terminal panels to be ready before returning.\"\n },\n {\n \"name\": \"--no-focus\",\n \"description\": \"Create the pane or panel in the background without changing focus.\"\n },\n {\n \"name\": \"--focus\",\n \"description\": \"Explicitly focus the created pane or panel.\"\n },\n {\n \"name\": \"--pinned\",\n \"description\": \"Create the pane already pinned (the UI's favorite/pin star).\"\n },\n {\n \"name\": \"--force\",\n \"description\": \"Archive even if the pane's branch has uncommitted, untracked, or unpushed changes.\"\n }\n ]\n },\n \"help\": {\n \"npm\": {\n \"default\": [\n \"Usage:\",\n \" runpane\",\n \" runpane setup\",\n \" runpane install [client|daemon] [options]\",\n \" runpane update [options]\",\n \" runpane version\",\n \" runpane doctor\",\n \" runpane agent-context [--json]\",\n \" runpane agents doctor --agent <codex|claude> [--repo <selector>] [--json]\",\n \" runpane repos list [--json]\",\n \" runpane repos add --path <path> [--name <name>]\",\n \" runpane panes list [--repo <selector>] [--json]\",\n \" runpane panes create --repo <selector> --name <name> --agent <codex|claude> [--source user|agent] [--focus|--no-focus] [--wait-ready]\",\n \" runpane panes archive --pane <pane-id> [--source user|agent] [--force] --yes\",\n \" runpane panels create --pane <pane-id> --agent <codex|claude> [--source user|agent] [--focus|--no-focus] --yes\",\n \" runpane panels list --pane <pane-id> [--json]\",\n \" runpane panels output --panel <panel-id> [--limit <count>] [--json]\",\n \" runpane panels screen --panel <panel-id> [--limit <count>] [--json]\",\n \" runpane panels input --panel <panel-id> (--text <text>|--input-file <path|->) --yes [--json]\",\n \" runpane panels submit --panel <panel-id> (--text <text>|--input-file <path|->) --yes [--json]\",\n \" runpane panels submit-composer --panel <panel-id> [--strategy auto|codex-ctrl-enter|enter] --yes [--json]\",\n \" runpane panels wait --panel <panel-id> [--for initialized|ready|idle|text] [--json]\",\n \" runpane help [command]\",\n \"\",\n \"Quick start:\",\n \" npx --yes runpane@latest\",\n \" npm i -g runpane && runpane setup\",\n \"\",\n \"Advanced examples:\",\n \" npx --yes runpane@latest install client\",\n \" npx --yes runpane@latest install daemon --label \\\"My Server\\\"\",\n \" pnpm dlx runpane@latest\",\n \"\",\n \"Common commands:\",\n \" runpane help\",\n \" runpane setup\",\n \" runpane install\",\n \" runpane doctor\",\n \"\",\n \"Agent discovery:\",\n \" runpane doctor --json\",\n \" runpane agent-context --json\",\n \" runpane agent-context --command \\\"<command>\\\" --json\",\n \"\",\n \"Run \\\"runpane help panes create\\\" for pane orchestration options.\"\n ],\n \"install\": [\n \"Usage:\",\n \" runpane install [client|daemon] [options]\",\n \"\",\n \"Examples:\",\n \" npx --yes runpane@latest install client\",\n \" npx --yes runpane@latest install daemon --label \\\"My Server\\\"\",\n \" pnpm dlx runpane@latest install daemon --prefer-tunnel ssh --label \\\"VM\\\"\",\n \"\",\n \"Wrapper options:\",\n \" --version <latest|vX.Y.Z> Pane release to install\",\n \" --format <auto|appimage|deb|dmg|zip|exe>\",\n \" --download-dir <path>\",\n \" --pane-path <path> Use an existing Pane executable\",\n \" --dry-run Print the plan without downloading\",\n \" --yes Skip interactive prompts where possible\",\n \" --verbose\",\n \"\",\n \"Daemon passthrough options:\",\n \" --label <name>\",\n \" --prefer-tunnel <tailscale|ssh|manual|auto>\",\n \" --channel <stable|nightly>\",\n \" --base-url <url>\",\n \" --pane-dir <path>\",\n \" --listen-port <port> / --port <port>\",\n \" --auto-listen-port\",\n \" --interactive-tailscale-setup\",\n \" --no-install-service\",\n \" --no-tailscale-serve\",\n \" --print-only\",\n \" --repo-ref <ref>\"\n ],\n \"setup\": [\n \"Usage:\",\n \" runpane setup\",\n \"\",\n \"Opens the guided setup for desktop install, remote host setup, update, and diagnostics.\",\n \"\",\n \"Quick start:\",\n \" npx --yes runpane@latest\",\n \" npm i -g runpane && runpane setup\"\n ],\n \"update\": [\n \"Usage:\",\n \" runpane update [options]\",\n \"\",\n \"Updates Pane using the same artifact selection as \\\"runpane install client\\\".\",\n \"\",\n \"Options:\",\n \" --version <latest|vX.Y.Z>\",\n \" --format <auto|appimage|deb|dmg|zip|exe>\",\n \" --download-dir <path>\",\n \" --pane-path <path>\",\n \" --dry-run\",\n \" --yes\",\n \" --verbose\"\n ],\n \"version\": [\n \"Usage:\",\n \" runpane version\",\n \" runpane --version\"\n ],\n \"doctor\": [\n \"Usage:\",\n \" runpane doctor [--json] [--pane-dir <path>] [--pane-path <path>] [--format <format>] [--verbose]\",\n \"\",\n \"Checks wrapper/runtime details, release metadata, installed Pane detection, and Pane daemon reachability.\",\n \"\",\n \"Options:\",\n \" --json Print a machine-readable environment report\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --pane-path <path> Inspect a specific Pane executable\",\n \" --format <format> Release artifact format to inspect\",\n \" --verbose Print extra diagnostics\",\n \"\",\n \"Agent discovery:\",\n \" runpane doctor --json\",\n \" runpane agent-context --json\"\n ],\n \"repos list\": [\n \"Usage:\",\n \" runpane repos list [--json] [--pane-dir <path>]\",\n \"\",\n \"Lists repositories saved in the running Pane app.\",\n \"\",\n \"Options:\",\n \" --json Print machine-readable output\",\n \" --pane-dir <path> Connect to a specific Pane data directory\"\n ],\n \"repos add\": [\n \"Usage:\",\n \" runpane repos add --path <path> [--name <name>] [--json] [--yes]\",\n \"\",\n \"Registers an existing git repository with the running Pane app.\",\n \"\",\n \"Options:\",\n \" --path <path> Existing git repository path\",\n \" --name <name> Saved repository name; defaults to the directory name\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --dry-run Validate and preview without adding the repo\",\n \" --yes Skip confirmation for mutating commands\"\n ],\n \"panes\": [\n \"Pane session commands.\",\n \"\",\n \"Usage:\",\n \" runpane panes <command> [options]\",\n \"\",\n \"Commands:\",\n \" runpane panes list [--repo <selector>] [--json]\",\n \" runpane panes create --repo <selector> --name <name> --agent <codex|claude> [options]\",\n \" runpane panes archive --pane <pane-id> [--force] [--source user|agent] --yes [--json]\",\n \" runpane panes pin --pane <pane-id> --yes [--dry-run] [--json]\",\n \" runpane panes unpin --pane <pane-id> --yes [--dry-run] [--json]\",\n \"\",\n \"Run \\\"runpane help panes <command>\\\" for command-specific options.\"\n ],\n \"panes list\": [\n \"Usage:\",\n \" runpane panes list [--repo <selector>] [--json]\",\n \"\",\n \"Lists Pane sessions. Pass --repo to limit results to one saved repository.\",\n \"\",\n \"Options:\",\n \" --repo <selector> active, id, exact path, or saved repository name\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panes create\": [\n \"Usage:\",\n \" runpane panes create --repo <selector> --name <name> --agent <codex|claude> [options]\",\n \" runpane panes create --from-json <path|-> [--yes] [--json]\",\n \"\",\n \"Creates user-visible Panes (Pane sessions) for feature/PR work in a saved repository and opens a terminal-backed tool tab. Pane creates and owns the worktree/branch for each new Pane.\",\n \"\",\n \"Options:\",\n \" --repo <selector> active, id, exact path, or saved repository name\",\n \" --name <name> Pane/session name\",\n \" --worktree-name <name> Worktree name; defaults to --name\",\n \" --base-branch <branch> Base branch for the worktree\",\n \" --agent <codex|claude> Built-in terminal template\",\n \" --tool-command <command> Custom terminal command\",\n \" --title <title> Terminal tab title\",\n \" --initial-input <text> Text sent after the command is ready\",\n \" --prompt <text> Alias for --initial-input\",\n \" --initial-input-file <path|-> Read initial input from a file or stdin\",\n \" --from-json <path|-> Read a full request payload\",\n \" --timeout-ms <milliseconds> Pane creation timeout\",\n \" --wait-ready Wait for terminal readiness before returning\",\n \" --ready-timeout-ms <ms> Readiness wait timeout; defaults to 30000\",\n \" --concurrency <count> Accepted; creation is currently serialized\",\n \" --source <user|agent> Mark mutation source; agent implies background creation\",\n \" --no-focus Create in the background without stealing focus\",\n \" --focus Explicitly focus the created pane\",\n \" --pinned Create already pinned (the UI's favorite/pin star)\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --dry-run Validate and preview without creating panes\",\n \" --yes Skip confirmation for mutating commands\"\n ],\n \"panes archive\": [\n \"Usage:\",\n \" runpane panes archive --pane <pane-id> [--source user|agent] [--force] --yes [--json]\",\n \"\",\n \"Archives a Pane (session) exactly like the UI Archive action, including removal of its Pane-managed git worktree. Refuses to archive a pane with uncommitted, untracked, or unpushed-to-remote changes unless --force is passed. Waits for worktree removal to finish before returning.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane/session id to archive\",\n \" --source <user|agent> Mark mutation source\",\n \" --force Archive even if the pane has uncommitted, untracked, or unpushed changes\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --yes Skip confirmation for mutating commands\"\n ],\n \"panes pin\": [\n \"Usage:\",\n \" runpane panes pin --pane <pane-id> --yes [--dry-run] [--json]\",\n \"\",\n \"Declaratively pins a Pane using the Pane UI's favorite/pin star. Repeating the command is idempotent and never changes focus. Use --dry-run to validate and preview without mutating.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane/session id to pin\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --dry-run Validate and preview without pinning the pane\",\n \" --yes Skip confirmation for mutating commands\"\n ],\n \"panes unpin\": [\n \"Usage:\",\n \" runpane panes unpin --pane <pane-id> --yes [--dry-run] [--json]\",\n \"\",\n \"Declaratively unpins a Pane using the Pane UI's favorite/pin star. Repeating the command is idempotent and never changes focus. Use --dry-run to validate and preview without mutating.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane/session id to unpin\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --dry-run Validate and preview without unpinning the pane\",\n \" --yes Skip confirmation for mutating commands\"\n ],\n \"panels\": [\n \"Terminal-backed panel commands.\",\n \"\",\n \"Usage:\",\n \" runpane panels <command> [options]\",\n \"\",\n \"Commands:\",\n \" runpane panels create --pane <pane-id> --agent <codex|claude> [options]\",\n \" runpane panels list --pane <pane-id> [--json]\",\n \" runpane panels screen --panel <panel-id> [--limit <count>] [--json]\",\n \" runpane panels output --panel <panel-id> [--limit <count>] [--json]\",\n \" runpane panels input --panel <panel-id> (--text <text>|--input-file <path|->) --yes [--json]\",\n \" runpane panels submit --panel <panel-id> (--text <text>|--input-file <path|->) --yes [--json]\",\n \" runpane panels submit-composer --panel <panel-id> [--strategy auto|codex-ctrl-enter|enter] --yes [--json]\",\n \" runpane panels wait --panel <panel-id> [--for initialized|ready|idle|text] [--json]\",\n \"\",\n \"Run \\\"runpane help panels create\\\" or another command-specific topic for options.\"\n ],\n \"panels list\": [\n \"Usage:\",\n \" runpane panels list --pane <pane-id> [--json]\",\n \"\",\n \"Lists tool panels in a Pane session.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Pane/session id\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panels output\": [\n \"Usage:\",\n \" runpane panels output --panel <panel-id> [--limit <count>] [--json]\",\n \"\",\n \"Reads bounded recent terminal output from a panel.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Tool panel id\",\n \" --limit <count> Maximum recent output lines or records to read\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panels input\": [\n \"Usage:\",\n \" runpane panels input --panel <panel-id> (--text <text>|--input-file <path|->) --yes [--json]\",\n \"\",\n \"Sends exact input bytes to a terminal panel. Include a newline in the input when you mean Enter.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id\",\n \" --text <text> Text bytes to send\",\n \" --input-file <path|-> Read input from a file or stdin\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --yes Skip confirmation for mutating commands\"\n ],\n \"agent-context\": [\n \"Usage:\",\n \" runpane agent-context [--json]\",\n \" runpane agent-context --command <command> [--json]\",\n \"\",\n \"Prints Pane command context for coding agents without requiring a running Pane app.\",\n \"\",\n \"Options:\",\n \" --command <command> Print the full definition for one runpane command\",\n \" --json Print machine-readable output\",\n \"\",\n \"Examples:\",\n \" runpane agent-context\",\n \" runpane agent-context --json\",\n \" runpane agent-context --command \\\"panes create\\\"\",\n \" runpane agent-context --command \\\"panes create\\\" --json\"\n ],\n \"agents doctor\": [\n \"Usage:\",\n \" runpane agents doctor --agent <codex|claude> [--repo <selector>] [--json]\",\n \"\",\n \"Diagnoses whether Codex or Claude is available in the same repository environment Pane will use.\",\n \"\",\n \"Options:\",\n \" --agent <codex|claude> Built-in agent command to diagnose\",\n \" --repo <selector> active, id, exact path, or saved repository name; defaults to active\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panels screen\": [\n \"Usage:\",\n \" runpane panels screen --panel <panel-id> [--limit <count>] [--json]\",\n \"\",\n \"Reads a compact current-screen view from a terminal panel. Prefers alternate-screen/TUI output and falls back to recent scrollback.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id\",\n \" --limit <count> Maximum lines to return; defaults to 80\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panels submit\": [\n \"Usage:\",\n \" runpane panels submit --panel <panel-id> (--text <text>|--input-file <path|->) --yes [--json]\",\n \"\",\n \"Sends text to a terminal panel and normalizes the final terminal Enter to CR. Use this for ordinary prompt answers and shell commands.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id\",\n \" --text <text> Text to submit before Enter\",\n \" --input-file <path|-> Read text from a file or stdin before Enter\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --yes Skip confirmation for this mutating command\"\n ],\n \"panels wait\": [\n \"Usage:\",\n \" runpane panels wait --panel <panel-id> [--for initialized|ready|idle|text] [--contains <text>] [--timeout-ms <ms>] [--interval-ms <ms>] [--json]\",\n \"\",\n \"Polls a terminal panel until it is initialized, ready, idle, or contains text. Output is intentionally small and includes next-step guidance.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id\",\n \" --for <condition> initialized, ready, idle, or text; defaults to ready for CLI panels and idle otherwise\",\n \" --contains <text> Text required for --for text; implies --for text when omitted\",\n \" --timeout-ms <milliseconds> Wait timeout; defaults to 30000\",\n \" --interval-ms <milliseconds> Poll interval; defaults to 500\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panels create\": [\n \"Create a terminal-backed tool panel inside an existing Pane session.\",\n \"\",\n \"Usage:\",\n \" runpane panels create --pane <pane-id> --agent <codex|claude> [--title <title>] [--initial-input <text>] [--no-focus] [--wait-ready] --yes [--json]\",\n \" runpane panels create --pane <pane-id> --tool-command <command> [--title <title>] [--no-focus] --yes [--json]\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Existing Pane session to add the panel to.\",\n \" --agent <codex|claude> Built-in agent command template to launch.\",\n \" --tool-command <command> Custom terminal command to launch.\",\n \" --title <title> Panel title override.\",\n \" --initial-input, --prompt Initial input to send after the tool starts.\",\n \" --initial-input-file <path|-> Read initial input from a file or stdin.\",\n \" --no-focus Create the panel in the background.\",\n \" --focus Explicitly focus the created panel.\",\n \" --source <user|agent> Mark the mutation source; agent implies background creation.\",\n \" --wait-ready Wait until the terminal tool is ready.\",\n \" --ready-timeout-ms <ms> Readiness wait timeout.\",\n \" --yes Skip confirmation prompts.\",\n \" --json Print JSON output.\"\n ],\n \"panels submit-composer\": [\n \"Submit an agent composer using the panel-appropriate key sequence.\",\n \"\",\n \"Usage:\",\n \" runpane panels submit-composer --panel <panel-id> [--strategy auto|codex-ctrl-enter|enter] --yes [--json]\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id.\",\n \" --strategy <strategy> Defaults to auto; Codex sends Ctrl+Enter and other panels send Enter.\",\n \" --yes Skip confirmation prompts.\",\n \" --json Print JSON output.\"\n ]\n },\n \"pip\": {\n \"default\": [\n \"Usage:\",\n \" runpane\",\n \" runpane setup\",\n \" runpane install [client|daemon] [options]\",\n \" runpane update [options]\",\n \" runpane version\",\n \" runpane doctor\",\n \" runpane agent-context [--json]\",\n \" runpane agents doctor --agent <codex|claude> [--repo <selector>] [--json]\",\n \" runpane repos list [--json]\",\n \" runpane repos add --path <path> [--name <name>]\",\n \" runpane panes list [--repo <selector>] [--json]\",\n \" runpane panes create --repo <selector> --name <name> --agent <codex|claude> [--source user|agent] [--focus|--no-focus] [--wait-ready]\",\n \" runpane panes archive --pane <pane-id> [--source user|agent] [--force] --yes\",\n \" python -m runpane panels create --pane <pane-id> --agent <codex|claude> [--source user|agent] [--focus|--no-focus] --yes\",\n \" runpane panels list --pane <pane-id> [--json]\",\n \" runpane panels output --panel <panel-id> [--limit <count>] [--json]\",\n \" runpane panels screen --panel <panel-id> [--limit <count>] [--json]\",\n \" runpane panels input --panel <panel-id> (--text <text>|--input-file <path|->) --yes [--json]\",\n \" runpane panels submit --panel <panel-id> (--text <text>|--input-file <path|->) --yes [--json]\",\n \" runpane panels submit-composer --panel <panel-id> [--strategy auto|codex-ctrl-enter|enter] --yes [--json]\",\n \" runpane panels wait --panel <panel-id> [--for initialized|ready|idle|text] [--json]\",\n \" runpane help [command]\",\n \"\",\n \"Quick start:\",\n \" pipx run runpane\",\n \" python -m pip install runpane && python -m runpane setup\",\n \"\",\n \"Advanced examples:\",\n \" pipx run runpane install client\",\n \" pipx run runpane install daemon --label \\\"My Server\\\"\",\n \" uvx runpane@latest\",\n \"\",\n \"Common commands:\",\n \" runpane help\",\n \" runpane setup\",\n \" runpane install\",\n \" runpane doctor\",\n \"\",\n \"Agent discovery:\",\n \" runpane doctor --json\",\n \" runpane agent-context --json\",\n \" runpane agent-context --command \\\"<command>\\\" --json\",\n \"\",\n \"Run \\\"runpane help panes create\\\" for pane orchestration options.\"\n ],\n \"install\": [\n \"Usage:\",\n \" runpane install [client|daemon] [options]\",\n \"\",\n \"Examples:\",\n \" npx --yes runpane@latest install daemon --label \\\"My Server\\\"\",\n \" pnpm dlx runpane@latest install daemon --prefer-tunnel ssh --label \\\"VM\\\"\",\n \" pipx run runpane install daemon --label \\\"My Server\\\"\",\n \"\",\n \"Wrapper options:\",\n \" --version <latest|vX.Y.Z>\",\n \" --format <auto|appimage|deb|dmg|zip|exe>\",\n \" --download-dir <path>\",\n \" --pane-path <path>\",\n \" --dry-run\",\n \" --yes\",\n \" --verbose\",\n \"\",\n \"Daemon passthrough options:\",\n \" --label <name>\",\n \" --prefer-tunnel <tailscale|ssh|manual|auto>\",\n \" --channel <stable|nightly>\",\n \" --base-url <url>\",\n \" --pane-dir <path>\",\n \" --listen-port <port> / --port <port>\",\n \" --auto-listen-port\",\n \" --interactive-tailscale-setup\",\n \" --no-install-service\",\n \" --no-tailscale-serve\",\n \" --print-only\",\n \" --repo-ref <ref>\"\n ],\n \"setup\": [\n \"Usage:\",\n \" runpane setup\",\n \"\",\n \"Opens the guided setup for desktop install, remote host setup, update, and diagnostics.\",\n \"\",\n \"Quick start:\",\n \" pipx run runpane\",\n \" python -m pip install runpane && python -m runpane setup\"\n ],\n \"update\": [\n \"Usage:\",\n \" runpane update [--version <latest|vX.Y.Z>] [--dry-run] [--yes]\"\n ],\n \"version\": [\n \"Usage:\",\n \" runpane version\",\n \" runpane --version\"\n ],\n \"doctor\": [\n \"Usage:\",\n \" runpane doctor [--json] [--pane-dir <path>] [--pane-path <path>] [--format <format>] [--verbose]\",\n \"\",\n \"Checks wrapper/runtime details, release metadata, installed Pane detection, and Pane daemon reachability.\",\n \"\",\n \"Options:\",\n \" --json\",\n \" --pane-dir <path>\",\n \" --pane-path <path>\",\n \" --format <format>\",\n \" --verbose\",\n \"\",\n \"Agent discovery:\",\n \" runpane doctor --json\",\n \" runpane agent-context --json\"\n ],\n \"repos list\": [\n \"Usage:\",\n \" runpane repos list [--json] [--pane-dir <path>]\",\n \"\",\n \"Lists repositories saved in the running Pane app.\",\n \"\",\n \"Options:\",\n \" --json\",\n \" --pane-dir <path>\"\n ],\n \"repos add\": [\n \"Usage:\",\n \" runpane repos add --path <path> [--name <name>] [--json] [--yes]\",\n \"\",\n \"Registers an existing git repository with the running Pane app.\",\n \"\",\n \"Options:\",\n \" --path <path>\",\n \" --name <name>\",\n \" --pane-dir <path>\",\n \" --json\",\n \" --dry-run\",\n \" --yes\"\n ],\n \"panes\": [\n \"Pane session commands.\",\n \"\",\n \"Usage:\",\n \" runpane panes <command> [options]\",\n \"\",\n \"Commands:\",\n \" runpane panes list [--repo <selector>] [--json]\",\n \" runpane panes create --repo <selector> --name <name> --agent <codex|claude> [options]\",\n \" runpane panes archive --pane <pane-id> [--force] [--source user|agent] --yes [--json]\",\n \" runpane panes pin --pane <pane-id> --yes [--dry-run] [--json]\",\n \" runpane panes unpin --pane <pane-id> --yes [--dry-run] [--json]\",\n \"\",\n \"Run \\\"runpane help panes <command>\\\" for command-specific options.\"\n ],\n \"panes list\": [\n \"Usage:\",\n \" runpane panes list [--repo <selector>] [--json]\",\n \"\",\n \"Lists Pane sessions. Pass --repo to limit results to one saved repository.\",\n \"\",\n \"Options:\",\n \" --repo <selector>\",\n \" --pane-dir <path>\",\n \" --json\"\n ],\n \"panes create\": [\n \"Usage:\",\n \" runpane panes create --repo <selector> --name <name> --agent <codex|claude> [options]\",\n \" runpane panes create --from-json <path|-> [--yes] [--json]\",\n \"\",\n \"Creates user-visible Panes (Pane sessions) for feature/PR work in a saved repository and opens a terminal-backed tool tab. Pane creates and owns the worktree/branch for each new Pane.\",\n \"\",\n \"Options:\",\n \" --repo <selector> active, id, exact path, or saved repository name\",\n \" --name <name> Pane/session name\",\n \" --worktree-name <name> Worktree name; defaults to --name\",\n \" --base-branch <branch> Base branch for the worktree\",\n \" --agent <codex|claude> Built-in terminal template\",\n \" --tool-command <command> Custom terminal command\",\n \" --title <title> Terminal tab title\",\n \" --initial-input <text> Text sent after the command is ready\",\n \" --prompt <text> Alias for --initial-input\",\n \" --initial-input-file <path|-> Read initial input from a file or stdin\",\n \" --from-json <path|-> Read a full request payload\",\n \" --timeout-ms <milliseconds> Pane creation timeout\",\n \" --wait-ready Wait for terminal readiness before returning\",\n \" --ready-timeout-ms <ms> Readiness wait timeout; defaults to 30000\",\n \" --concurrency <count> Accepted; creation is currently serialized\",\n \" --source <user|agent> Mark mutation source; agent implies background creation\",\n \" --no-focus Create in the background without stealing focus\",\n \" --focus Explicitly focus the created pane\",\n \" --pinned Create already pinned (the UI's favorite/pin star)\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --dry-run Validate and preview without creating panes\",\n \" --yes Skip confirmation for mutating commands\"\n ],\n \"panes archive\": [\n \"Usage:\",\n \" runpane panes archive --pane <pane-id> [--source user|agent] [--force] --yes [--json]\",\n \"\",\n \"Archives a Pane (session) exactly like the UI Archive action, including removal of its Pane-managed git worktree. Refuses to archive a pane with uncommitted, untracked, or unpushed-to-remote changes unless --force is passed. Waits for worktree removal to finish before returning.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id>\",\n \" --source <user|agent>\",\n \" --force\",\n \" --pane-dir <path>\",\n \" --json\",\n \" --yes\"\n ],\n \"panes pin\": [\n \"Usage:\",\n \" runpane panes pin --pane <pane-id> --yes [--dry-run] [--json]\",\n \"\",\n \"Declaratively pins a Pane using the Pane UI's favorite/pin star. Repeating the command is idempotent and never changes focus. Use --dry-run to validate and preview without mutating.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id>\",\n \" --pane-dir <path>\",\n \" --json\",\n \" --dry-run\",\n \" --yes\"\n ],\n \"panes unpin\": [\n \"Usage:\",\n \" runpane panes unpin --pane <pane-id> --yes [--dry-run] [--json]\",\n \"\",\n \"Declaratively unpins a Pane using the Pane UI's favorite/pin star. Repeating the command is idempotent and never changes focus. Use --dry-run to validate and preview without mutating.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id>\",\n \" --pane-dir <path>\",\n \" --json\",\n \" --dry-run\",\n \" --yes\"\n ],\n \"panels\": [\n \"Terminal-backed panel commands.\",\n \"\",\n \"Usage:\",\n \" runpane panels <command> [options]\",\n \"\",\n \"Commands:\",\n \" runpane panels create --pane <pane-id> --agent <codex|claude> [options]\",\n \" runpane panels list --pane <pane-id> [--json]\",\n \" runpane panels screen --panel <panel-id> [--limit <count>] [--json]\",\n \" runpane panels output --panel <panel-id> [--limit <count>] [--json]\",\n \" runpane panels input --panel <panel-id> (--text <text>|--input-file <path|->) --yes [--json]\",\n \" runpane panels submit --panel <panel-id> (--text <text>|--input-file <path|->) --yes [--json]\",\n \" runpane panels submit-composer --panel <panel-id> [--strategy auto|codex-ctrl-enter|enter] --yes [--json]\",\n \" runpane panels wait --panel <panel-id> [--for initialized|ready|idle|text] [--json]\",\n \"\",\n \"Run \\\"runpane help panels create\\\" or another command-specific topic for options.\"\n ],\n \"panels list\": [\n \"Usage:\",\n \" runpane panels list --pane <pane-id> [--json]\",\n \"\",\n \"Lists tool panels in a Pane session.\",\n \"\",\n \"Options:\",\n \" --pane <pane-id>\",\n \" --pane-dir <path>\",\n \" --json\"\n ],\n \"panels output\": [\n \"Usage:\",\n \" runpane panels output --panel <panel-id> [--limit <count>] [--json]\",\n \"\",\n \"Reads recent terminal output records from a panel.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id>\",\n \" --limit <count>\",\n \" --pane-dir <path>\",\n \" --json\"\n ],\n \"panels input\": [\n \"Usage:\",\n \" runpane panels input --panel <panel-id> (--text <text>|--input-file <path|->) --yes [--json]\",\n \"\",\n \"Sends exact input bytes to a terminal panel. Include a newline in the input when you mean Enter.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id>\",\n \" --text <text>\",\n \" --input-file <path|->\",\n \" --pane-dir <path>\",\n \" --json\",\n \" --yes\"\n ],\n \"agent-context\": [\n \"Usage:\",\n \" runpane agent-context [--json]\",\n \" runpane agent-context --command <command> [--json]\",\n \"\",\n \"Prints Pane command context for coding agents without requiring a running Pane app.\",\n \"\",\n \"Options:\",\n \" --command <command>\",\n \" --json\",\n \"\",\n \"Examples:\",\n \" runpane agent-context\",\n \" runpane agent-context --json\",\n \" runpane agent-context --command \\\"panes create\\\"\",\n \" runpane agent-context --command \\\"panes create\\\" --json\"\n ],\n \"agents doctor\": [\n \"Usage:\",\n \" runpane agents doctor --agent <codex|claude> [--repo <selector>] [--json]\",\n \"\",\n \"Diagnoses whether Codex or Claude is available in the same repository environment Pane will use.\",\n \"\",\n \"Options:\",\n \" --agent <codex|claude> Built-in agent command to diagnose\",\n \" --repo <selector> active, id, exact path, or saved repository name; defaults to active\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panels screen\": [\n \"Usage:\",\n \" runpane panels screen --panel <panel-id> [--limit <count>] [--json]\",\n \"\",\n \"Reads a compact current-screen view from a terminal panel. Prefers alternate-screen/TUI output and falls back to recent scrollback.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id\",\n \" --limit <count> Maximum lines to return; defaults to 80\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panels submit\": [\n \"Usage:\",\n \" runpane panels submit --panel <panel-id> (--text <text>|--input-file <path|->) --yes [--json]\",\n \"\",\n \"Sends text to a terminal panel and normalizes the final terminal Enter to CR. Use this for ordinary prompt answers and shell commands.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id\",\n \" --text <text> Text to submit before Enter\",\n \" --input-file <path|-> Read text from a file or stdin before Enter\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\",\n \" --yes Skip confirmation for this mutating command\"\n ],\n \"panels wait\": [\n \"Usage:\",\n \" runpane panels wait --panel <panel-id> [--for initialized|ready|idle|text] [--contains <text>] [--timeout-ms <ms>] [--interval-ms <ms>] [--json]\",\n \"\",\n \"Polls a terminal panel until it is initialized, ready, idle, or contains text. Output is intentionally small and includes next-step guidance.\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id\",\n \" --for <condition> initialized, ready, idle, or text; defaults to ready for CLI panels and idle otherwise\",\n \" --contains <text> Text required for --for text; implies --for text when omitted\",\n \" --timeout-ms <milliseconds> Wait timeout; defaults to 30000\",\n \" --interval-ms <milliseconds> Poll interval; defaults to 500\",\n \" --pane-dir <path> Connect to a specific Pane data directory\",\n \" --json Print machine-readable output\"\n ],\n \"panels create\": [\n \"Create a terminal-backed tool panel inside an existing Pane session.\",\n \"\",\n \"Usage:\",\n \" python -m runpane panels create --pane <pane-id> --agent <codex|claude> [--title <title>] [--initial-input <text>] [--no-focus] [--wait-ready] --yes [--json]\",\n \" python -m runpane panels create --pane <pane-id> --tool-command <command> [--title <title>] [--no-focus] --yes [--json]\",\n \"\",\n \"Options:\",\n \" --pane <pane-id> Existing Pane session to add the panel to.\",\n \" --agent <codex|claude> Built-in agent command template to launch.\",\n \" --tool-command <command> Custom terminal command to launch.\",\n \" --title <title> Panel title override.\",\n \" --initial-input, --prompt Initial input to send after the tool starts.\",\n \" --initial-input-file <path|-> Read initial input from a file or stdin.\",\n \" --no-focus Create the panel in the background.\",\n \" --focus Explicitly focus the created panel.\",\n \" --source <user|agent> Mark the mutation source; agent implies background creation.\",\n \" --wait-ready Wait until the terminal tool is ready.\",\n \" --ready-timeout-ms <ms> Readiness wait timeout.\",\n \" --yes Skip confirmation prompts.\",\n \" --json Print JSON output.\"\n ],\n \"panels submit-composer\": [\n \"Submit an agent composer using the panel-appropriate key sequence.\",\n \"\",\n \"Usage:\",\n \" python -m runpane panels submit-composer --panel <panel-id> [--strategy auto|codex-ctrl-enter|enter] --yes [--json]\",\n \"\",\n \"Options:\",\n \" --panel <panel-id> Terminal panel id.\",\n \" --strategy <strategy> Defaults to auto; Codex sends Ctrl+Enter and other panels send Enter.\",\n \" --yes Skip confirmation prompts.\",\n \" --json Print JSON output.\"\n ]\n }\n },\n \"docs\": {\n \"maintainerRules\": [\n \"Treat `contracts/runpane/contract.json` as the source of truth for both wrapper packages and generated docs.\",\n \"Every command, flag, platform default, artifact-selection rule, and attribution rule change must be reflected in the contract.\",\n \"The npm and PyPI wrappers must expose the same command behavior unless the contract explicitly documents a package-manager-specific difference.\",\n \"Root `README.md` and package READMEs should lead with one guided quick-start command. Explicit commands, package-manager variants, and flags belong in an Advanced section.\",\n \"Release version bumps must keep root `package.json`, `packages/runpane`, and `packages/runpane-py` versions in sync. Run `pnpm run check:runpane-package-versions` before release.\",\n \"`pnpm run test:runpane-contract` must pass before changing wrapper command parsing, help output, platform defaults, release asset selection, or generated contract artifacts.\",\n \"Token-based npm or PyPI publishing is a temporary fallback. Prefer trusted publishing once the package names are reserved and trusted publishers are configured.\"\n ],\n \"recommendedQuickStarts\": [\n \"npx --yes runpane@latest\",\n \"npm i -g runpane && runpane setup\",\n \"pipx run runpane\",\n \"python -m pip install runpane && python -m runpane setup\"\n ],\n \"npmCommands\": [\n \"npx --yes runpane@latest\",\n \"npx --yes runpane@latest setup\",\n \"npx --yes runpane@latest install client\",\n \"npx --yes runpane@latest install daemon --label \\\"My Server\\\"\",\n \"pnpm dlx runpane@latest\",\n \"pnpm dlx runpane@latest install daemon --label \\\"My Server\\\"\",\n \"npm i -g runpane && runpane\",\n \"npm i -g runpane && runpane setup\",\n \"npm i -g runpane && runpane install daemon --label \\\"My Server\\\"\",\n \"pnpm add -g runpane && runpane\",\n \"pnpm add -g runpane && runpane setup\",\n \"pnpm add -g runpane && runpane install daemon --label \\\"My Server\\\"\",\n \"yarn dlx runpane@latest install daemon --label \\\"My Server\\\"\",\n \"bunx runpane@latest install daemon --label \\\"My Server\\\"\"\n ],\n \"pythonCommands\": [\n \"pipx run runpane\",\n \"pipx run runpane setup\",\n \"python -m pip install runpane\",\n \"runpane\",\n \"runpane setup\",\n \"python -m runpane setup\",\n \"runpane install daemon --label \\\"My Server\\\"\",\n \"pipx install runpane\",\n \"runpane\",\n \"runpane setup\",\n \"pipx run runpane install daemon --label \\\"My Server\\\"\",\n \"uvx runpane@latest\",\n \"uvx runpane@latest setup\",\n \"uvx runpane@latest install daemon --label \\\"My Server\\\"\",\n \"python -m runpane install daemon --label \\\"My Server\\\"\"\n ],\n \"packageManagerNotes\": [\n \"Use `pnpm dlx` for one-shot pnpm execution and `pnpm add -g` for persistent CLI installation.\",\n \"Do not document `pnpm install runpane` as the public CLI install path.\"\n ],\n \"commandUsages\": [\n \"runpane\",\n \"runpane setup\",\n \"runpane install\",\n \"runpane install client\",\n \"runpane install daemon\",\n \"runpane update\",\n \"runpane version\",\n \"runpane doctor\",\n \"runpane doctor --json\",\n \"runpane agent-context\",\n \"runpane agent-context --command \\\"panes create\\\" --json\",\n \"runpane repos list --json\",\n \"runpane repos add --path /path/to/repo --name Pane --yes --json\",\n \"runpane panes list --repo active --json\",\n \"runpane panes create --repo active --name issue-252 --agent <agent> --prompt \\\"Kick off the discussion skill for issue 252\\\" --source agent --no-focus --wait-ready --yes --json\",\n \"runpane panes create --from-json panes.json --yes --json\",\n \"runpane panes archive --pane <pane-id> --source agent --yes --json\",\n \"runpane panels list --pane <pane-id> --json\",\n \"runpane panels output --panel <panel-id> --limit 200 --json\",\n \"printf 'Continue\\\\n' | runpane panels input --panel <panel-id> --input-file - --yes --json\",\n \"runpane help\",\n \"runpane <command> --help\"\n ],\n \"commandDescriptions\": [\n \"`runpane` with no arguments and `runpane setup` open an interactive wizard when stdin and stdout are TTYs. In non-interactive shells or CI, both forms must print help, common commands, and agent discovery hints, then exit successfully instead of waiting for input.\",\n \"`runpane install` is an alias for `runpane install client`.\",\n \"`runpane install client` downloads the selected Pane desktop artifact and installs, opens, or launches it for the current platform.\",\n \"`runpane install daemon` downloads or installs Pane, resolves a stable Pane executable path, and spawns `<pane executable> --remote-setup <forwarded remote setup args>`.\",\n \"The wrapper must stream Pane stdout/stderr without reformatting because `pane --remote-setup` prints the one-time `pane-remote://...` connection code.\",\n \"`runpane update` uses the same release resolution and installer path as `install client`.\",\n \"`runpane version` prints only wrapper package metadata and does not contact, launch, or focus the Pane app or daemon.\",\n \"`runpane doctor` checks platform support, release metadata reachability, download URL selection, installed Pane detection, daemon reachability, and remote-daemon hints. Add `--json` for a machine-readable report that agents should run before mutating Pane state. Installed app version detection must be best-effort and must not launch, focus, or configure Pane; macOS wrappers read app bundle metadata instead of executing `Pane --version`.\",\n \"`runpane agent-context` prints a brief, token-efficient command schema for coding agents without connecting to the Pane daemon.\",\n \"`runpane agent-context --command \\\"panes create\\\"` prints the detailed definition for one command. Add `--json` for machine-readable output.\",\n \"`runpane repos list` connects to the running local Pane daemon and prints saved repository records.\",\n \"`runpane repos add` registers an existing git repository with the running local Pane daemon. It does not create directories or initialize git repositories by default.\",\n \"`runpane panes list` lists Pane sessions, optionally scoped to one saved repository.\",\n \"`runpane panes create` connects to the running local Pane daemon, resolves the requested saved base repository, creates user-visible Pane sessions backed by Pane-managed worktrees/branches, opens terminal-backed tool tabs, and optionally sends initial input to the started tool. Built-in agent panes and `--source agent` default to background/no-focus unless `--focus` is passed.\",\n \"`runpane panes archive` archives a Pane exactly like the UI Archive action, including removal of its Pane-managed git worktree, and refuses (unless `--force`) when the pane's branch has uncommitted, untracked, or unpushed-to-remote changes. It waits for worktree removal to finish before returning and reports the outcome in `worktreeCleanup`.\",\n \"`runpane panels list` lists tool panels inside one Pane session.\",\n \"`runpane panels output` reads bounded recent terminal output from one panel and strips common terminal control noise for agent use.\",\n \"`runpane panels input` sends exact input bytes to one terminal panel. Prefer `--input-file` for newlines, Ctrl-C, quotes, or shell-sensitive text.\",\n \"`runpane panes create --prompt` is an alias for `--initial-input`; request JSON and daemon payloads should use the canonical `initialInput` field.\",\n \"When running from WSL while Pane is installed on Windows, the Linux wrapper may look for a missing `/tmp/pane-daemon.../daemon.sock` or resolve to a Windows shim such as Volta. In that case invoke the Windows wrapper through PowerShell from a Windows cwd, for example `powershell.exe -NoProfile -Command 'Set-Location $env:TEMP; runpane repos list --json'`.\"\n ],\n \"wrapperFlagNote\": \"The top-level `runpane --version` form prints the wrapper version. The install subcommand form `runpane install --version vX.Y.Z` selects a Pane release.\",\n \"localControlFlagNote\": \"`runpane doctor --json`, `runpane repos list`, `runpane panes list`, `runpane panes create`, and `runpane panels ...` commands use or describe the local framed daemon socket/pipe for a running Pane app. `--pane-dir` points the wrapper at a non-default Pane data directory, such as `PANE_DIR=~/.pane_test` in development. `runpane agent-context` is local/offline and can be used before Pane is running. In a Pane repository checkout, if `runpane` is not on PATH, use the built local wrapper with Node 22, for example `PATH=/opt/homebrew/opt/node@22/bin:$PATH node packages/runpane/dist/cli.js doctor --json`. From WSL, if the user runs Windows Pane, call the Windows wrapper through `powershell.exe -NoProfile -Command 'Set-Location $env:TEMP; runpane ...'` so the command can reach the Windows named-pipe daemon and avoid UNC cwd issues.\",\n \"daemonFlagNote\": \"Unknown daemon flags should be forwarded rather than dropped so newer Pane versions can extend `--remote-setup` without requiring an immediate wrapper release. Unknown flags for non-daemon commands should fail clearly.\",\n \"downloadAttribution\": [\n \"The npm package uses `source=npm` for all npm-registry consumers, including `npx`, `pnpm dlx`, `yarn dlx`, `bunx`, and global npm/pnpm installs.\",\n \"The PyPI package uses `source=pip` for all Python consumers, including pip, pipx, uvx, and `python -m runpane`.\",\n \"Wrappers should prefer `https://runpane.com/api/download?platform=<platform>&arch=<arch>&format=<format>&version=<version>&channel=<channel>&source=<npm|pip>`.\",\n \"If the website route cannot satisfy the download, wrappers may fall back to the matching GitHub release asset and print a warning that website attribution may be incomplete for that run.\",\n \"Wrappers also emit best-effort lifecycle telemetry to `https://runpane.com/api/runpane/telemetry` for command start/success/failure, download request/success/failure, and GitHub fallback usage.\",\n \"Wrapper telemetry uses a persisted anonymous `install_id` in the form `install_<uuid>` and PostHog `distinct_id = install:<install_id>` so distinct wrapper users can be counted with `count(DISTINCT properties.install_id)`.\",\n \"Wrapper telemetry must be disabled in CI and when `RUNPANE_TELEMETRY_DISABLED` is set, and must not include raw paths, labels, prompts, raw error text, or environment values.\"\n ],\n \"publishingCredentials\": [\n \"Local implementation, build, and dry-run validation do not need npm or PyPI API tokens.\",\n \"Release publishing should prefer npm Trusted Publishing and PyPI Trusted Publishing from GitHub Actions.\",\n \"Fallback `NPM_TOKEN` or `PYPI_API_TOKEN` credentials may be used for first package reservation or manual publication only. They must be supplied through local environment variables or GitHub Actions secrets, never committed, and revoked or rotated after use.\"\n ]\n },\n \"testFixtures\": {\n \"parserSamples\": [\n [\n \"setup\"\n ],\n [\n \"install\"\n ],\n [\n \"install\",\n \"client\",\n \"--version\",\n \"v2.2.8\",\n \"--format\",\n \"dmg\",\n \"--download-dir\",\n \"/tmp/pane-downloads\",\n \"--dry-run\",\n \"--yes\"\n ],\n [\n \"install\",\n \"daemon\",\n \"--label\",\n \"VM\",\n \"--prefer-tunnel\",\n \"ssh\",\n \"--channel\",\n \"nightly\",\n \"--base-url\",\n \"https://example.test\",\n \"--pane-dir\",\n \"/tmp/pane\",\n \"--listen-port\",\n \"4555\",\n \"--auto-listen-port\",\n \"--print-only\",\n \"--repo-ref\",\n \"main\",\n \"--unknown-future-flag\",\n \"future-value\",\n \"--dry-run\",\n \"--verbose\"\n ],\n [\n \"update\",\n \"--version\",\n \"latest\",\n \"--format\",\n \"appimage\",\n \"--pane-path\",\n \"/usr/bin/pane\",\n \"--dry-run\"\n ],\n [\n \"doctor\",\n \"--pane-path\",\n \"/usr/bin/pane\",\n \"--format\",\n \"zip\",\n \"--verbose\"\n ],\n [\n \"doctor\",\n \"--json\",\n \"--pane-dir\",\n \"/tmp/pane\"\n ],\n [\n \"agent-context\"\n ],\n [\n \"agent-context\",\n \"--command\",\n \"panes create\",\n \"--json\"\n ],\n [\n \"repos\",\n \"list\",\n \"--json\",\n \"--pane-dir\",\n \"/tmp/pane\"\n ],\n [\n \"repos\",\n \"add\",\n \"--path\",\n \"/tmp/repo\",\n \"--name\",\n \"Repo\",\n \"--dry-run\",\n \"--yes\",\n \"--json\",\n \"--pane-dir\",\n \"/tmp/pane\"\n ],\n [\n \"panes\",\n \"list\",\n \"--repo\",\n \"active\",\n \"--json\"\n ],\n [\n \"panes\",\n \"create\",\n \"--repo\",\n \"active\",\n \"--name\",\n \"issue-252\",\n \"--agent\",\n \"codex\",\n \"--prompt\",\n \"Kick off discussion\",\n \"--source\",\n \"agent\",\n \"--pinned\",\n \"--dry-run\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"create\",\n \"--from-json\",\n \"-\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"archive\",\n \"--pane\",\n \"session-1\",\n \"--force\",\n \"--source\",\n \"agent\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"pin\",\n \"--pane\",\n \"session-1\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panes\",\n \"unpin\",\n \"--pane\",\n \"session-1\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panels\",\n \"list\",\n \"--pane\",\n \"session-1\",\n \"--json\"\n ],\n [\n \"panels\",\n \"output\",\n \"--panel\",\n \"panel-1\",\n \"--limit\",\n \"200\",\n \"--json\"\n ],\n [\n \"panels\",\n \"input\",\n \"--panel\",\n \"panel-1\",\n \"--text\",\n \"Continue\\\\n\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"--version\"\n ],\n [\n \"agents\",\n \"doctor\",\n \"--agent\",\n \"codex\",\n \"--repo\",\n \"active\",\n \"--json\"\n ],\n [\n \"panes\",\n \"create\",\n \"--from-json\",\n \"-\",\n \"--source\",\n \"agent\",\n \"--focus\",\n \"--wait-ready\",\n \"--ready-timeout-ms\",\n \"45000\",\n \"--concurrency\",\n \"2\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panels\",\n \"screen\",\n \"--panel\",\n \"panel-1\",\n \"--limit\",\n \"80\",\n \"--json\"\n ],\n [\n \"panels\",\n \"submit\",\n \"--panel\",\n \"panel-1\",\n \"--text\",\n \"2\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panels\",\n \"wait\",\n \"--panel\",\n \"panel-1\",\n \"--for\",\n \"text\",\n \"--contains\",\n \"ready\",\n \"--timeout-ms\",\n \"30000\",\n \"--interval-ms\",\n \"500\",\n \"--json\"\n ],\n [\n \"panels\",\n \"create\",\n \"--pane\",\n \"pane-1\",\n \"--agent\",\n \"codex\",\n \"--source\",\n \"agent\",\n \"--no-focus\",\n \"--wait-ready\",\n \"--ready-timeout-ms\",\n \"15000\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panels\",\n \"create\",\n \"--pane\",\n \"pane-1\",\n \"--agent\",\n \"codex\",\n \"--focus\",\n \"--yes\",\n \"--json\"\n ],\n [\n \"panels\",\n \"submit-composer\",\n \"--panel\",\n \"panel-1\",\n \"--strategy\",\n \"auto\",\n \"--yes\",\n \"--json\"\n ]\n ],\n \"topLevelHelpIncludes\": [\n \"runpane setup\",\n \"runpane install\",\n \"runpane update\",\n \"runpane version\",\n \"runpane doctor\",\n \"runpane agent-context\",\n \"runpane repos list\",\n \"runpane repos add\",\n \"runpane panes list\",\n \"runpane panes create\",\n \"runpane panes archive\",\n \"runpane panels create\",\n \"runpane panels list\",\n \"runpane panels output\",\n \"runpane panels input\",\n \"runpane agents doctor\",\n \"runpane panels screen\",\n \"runpane panels submit\",\n \"runpane panels submit-composer\",\n \"runpane panels wait\",\n \"runpane doctor --json\",\n \"runpane agent-context --json\",\n \"Agent discovery:\",\n \"Common commands:\"\n ],\n \"npmHelpIncludes\": [\n \"pnpm dlx runpane@latest\",\n \"npx --yes runpane@latest\"\n ],\n \"pipHelpIncludes\": [\n \"pipx run runpane\",\n \"python -m pip install runpane && python -m runpane setup\"\n ],\n \"installHelpIncludes\": [\n \"--version <latest|vX.Y.Z>\",\n \"--format <auto|appimage|deb|dmg|zip|exe>\",\n \"--download-dir <path>\",\n \"--pane-path <path>\",\n \"--label <name>\",\n \"--prefer-tunnel <tailscale|ssh|manual|auto>\",\n \"--repo-ref <ref>\"\n ]\n },\n \"jsonSchemas\": {\n \"error\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"error\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": false\n },\n \"error\": {\n \"type\": \"object\",\n \"required\": [\n \"message\"\n ],\n \"properties\": {\n \"message\": {\n \"type\": \"string\"\n },\n \"code\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"doctorResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"source\",\n \"wrapper\",\n \"release\",\n \"installedPane\",\n \"daemon\",\n \"remoteSetup\",\n \"nextCommands\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"source\": {\n \"enum\": [\n \"npm\",\n \"pip\"\n ]\n },\n \"wrapper\": {\n \"type\": \"object\",\n \"required\": [\n \"runtime\",\n \"version\",\n \"paneDir\",\n \"endpoint\"\n ],\n \"properties\": {\n \"runtime\": {\n \"enum\": [\n \"node\",\n \"python\"\n ]\n },\n \"version\": {\n \"type\": \"string\"\n },\n \"paneDir\": {\n \"type\": \"string\"\n },\n \"endpoint\": {\n \"type\": \"object\",\n \"required\": [\n \"transport\",\n \"path\"\n ],\n \"properties\": {\n \"transport\": {\n \"enum\": [\n \"pipe\",\n \"unix\"\n ]\n },\n \"path\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"platform\": {\n \"type\": \"object\",\n \"properties\": {\n \"os\": {\n \"type\": \"string\"\n },\n \"arch\": {\n \"type\": \"string\"\n },\n \"error\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": true\n },\n \"release\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"error\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": true\n },\n \"installedPane\": {\n \"type\": \"object\",\n \"required\": [\n \"found\"\n ],\n \"properties\": {\n \"found\": {\n \"type\": \"boolean\"\n },\n \"path\": {\n \"type\": \"string\"\n },\n \"version\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"daemon\": {\n \"type\": \"object\",\n \"required\": [\n \"reachable\",\n \"endpoint\"\n ],\n \"properties\": {\n \"reachable\": {\n \"type\": \"boolean\"\n },\n \"endpoint\": {\n \"type\": \"object\",\n \"required\": [\n \"transport\",\n \"path\"\n ],\n \"properties\": {\n \"transport\": {\n \"enum\": [\n \"pipe\",\n \"unix\"\n ]\n },\n \"path\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"result\": {\n \"type\": \"object\"\n },\n \"error\": {\n \"type\": \"string\"\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"remoteSetup\": {\n \"type\": \"object\",\n \"required\": [\n \"ready\",\n \"displayAvailable\",\n \"headlessEnvironmentApplied\",\n \"diagnostics\"\n ],\n \"properties\": {\n \"ready\": {\n \"type\": \"boolean\"\n },\n \"displayAvailable\": {\n \"type\": \"boolean\"\n },\n \"headlessEnvironmentApplied\": {\n \"type\": \"boolean\"\n },\n \"diagnostics\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"code\",\n \"severity\",\n \"message\"\n ],\n \"properties\": {\n \"code\": {\n \"enum\": [\n \"PANE_APPIMAGE_FUSE_MISSING\",\n \"PANE_ELECTRON_SANDBOX_ROOT\",\n \"PANE_ELECTRON_SANDBOX_UNAVAILABLE\",\n \"PANE_USER_SERVICE_UNAVAILABLE\"\n ]\n },\n \"severity\": {\n \"enum\": [\n \"warning\",\n \"error\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"recoveryCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"additionalProperties\": false\n },\n \"nextCommands\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n },\n \"additionalProperties\": false\n },\n \"repoListResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"repos\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"repos\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"id\",\n \"name\",\n \"path\",\n \"active\",\n \"sessionCount\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"number\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"path\": {\n \"type\": \"string\"\n },\n \"active\": {\n \"type\": \"boolean\"\n },\n \"environment\": {\n \"type\": \"string\"\n },\n \"sessionCount\": {\n \"type\": \"number\"\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"additionalProperties\": false\n },\n \"repoAddRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"path\"\n ],\n \"properties\": {\n \"path\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"dryRun\": {\n \"type\": \"boolean\"\n }\n },\n \"additionalProperties\": false\n },\n \"repoAddResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"created\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"created\": {\n \"type\": \"boolean\"\n },\n \"dryRun\": {\n \"type\": \"boolean\"\n },\n \"repo\": {\n \"$ref\": \"#/jsonSchemas/repoListResult/properties/repos/items\"\n },\n \"preview\": {\n \"type\": \"object\",\n \"required\": [\n \"name\",\n \"path\",\n \"alreadyExists\",\n \"wouldCreate\"\n ],\n \"properties\": {\n \"name\": {\n \"type\": \"string\"\n },\n \"path\": {\n \"type\": \"string\"\n },\n \"alreadyExists\": {\n \"type\": \"boolean\"\n },\n \"wouldCreate\": {\n \"type\": \"boolean\"\n },\n \"environment\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"paneCreateRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"repo\",\n \"panes\"\n ],\n \"properties\": {\n \"repo\": {\n \"oneOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"type\": \"object\",\n \"properties\": {\n \"id\": {\n \"type\": \"number\"\n },\n \"path\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"active\": {\n \"const\": true\n }\n },\n \"additionalProperties\": false\n }\n ]\n },\n \"panes\": {\n \"type\": \"array\",\n \"minItems\": 1,\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"name\",\n \"tool\"\n ],\n \"properties\": {\n \"name\": {\n \"type\": \"string\"\n },\n \"worktreeName\": {\n \"type\": \"string\"\n },\n \"baseBranch\": {\n \"type\": \"string\"\n },\n \"sessionPrompt\": {\n \"type\": \"string\"\n },\n \"pinned\": {\n \"type\": \"boolean\"\n },\n \"tool\": {\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"required\": [\n \"agent\"\n ],\n \"properties\": {\n \"agent\": {\n \"enum\": [\n \"codex\",\n \"claude\"\n ]\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"initialInput\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"command\"\n ],\n \"properties\": {\n \"command\": {\n \"type\": \"string\"\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"initialInput\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n ]\n }\n },\n \"additionalProperties\": false\n }\n },\n \"dryRun\": {\n \"type\": \"boolean\"\n },\n \"timeoutMs\": {\n \"type\": \"number\"\n },\n \"waitReady\": {\n \"type\": \"boolean\"\n },\n \"readyTimeoutMs\": {\n \"type\": \"number\"\n },\n \"concurrency\": {\n \"type\": \"number\"\n },\n \"noFocus\": {\n \"type\": \"boolean\"\n },\n \"focus\": {\n \"type\": \"boolean\"\n },\n \"source\": {\n \"enum\": [\n \"user\",\n \"agent\"\n ]\n }\n },\n \"additionalProperties\": false\n },\n \"paneCreateResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"repo\",\n \"items\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"repo\": {\n \"$ref\": \"#/jsonSchemas/repoListResult/properties/repos/items\"\n },\n \"items\": {\n \"type\": \"array\",\n \"items\": {\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"index\",\n \"name\",\n \"pinned\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"index\": {\n \"type\": \"number\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"pinned\": {\n \"type\": \"boolean\"\n },\n \"sessionId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"worktreePath\": {\n \"type\": \"string\"\n },\n \"nextCommand\": {\n \"type\": \"string\"\n },\n \"tool\": {\n \"type\": \"object\",\n \"required\": [\n \"title\",\n \"command\"\n ],\n \"properties\": {\n \"title\": {\n \"type\": \"string\"\n },\n \"command\": {\n \"type\": \"string\"\n },\n \"agent\": {\n \"enum\": [\n \"codex\",\n \"claude\"\n ]\n }\n },\n \"additionalProperties\": false\n },\n \"active\": {\n \"type\": \"boolean\"\n },\n \"focused\": {\n \"type\": \"boolean\"\n },\n \"readiness\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"condition\",\n \"matched\",\n \"timedOut\",\n \"elapsedMs\",\n \"state\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"condition\": {\n \"enum\": [\n \"initialized\",\n \"ready\",\n \"idle\",\n \"text\"\n ]\n },\n \"matched\": {\n \"type\": \"boolean\"\n },\n \"timedOut\": {\n \"type\": \"boolean\"\n },\n \"elapsedMs\": {\n \"type\": \"number\"\n },\n \"state\": {\n \"type\": \"object\",\n \"required\": [\n \"initialized\"\n ],\n \"properties\": {\n \"initialized\": {\n \"type\": \"boolean\"\n },\n \"isAlternateScreen\": {\n \"type\": \"boolean\"\n },\n \"activityStatus\": {\n \"enum\": [\n \"active\",\n \"idle\"\n ]\n },\n \"isCliReady\": {\n \"type\": \"boolean\"\n },\n \"isCliPanel\": {\n \"type\": \"boolean\"\n },\n \"agentType\": {\n \"enum\": [\n \"codex\",\n \"claude\"\n ]\n },\n \"lastActivity\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"blocked\": {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"message\"\n ],\n \"properties\": {\n \"kind\": {\n \"enum\": [\n \"codex-update\",\n \"agent-prompt\",\n \"unknown\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"suggestedCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"initialInput\": {\n \"type\": \"object\",\n \"required\": [\n \"delivered\",\n \"submitted\",\n \"inputBytes\"\n ],\n \"properties\": {\n \"delivered\": {\n \"type\": \"boolean\"\n },\n \"submitted\": {\n \"type\": \"boolean\"\n },\n \"inputBytes\": {\n \"type\": \"number\"\n },\n \"strategy\": {\n \"enum\": [\n \"codex-ctrl-enter\",\n \"enter\",\n \"argument\"\n ]\n },\n \"sequenceName\": {\n \"enum\": [\n \"codex-ctrl-enter-cr\",\n \"enter-cr\",\n \"argument\"\n ]\n },\n \"verifiedSubmitted\": {\n \"type\": \"boolean\"\n },\n \"sentAt\": {\n \"type\": \"string\"\n },\n \"blocked\": {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"message\"\n ],\n \"properties\": {\n \"kind\": {\n \"enum\": [\n \"codex-update\",\n \"agent-prompt\",\n \"unknown\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"suggestedCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"error\": {\n \"type\": \"object\",\n \"required\": [\n \"message\"\n ],\n \"properties\": {\n \"message\": {\n \"type\": \"string\"\n },\n \"code\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"index\",\n \"error\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": false\n },\n \"index\": {\n \"type\": \"number\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"error\": {\n \"type\": \"object\",\n \"required\": [\n \"message\"\n ],\n \"properties\": {\n \"message\": {\n \"type\": \"string\"\n },\n \"code\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n }\n ]\n }\n }\n },\n \"additionalProperties\": false\n },\n \"paneListResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"panes\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"repo\": {\n \"$ref\": \"#/jsonSchemas/repoListResult/properties/repos/items\"\n },\n \"panes\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"id\",\n \"paneId\",\n \"name\",\n \"status\",\n \"worktreePath\",\n \"repoId\",\n \"panelCount\",\n \"pinned\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"status\": {\n \"type\": \"string\"\n },\n \"worktreePath\": {\n \"type\": \"string\"\n },\n \"repoId\": {\n \"type\": \"number\"\n },\n \"repoName\": {\n \"type\": \"string\"\n },\n \"panelCount\": {\n \"type\": \"number\"\n },\n \"pinned\": {\n \"type\": \"boolean\"\n },\n \"createdAt\": {\n \"type\": \"string\"\n },\n \"lastActivity\": {\n \"type\": \"string\"\n },\n \"archived\": {\n \"type\": \"boolean\"\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"additionalProperties\": false\n },\n \"paneArchiveRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"paneId\"\n ],\n \"properties\": {\n \"paneId\": {\n \"type\": \"string\"\n },\n \"force\": {\n \"type\": \"boolean\"\n },\n \"source\": {\n \"enum\": [\n \"user\",\n \"agent\"\n ]\n }\n },\n \"additionalProperties\": false\n },\n \"panePinRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"paneId\",\n \"pinned\"\n ],\n \"properties\": {\n \"paneId\": {\n \"type\": \"string\"\n },\n \"pinned\": {\n \"type\": \"boolean\"\n },\n \"dryRun\": {\n \"type\": \"boolean\"\n }\n },\n \"additionalProperties\": false\n },\n \"panePinResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"paneId\",\n \"pinned\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"pinned\": {\n \"type\": \"boolean\"\n },\n \"dryRun\": {\n \"const\": true\n },\n \"favoritePinnedAt\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"paneArchiveResult\": {\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"paneId\",\n \"archived\",\n \"forced\",\n \"worktreeCleanup\",\n \"safetyCheck\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"archived\": {\n \"const\": true\n },\n \"forced\": {\n \"type\": \"boolean\"\n },\n \"worktreeCleanup\": {\n \"enum\": [\n \"completed\",\n \"failed\",\n \"timeout\",\n \"not-applicable\"\n ]\n },\n \"worktreePath\": {\n \"type\": \"string\"\n },\n \"safetyCheck\": {\n \"type\": \"object\",\n \"required\": [\n \"performed\"\n ],\n \"properties\": {\n \"performed\": {\n \"type\": \"boolean\"\n },\n \"hasUncommittedChanges\": {\n \"type\": \"boolean\"\n },\n \"hasUntrackedFiles\": {\n \"type\": \"boolean\"\n },\n \"hasUpstream\": {\n \"type\": \"boolean\"\n },\n \"unpushedCommits\": {\n \"type\": \"number\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"paneId\",\n \"blocked\",\n \"nextCommand\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": false\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"blocked\": {\n \"type\": \"object\",\n \"required\": [\n \"code\",\n \"message\",\n \"safetyCheck\"\n ],\n \"properties\": {\n \"code\": {\n \"enum\": [\n \"uncommitted-changes\",\n \"unpushed-commits\",\n \"uncommitted-and-unpushed\",\n \"status-unknown\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"safetyCheck\": {\n \"type\": \"object\",\n \"required\": [\n \"performed\"\n ],\n \"properties\": {\n \"performed\": {\n \"type\": \"boolean\"\n },\n \"hasUncommittedChanges\": {\n \"type\": \"boolean\"\n },\n \"hasUntrackedFiles\": {\n \"type\": \"boolean\"\n },\n \"hasUpstream\": {\n \"type\": \"boolean\"\n },\n \"unpushedCommits\": {\n \"type\": \"number\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n ]\n },\n \"panelListResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"paneId\",\n \"panels\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panels\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"id\",\n \"panelId\",\n \"paneId\",\n \"type\",\n \"title\",\n \"active\"\n ],\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"type\": {\n \"type\": \"string\"\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"active\": {\n \"type\": \"boolean\"\n },\n \"initialized\": {\n \"type\": \"boolean\"\n },\n \"agentType\": {\n \"type\": \"string\"\n },\n \"isCliPanel\": {\n \"type\": \"boolean\"\n },\n \"position\": {\n \"type\": \"number\"\n },\n \"createdAt\": {\n \"type\": \"string\"\n },\n \"lastActiveAt\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"additionalProperties\": false\n },\n \"panelOutputResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"panelId\",\n \"limit\",\n \"returnedCount\",\n \"hasMore\",\n \"outputs\",\n \"text\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"limit\": {\n \"type\": \"number\"\n },\n \"returnedCount\": {\n \"type\": \"number\"\n },\n \"hasMore\": {\n \"type\": \"boolean\"\n },\n \"outputs\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"type\",\n \"data\",\n \"timestamp\"\n ],\n \"properties\": {\n \"type\": {\n \"type\": \"string\"\n },\n \"data\": {},\n \"timestamp\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"text\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelInputRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"panelId\",\n \"input\"\n ],\n \"properties\": {\n \"panelId\": {\n \"type\": \"string\"\n },\n \"input\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelInputResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"panelId\",\n \"inputBytes\",\n \"sentAt\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"inputBytes\": {\n \"type\": \"number\"\n },\n \"sentAt\": {\n \"type\": \"string\"\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"agentContextBriefResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"mode\",\n \"source\",\n \"summary\",\n \"rules\",\n \"tools\",\n \"detailCommand\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"mode\": {\n \"const\": \"brief\"\n },\n \"source\": {\n \"const\": \"runpane-contract\"\n },\n \"summary\": {\n \"type\": \"string\"\n },\n \"rules\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"tools\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"name\",\n \"summary\",\n \"arguments\"\n ],\n \"properties\": {\n \"name\": {\n \"type\": \"string\"\n },\n \"summary\": {\n \"type\": \"string\"\n },\n \"arguments\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n },\n \"additionalProperties\": false\n }\n },\n \"detailCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"agentContextCommandResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"mode\",\n \"source\",\n \"command\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"mode\": {\n \"const\": \"command\"\n },\n \"source\": {\n \"const\": \"runpane-contract\"\n },\n \"command\": {\n \"type\": \"object\",\n \"required\": [\n \"name\",\n \"summary\",\n \"details\",\n \"arguments\",\n \"examples\"\n ],\n \"properties\": {\n \"name\": {\n \"type\": \"string\"\n },\n \"summary\": {\n \"type\": \"string\"\n },\n \"details\": {\n \"type\": \"string\"\n },\n \"requiresPaneDaemon\": {\n \"type\": \"boolean\"\n },\n \"mutates\": {\n \"type\": \"boolean\"\n },\n \"arguments\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\"\n }\n },\n \"examples\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"jsonSchemas\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n },\n \"notes\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"panelScreenResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"panelId\",\n \"source\",\n \"limit\",\n \"returnedLineCount\",\n \"hasMore\",\n \"text\",\n \"state\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"source\": {\n \"enum\": [\n \"alternateScreen\",\n \"scrollback\",\n \"persistedOutput\",\n \"empty\"\n ]\n },\n \"limit\": {\n \"type\": \"number\"\n },\n \"returnedLineCount\": {\n \"type\": \"number\"\n },\n \"hasMore\": {\n \"type\": \"boolean\"\n },\n \"text\": {\n \"type\": \"string\"\n },\n \"state\": {\n \"type\": \"object\",\n \"required\": [\n \"initialized\"\n ],\n \"properties\": {\n \"initialized\": {\n \"type\": \"boolean\"\n },\n \"isAlternateScreen\": {\n \"type\": \"boolean\"\n },\n \"activityStatus\": {\n \"enum\": [\n \"active\",\n \"idle\"\n ]\n },\n \"isCliReady\": {\n \"type\": \"boolean\"\n },\n \"isCliPanel\": {\n \"type\": \"boolean\"\n },\n \"agentType\": {\n \"enum\": [\n \"codex\",\n \"claude\"\n ]\n },\n \"lastActivity\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"initialInput\": {\n \"$ref\": \"#/jsonSchemas/paneCreateResult/properties/items/items/oneOf/0/properties/initialInput\"\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelSubmitRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"panelId\",\n \"input\"\n ],\n \"properties\": {\n \"panelId\": {\n \"type\": \"string\"\n },\n \"input\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelSubmitResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"panelId\",\n \"inputBytes\",\n \"enter\",\n \"sentAt\"\n ],\n \"properties\": {\n \"ok\": {\n \"const\": true\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"inputBytes\": {\n \"type\": \"number\"\n },\n \"enter\": {\n \"const\": \"cr\"\n },\n \"sentAt\": {\n \"type\": \"string\"\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelWaitResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"panelId\",\n \"condition\",\n \"matched\",\n \"timedOut\",\n \"elapsedMs\",\n \"state\",\n \"screen\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"condition\": {\n \"enum\": [\n \"initialized\",\n \"ready\",\n \"idle\",\n \"text\"\n ]\n },\n \"matched\": {\n \"type\": \"boolean\"\n },\n \"timedOut\": {\n \"type\": \"boolean\"\n },\n \"elapsedMs\": {\n \"type\": \"number\"\n },\n \"state\": {\n \"type\": \"object\",\n \"required\": [\n \"initialized\"\n ],\n \"properties\": {\n \"initialized\": {\n \"type\": \"boolean\"\n },\n \"isAlternateScreen\": {\n \"type\": \"boolean\"\n },\n \"activityStatus\": {\n \"enum\": [\n \"active\",\n \"idle\"\n ]\n },\n \"isCliReady\": {\n \"type\": \"boolean\"\n },\n \"isCliPanel\": {\n \"type\": \"boolean\"\n },\n \"agentType\": {\n \"enum\": [\n \"codex\",\n \"claude\"\n ]\n },\n \"lastActivity\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"blocked\": {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"message\"\n ],\n \"properties\": {\n \"kind\": {\n \"enum\": [\n \"codex-update\",\n \"agent-prompt\",\n \"unknown\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"suggestedCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"screen\": {\n \"type\": \"object\",\n \"required\": [\n \"source\",\n \"text\",\n \"hasMore\"\n ],\n \"properties\": {\n \"source\": {\n \"enum\": [\n \"alternateScreen\",\n \"scrollback\",\n \"persistedOutput\",\n \"empty\"\n ]\n },\n \"text\": {\n \"type\": \"string\"\n },\n \"hasMore\": {\n \"type\": \"boolean\"\n }\n },\n \"additionalProperties\": false\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"agentDoctorResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"agent\",\n \"command\",\n \"available\",\n \"checks\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"agent\": {\n \"enum\": [\n \"codex\",\n \"claude\"\n ]\n },\n \"command\": {\n \"type\": \"string\"\n },\n \"repo\": {\n \"$ref\": \"#/jsonSchemas/repoListResult/properties/repos/items\"\n },\n \"environment\": {\n \"enum\": [\n \"wsl\",\n \"windows\",\n \"linux\",\n \"macos\"\n ]\n },\n \"available\": {\n \"type\": \"boolean\"\n },\n \"executablePath\": {\n \"type\": \"string\"\n },\n \"version\": {\n \"type\": \"string\"\n },\n \"checks\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"required\": [\n \"name\",\n \"ok\",\n \"message\"\n ],\n \"properties\": {\n \"name\": {\n \"type\": \"string\"\n },\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"message\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"warnings\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n },\n \"additionalProperties\": false\n },\n \"panelCreateRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"paneId\",\n \"tool\"\n ],\n \"properties\": {\n \"paneId\": {\n \"type\": \"string\"\n },\n \"type\": {\n \"const\": \"terminal\"\n },\n \"tool\": {\n \"type\": \"object\",\n \"additionalProperties\": true\n },\n \"noFocus\": {\n \"type\": \"boolean\"\n },\n \"focus\": {\n \"type\": \"boolean\"\n },\n \"source\": {\n \"enum\": [\n \"user\",\n \"agent\"\n ]\n },\n \"waitReady\": {\n \"type\": \"boolean\"\n },\n \"readyTimeoutMs\": {\n \"type\": \"number\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelCreateResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"paneId\",\n \"panelId\",\n \"title\",\n \"active\",\n \"focused\",\n \"tool\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"title\": {\n \"type\": \"string\"\n },\n \"active\": {\n \"type\": \"boolean\"\n },\n \"focused\": {\n \"type\": \"boolean\"\n },\n \"tool\": {\n \"type\": \"object\",\n \"required\": [\n \"title\",\n \"command\"\n ],\n \"properties\": {\n \"title\": {\n \"type\": \"string\"\n },\n \"command\": {\n \"type\": \"string\"\n },\n \"agent\": {\n \"enum\": [\n \"codex\",\n \"claude\"\n ]\n }\n },\n \"additionalProperties\": false\n },\n \"readiness\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"condition\",\n \"matched\",\n \"timedOut\",\n \"elapsedMs\",\n \"state\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"condition\": {\n \"enum\": [\n \"initialized\",\n \"ready\",\n \"idle\",\n \"text\"\n ]\n },\n \"matched\": {\n \"type\": \"boolean\"\n },\n \"timedOut\": {\n \"type\": \"boolean\"\n },\n \"elapsedMs\": {\n \"type\": \"number\"\n },\n \"state\": {\n \"type\": \"object\",\n \"required\": [\n \"initialized\"\n ],\n \"properties\": {\n \"initialized\": {\n \"type\": \"boolean\"\n },\n \"isAlternateScreen\": {\n \"type\": \"boolean\"\n },\n \"activityStatus\": {\n \"enum\": [\n \"active\",\n \"idle\"\n ]\n },\n \"isCliReady\": {\n \"type\": \"boolean\"\n },\n \"isCliPanel\": {\n \"type\": \"boolean\"\n },\n \"agentType\": {\n \"enum\": [\n \"codex\",\n \"claude\"\n ]\n },\n \"lastActivity\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"blocked\": {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"message\"\n ],\n \"properties\": {\n \"kind\": {\n \"enum\": [\n \"codex-update\",\n \"agent-prompt\",\n \"unknown\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"suggestedCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"panelSubmitComposerRequest\": {\n \"type\": \"object\",\n \"required\": [\n \"panelId\"\n ],\n \"properties\": {\n \"panelId\": {\n \"type\": \"string\"\n },\n \"strategy\": {\n \"enum\": [\n \"auto\",\n \"codex-ctrl-enter\",\n \"enter\"\n ]\n }\n },\n \"additionalProperties\": false\n },\n \"panelSubmitComposerResult\": {\n \"type\": \"object\",\n \"required\": [\n \"ok\",\n \"panelId\",\n \"inputBytes\",\n \"strategy\",\n \"sequenceName\",\n \"verifiedSubmitted\",\n \"sentAt\"\n ],\n \"properties\": {\n \"ok\": {\n \"type\": \"boolean\"\n },\n \"panelId\": {\n \"type\": \"string\"\n },\n \"paneId\": {\n \"type\": \"string\"\n },\n \"inputBytes\": {\n \"type\": \"number\"\n },\n \"strategy\": {\n \"enum\": [\n \"codex-ctrl-enter\",\n \"enter\"\n ]\n },\n \"sequenceName\": {\n \"enum\": [\n \"codex-ctrl-enter-cr\",\n \"enter-cr\"\n ]\n },\n \"verifiedSubmitted\": {\n \"type\": \"boolean\"\n },\n \"sentAt\": {\n \"type\": \"string\"\n },\n \"blocked\": {\n \"type\": \"object\",\n \"required\": [\n \"kind\",\n \"message\"\n ],\n \"properties\": {\n \"kind\": {\n \"enum\": [\n \"codex-update\",\n \"agent-prompt\",\n \"unknown\"\n ]\n },\n \"message\": {\n \"type\": \"string\"\n },\n \"suggestedCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"nextCommand\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"agentContext\": {\n \"brief\": {\n \"title\": \"Pane agent context\",\n \"summary\": \"Pane lets a developer manage saved base repositories, user-visible Panes (Pane sessions) for feature/PR work, and terminal-backed panel tabs. A Pane is a visible workspace that normally maps to one Pane-managed git worktree and branch; a panel is a terminal tab inside one Pane and shares that Pane's worktree; an agent is a CLI process running inside a panel.\",\n \"rules\": [\n \"Start with `runpane doctor --json` to understand wrapper, platform, daemon reachability, and the next safe commands before mutating Pane state.\",\n \"In a Pane repository checkout, if `runpane` is not on PATH, use the built local wrapper with Node 22, for example `PATH=/opt/homebrew/opt/node@22/bin:$PATH node packages/runpane/dist/cli.js doctor --json`.\",\n \"Happy path for any user request to use Pane/RunPane: run `runpane doctor --json`, read `runpane agent-context --json`, resolve or add the saved base repo, create the requested visible Pane with a complete command such as `runpane panes create --repo <repo> --name <name> --agent <agent> --prompt \\\"<task>\\\" --source agent --pinned --no-focus --wait-ready --yes --json` when it should stay visible (or omit `--pinned`, or use `--tool-command <command>` instead of `--agent <agent>`), then validate with `panels wait` or `panels screen`.\",\n \"Treat Pane as the user's visible cockpit for watching/co-driving work. Do not create Panes or panels for private delegation unless the user asked for visible Pane orchestration or the result should appear in the Pane app.\",\n \"Register the saved base repository once with `repos add`; do not register a pre-created worktree as a separate repo unless the user explicitly asks.\",\n \"Use `panes create` for separate visible Panes (Pane sessions) for feature/PR work. Pane creates and owns the worktree/branch for each new Pane.\",\n \"Use `panels create` for reviewer/helper/clean-context tabs that should stay inside an existing Pane and share that Pane's worktree.\",\n \"For private background decomposition, use your normal subagent/worktree mechanism instead of Pane.\",\n \"Skill routing: when the user says `discussion`, `plan`, `simple-plan`, `create-plan`, or `implement`, or asks for those behaviors, treat three references as peer context: Pane's local skill cache under `<PANE_DIR>/skills/`, the Pane Chat orchestrator handoff at `<PANE_DIR>/skills/pane-chat/runpane-orchestrator.md` when present, and the workflow map at https://github.com/dcouple/skills/raw/main/docs/readme-workflow-map.png. For where the local cache and Pane Chat bootstrap live in Pane, reference https://github.com/dcouple/Pane/pull/291. Use GitHub reads against https://github.com/dcouple/skills/tree/main/parsa only to inspect or refresh referenced skill files; do not clone or install it unless the user asks.\",\n \"When an agent creates Panes or panels, pass `--source agent --no-focus --wait-ready --yes --json` unless the user explicitly wants focus moved; add `--pinned` when declaring a Pane that should remain in the UI's favorite/pin set.\",\n \"Use `runpane agent-context --json` for the full agent-facing CLI context, or `runpane agent-context --command <command> --json` for one detailed command definition.\",\n \"For `agent-context --command`, use canonical spaced names like `panes create`; copied forms like `panes.create` or `runpane panes create` are accepted too.\",\n \"Use `runpane repos list --json` to find the saved repository when unsure after doctor shows the daemon is reachable.\",\n \"If WSL cannot reach Pane or `runpane` resolves to a broken Windows shim, the user may be running Windows Pane; try `powershell.exe -NoProfile -Command 'Set-Location $env:TEMP; runpane doctor --json'`.\",\n \"If the repository exists on disk but is not saved in Pane, use `runpane repos add --path <repo> --yes --json` before creating panes.\",\n \"Use `runpane agents doctor --agent <codex|claude> --repo <selector> --json` when agent availability differs across host, Windows, WSL, or repo environments.\",\n \"Use `runpane panes create --wait-ready` to create Panes and validate initial terminal readiness in one call.\",\n \"Use `runpane panels screen` for compact current state, `panels wait` for readiness/text checks, `panels submit` for ordinary Enter-submitted input, and `panels submit-composer --strategy auto` for agent composers.\",\n \"Use `runpane panels input` only when exact bytes are required, such as Ctrl-C or handcrafted terminal input.\",\n \"After creating Panes or sending terminal input, validate with `panels wait` or bounded `panels screen` before reporting success.\"\n ],\n \"detailCommand\": \"runpane agent-context --command <command> [--json]\",\n \"tools\": [\n {\n \"name\": \"doctor\",\n \"summary\": \"Report wrapper, platform, installed Pane, and daemon reachability before an agent mutates Pane state.\",\n \"arguments\": [\n \"--json\",\n \"--pane-dir <path>\",\n \"--pane-path <path>\"\n ]\n },\n {\n \"name\": \"agent-context\",\n \"summary\": \"Print token-efficient Pane command context for coding agents.\",\n \"arguments\": [\n \"--command <command>\",\n \"--json\"\n ]\n },\n {\n \"name\": \"agents doctor\",\n \"summary\": \"Check whether Codex or Claude is available in the repo environment Pane will use.\",\n \"arguments\": [\n \"--agent <codex|claude>\",\n \"--repo <selector>\",\n \"--json\"\n ]\n },\n {\n \"name\": \"repos list\",\n \"summary\": \"List repositories saved in the running Pane app.\",\n \"arguments\": [\n \"--json\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"repos add\",\n \"summary\": \"Register an existing git repository with the running Pane app.\",\n \"arguments\": [\n \"--path <path>\",\n \"--name <name>\",\n \"--yes\",\n \"--json\",\n \"--dry-run\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"panes list\",\n \"summary\": \"List Pane sessions, optionally scoped to a saved repository.\",\n \"arguments\": [\n \"--repo <selector>\",\n \"--json\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"panes create\",\n \"summary\": \"Create user-visible Panes (Pane sessions) backed by Pane-managed worktrees for feature/PR work and open terminal-backed tool tabs.\",\n \"arguments\": [\n \"--repo <selector>\",\n \"--name <name>\",\n \"--agent <codex|claude>\",\n \"--tool-command <command>\",\n \"--prompt <text>\",\n \"--initial-input-file <path|->\",\n \"--from-json <path|->\",\n \"--source <user|agent>\",\n \"--pinned\",\n \"--no-focus\",\n \"--focus\",\n \"--wait-ready\",\n \"--ready-timeout-ms <ms>\",\n \"--concurrency <count>\",\n \"--yes\",\n \"--json\"\n ]\n },\n {\n \"name\": \"panes archive\",\n \"summary\": \"Archive a Pane exactly like the UI Archive action, including safe removal of its Pane-managed git worktree.\",\n \"arguments\": [\n \"--pane <pane-id>\",\n \"--source <user|agent>\",\n \"--force\",\n \"--yes\",\n \"--json\"\n ]\n },\n {\n \"name\": \"panes pin\",\n \"summary\": \"Declaratively pin a Pane (the Pane UI's favorite/pin star) without changing focus.\",\n \"arguments\": [\n \"--pane <pane-id>\",\n \"--yes\",\n \"--dry-run\",\n \"--json\"\n ]\n },\n {\n \"name\": \"panes unpin\",\n \"summary\": \"Declaratively unpin a Pane (the Pane UI's favorite/pin star) without changing focus.\",\n \"arguments\": [\n \"--pane <pane-id>\",\n \"--yes\",\n \"--dry-run\",\n \"--json\"\n ]\n },\n {\n \"name\": \"panels create\",\n \"summary\": \"Create reviewer/helper terminal tabs inside an existing Pane; they share that Pane's worktree.\",\n \"arguments\": [\n \"--pane <pane-id>\",\n \"--agent <codex|claude>\",\n \"--tool-command <command>\",\n \"--source <user|agent>\",\n \"--no-focus\",\n \"--focus\",\n \"--wait-ready\",\n \"--yes\",\n \"--json\"\n ]\n },\n {\n \"name\": \"panels list\",\n \"summary\": \"List tool panels inside a Pane session.\",\n \"arguments\": [\n \"--pane <pane-id>\",\n \"--json\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"panels output\",\n \"summary\": \"Read recent terminal output from a panel.\",\n \"arguments\": [\n \"--panel <panel-id>\",\n \"--limit <count>\",\n \"--json\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"panels screen\",\n \"summary\": \"Read a compact current-screen view from a terminal panel.\",\n \"arguments\": [\n \"--panel <panel-id>\",\n \"--limit <count>\",\n \"--json\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"panels input\",\n \"summary\": \"Send input bytes to a terminal panel.\",\n \"arguments\": [\n \"--panel <panel-id>\",\n \"--text <text>\",\n \"--input-file <path|->\",\n \"--yes\",\n \"--json\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"panels submit\",\n \"summary\": \"Send text plus terminal Enter to a terminal panel.\",\n \"arguments\": [\n \"--panel <panel-id>\",\n \"--text <text>\",\n \"--input-file <path|->\",\n \"--yes\",\n \"--json\",\n \"--pane-dir <path>\"\n ]\n },\n {\n \"name\": \"panels submit-composer\",\n \"summary\": \"Submit an agent composer with the correct key sequence, including Ctrl+Enter for Codex.\",\n \"arguments\": [\n \"--panel <panel-id>\",\n \"--strategy <auto|codex-ctrl-enter|enter>\",\n \"--yes\",\n \"--json\"\n ]\n },\n {\n \"name\": \"panels wait\",\n \"summary\": \"Wait for terminal initialized, ready, idle, or text state with compact output.\",\n \"arguments\": [\n \"--panel <panel-id>\",\n \"--for <condition>\",\n \"--contains <text>\",\n \"--timeout-ms <ms>\",\n \"--interval-ms <ms>\",\n \"--json\",\n \"--pane-dir <path>\"\n ]\n }\n ]\n },\n \"commands\": {\n \"help\": {\n \"name\": \"help\",\n \"summary\": \"Show help for runpane or a specific command.\",\n \"details\": \"Use this when you need human-readable usage text for a command.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"command\",\n \"value\": \"<command>\",\n \"required\": false,\n \"description\": \"Optional command topic such as \\\"panes create\\\".\"\n }\n ],\n \"examples\": [\n \"runpane help\",\n \"runpane help panes create\"\n ],\n \"notes\": [\n \"Help text is generated from the runpane contract.\"\n ]\n },\n \"setup\": {\n \"name\": \"setup\",\n \"summary\": \"Open the guided setup wizard for install, remote host setup, update, and diagnostics.\",\n \"details\": \"Use this for a human-driven Pane setup flow, not for unattended agent orchestration.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": false,\n \"arguments\": [],\n \"examples\": [\n \"runpane setup\"\n ],\n \"notes\": [\n \"In non-interactive shells, setup prints help and exits successfully.\"\n ]\n },\n \"install\": {\n \"name\": \"install\",\n \"summary\": \"Install Pane on this machine or configure this machine as a remote daemon host.\",\n \"details\": \"Installs or launches Pane release artifacts at command runtime. `install daemon` forwards remote setup flags to Pane.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"target\",\n \"value\": \"<client|daemon>\",\n \"required\": false,\n \"description\": \"Install the desktop client or configure a remote daemon host.\"\n },\n {\n \"name\": \"--version\",\n \"value\": \"<latest|vX.Y.Z>\",\n \"required\": false,\n \"description\": \"Pane release to install.\"\n },\n {\n \"name\": \"--format\",\n \"value\": \"<auto|appimage|deb|dmg|zip|exe>\",\n \"required\": false,\n \"description\": \"Release artifact format.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip interactive prompts where possible.\"\n }\n ],\n \"examples\": [\n \"runpane install client\",\n \"runpane install daemon --label \\\"My Server\\\"\"\n ],\n \"notes\": [\n \"Package install itself is inert; work begins only when runpane is executed.\"\n ]\n },\n \"update\": {\n \"name\": \"update\",\n \"summary\": \"Update the Pane desktop app using the same artifact path as install client.\",\n \"details\": \"Resolves and installs the selected Pane client release.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--version\",\n \"value\": \"<latest|vX.Y.Z>\",\n \"required\": false,\n \"description\": \"Pane release to update to.\"\n },\n {\n \"name\": \"--format\",\n \"value\": \"<auto|appimage|deb|dmg|zip|exe>\",\n \"required\": false,\n \"description\": \"Release artifact format.\"\n },\n {\n \"name\": \"--dry-run\",\n \"required\": false,\n \"description\": \"Print the update plan without downloading.\"\n }\n ],\n \"examples\": [\n \"runpane update\",\n \"runpane update --version latest\"\n ],\n \"notes\": [\n \"Equivalent artifact selection to `runpane install client`.\"\n ]\n },\n \"version\": {\n \"name\": \"version\",\n \"summary\": \"Print the runpane wrapper version without contacting, launching, or focusing Pane.\",\n \"details\": \"Use this to confirm which wrapper is available. App, daemon, and release diagnostics belong in doctor.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--pane-path\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Ignored for version; retained only for parser compatibility.\"\n }\n ],\n \"examples\": [\n \"runpane version\",\n \"runpane --version\"\n ],\n \"notes\": []\n },\n \"doctor\": {\n \"name\": \"doctor\",\n \"summary\": \"Run platform, release, installed Pane, daemon reachability, and remote setup diagnostics.\",\n \"details\": \"Use this first when an agent needs to understand whether Pane is installed, which wrapper/runtime is running, what daemon endpoint is expected, and whether the running Pane app is reachable. JSON mode should return a report even when the daemon is unreachable.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print a machine-readable environment report.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n },\n {\n \"name\": \"--pane-path\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Inspect a specific Pane executable.\"\n },\n {\n \"name\": \"--format\",\n \"value\": \"<format>\",\n \"required\": false,\n \"description\": \"Release artifact format to inspect.\"\n },\n {\n \"name\": \"--verbose\",\n \"required\": false,\n \"description\": \"Print extra diagnostics.\"\n }\n ],\n \"examples\": [\n \"runpane doctor --json\",\n \"runpane doctor\"\n ],\n \"jsonSchemas\": [\n \"doctorResult\"\n ],\n \"notes\": [\n \"Agents should run `runpane doctor --json` before mutating Pane state.\",\n \"The JSON report includes daemon reachability as data; an unreachable daemon is not a reason to skip the rest of the report.\",\n \"Use `runpane agent-context --json` after doctor when full CLI context is needed.\"\n ]\n },\n \"agent-context\": {\n \"name\": \"agent-context\",\n \"summary\": \"Print token-efficient Pane command context for coding agents.\",\n \"details\": \"Use this first when an agent needs to discover Pane primitives. It is local/offline and does not require a running Pane app.\",\n \"requiresPaneDaemon\": false,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--command\",\n \"value\": \"<command>\",\n \"required\": false,\n \"description\": \"Print the detailed definition for one command, for example \\\"panes create\\\".\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n }\n ],\n \"examples\": [\n \"runpane agent-context\",\n \"runpane agent-context --command \\\"panes create\\\" --json\"\n ],\n \"jsonSchemas\": [\n \"agentContextBriefResult\",\n \"agentContextCommandResult\"\n ],\n \"notes\": [\n \"Default output is brief so AGENTS.md can point here without bloating context.\",\n \"`--command` accepts canonical spaced names and common copied forms, including `panes.create` and `runpane panes create`.\"\n ]\n },\n \"repos list\": {\n \"name\": \"repos list\",\n \"summary\": \"List repositories saved in the running Pane app.\",\n \"details\": \"Use this to find the right Pane-managed repository before creating panes. If the repo is missing, use `repos add` first.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable repository records.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane repos list --json\"\n ],\n \"jsonSchemas\": [\n \"repoListResult\"\n ],\n \"notes\": [\n \"Requires a running Pane app or daemon for the selected Pane data directory.\",\n \"If this fails from WSL with a missing `/tmp/pane-daemon.../daemon.sock`, `volta: command not found`, or a Windows shim error, the user may be running Windows Pane. Retry via `powershell.exe -NoProfile -Command 'Set-Location $env:TEMP; runpane repos list --json'`.\",\n \"When using PowerShell from WSL, set a Windows cwd such as `$env:TEMP` or `$env:USERPROFILE` before running runpane to avoid UNC cwd issues.\"\n ]\n },\n \"repos add\": {\n \"name\": \"repos add\",\n \"summary\": \"Register an existing git repository with the running Pane app.\",\n \"details\": \"Use this when the repository exists on disk but is not saved in Pane yet. It validates the path as a git repository.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--path\",\n \"value\": \"<path>\",\n \"required\": true,\n \"description\": \"Existing git repository path to register with Pane.\"\n },\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"required\": false,\n \"description\": \"Saved repository name; defaults to the directory name.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for mutating commands.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--dry-run\",\n \"required\": false,\n \"description\": \"Validate and preview without adding the repo.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane repos add --path /path/to/repo --yes --json\"\n ],\n \"jsonSchemas\": [\n \"repoAddRequest\",\n \"repoAddResult\"\n ],\n \"notes\": [\n \"It does not create directories or initialize git repositories by default.\",\n \"For a Windows Pane app managing WSL repositories, prefer a saved WSL repo from `repos list` when possible. Adding a brand-new WSL repo from Windows may require WSL-aware path handling.\"\n ]\n },\n \"panes list\": {\n \"name\": \"panes list\",\n \"summary\": \"List Pane sessions, optionally scoped to a saved repository.\",\n \"details\": \"Use this after selecting a repository to find existing Pane sessions and their ids before inspecting panels.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--repo\",\n \"value\": \"<selector>\",\n \"required\": false,\n \"description\": \"Optional repository selector: active, id, exact path, or saved repository name.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panes list --repo active --json\"\n ],\n \"jsonSchemas\": [\n \"paneListResult\"\n ],\n \"notes\": [\n \"Without --repo, this lists sessions across saved Pane repositories.\"\n ]\n },\n \"panes create\": {\n \"name\": \"panes create\",\n \"summary\": \"Create user-visible Panes (Pane sessions) backed by Pane-managed worktrees for feature/PR work and open terminal-backed tool tabs.\",\n \"details\": \"Use this when the user wants separate visible Panes (Pane sessions) for feature or PR work. Select the saved base repository, choose a built-in agent or custom terminal command, and optionally send initial input after the tool starts. Pane creates and owns a git worktree/branch for each new Pane; do not pre-create a git worktree and register it as a separate repo unless the user explicitly asks. For private background decomposition, use your normal subagent/worktree mechanism instead of Pane.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--repo\",\n \"value\": \"<selector>\",\n \"required\": true,\n \"description\": \"Repository selector: active, id, exact path, or saved repository name.\"\n },\n {\n \"name\": \"--name\",\n \"value\": \"<name>\",\n \"required\": true,\n \"description\": \"Pane/session name.\"\n },\n {\n \"name\": \"--agent\",\n \"value\": \"<codex|claude>\",\n \"required\": false,\n \"description\": \"Built-in agent terminal template to open.\"\n },\n {\n \"name\": \"--tool-command\",\n \"value\": \"<command>\",\n \"required\": false,\n \"description\": \"Custom terminal command to run instead of a built-in agent.\"\n },\n {\n \"name\": \"--prompt\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Alias for --initial-input; sends text after the command is ready.\"\n },\n {\n \"name\": \"--initial-input-file\",\n \"value\": \"<path|->\",\n \"required\": false,\n \"description\": \"Read initial input from a file or stdin.\"\n },\n {\n \"name\": \"--from-json\",\n \"value\": \"<path|->\",\n \"required\": false,\n \"description\": \"Read a full panes.create request JSON payload.\"\n },\n {\n \"name\": \"--source\",\n \"value\": \"<user|agent>\",\n \"required\": false,\n \"description\": \"Mutation source; agent implies background/no-focus creation.\"\n },\n {\n \"name\": \"--no-focus\",\n \"required\": false,\n \"description\": \"Create the pane in the background without stealing focus.\"\n },\n {\n \"name\": \"--focus\",\n \"required\": false,\n \"description\": \"Explicitly focus the created pane.\"\n },\n {\n \"name\": \"--pinned\",\n \"required\": false,\n \"description\": \"Create the pane already pinned (the Pane UI's favorite/pin star); does not imply focus.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for mutating commands.\"\n },\n {\n \"name\": \"--dry-run\",\n \"required\": false,\n \"description\": \"Validate and preview without mutating.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--wait-ready\",\n \"required\": false,\n \"description\": \"Wait for each created terminal panel to be ready before returning.\"\n },\n {\n \"name\": \"--ready-timeout-ms\",\n \"value\": \"<ms>\",\n \"required\": false,\n \"description\": \"Readiness timeout per pane; defaults to 30000.\"\n },\n {\n \"name\": \"--concurrency\",\n \"value\": \"<count>\",\n \"required\": false,\n \"description\": \"Accepted for compatibility. Pane currently serializes multi-pane session creation so queued jobs do not time out before starting.\"\n }\n ],\n \"examples\": [\n \"runpane panes create --repo active --name issue-257 --agent <agent> --prompt \\\"Plan this issue\\\" --source agent --no-focus --wait-ready --yes --json\",\n \"runpane panes create --from-json panes.json --yes --json\",\n \"runpane panes create --repo active --name issue-123 --agent <agent> --prompt \\\"Plan this issue\\\" --source agent --no-focus --wait-ready --yes --json\"\n ],\n \"jsonSchemas\": [\n \"paneCreateRequest\",\n \"paneCreateResult\"\n ],\n \"notes\": [\n \"At least one of --agent or --tool-command is required unless --from-json is used.\",\n \"`panes create` is for user-visible Pane orchestration, not the agent's default private delegation mechanism.\",\n \"Register the saved base repository once. Pane creates and owns the worktree/branch for each new Pane.\",\n \"Use `panels create` instead when a reviewer/helper should share an existing Pane's worktree.\",\n \"Agent-created Panes should pass `--source agent --no-focus --wait-ready --yes --json` unless the user explicitly wants focus moved; add `--pinned` when the Pane should remain in the UI's favorite/pin set.\",\n \"The built-in agent templates come from the runpane contract; custom terminal commands can pass agent-specific flags when requested by the user.\",\n \"Use --initial-input-file for multi-line prompts or shell-sensitive initial input.\",\n \"When the JSON result includes nextCommand, run it to validate that the terminal produced output before reporting success.\",\n \"Multi-pane requests are created sequentially today. The --concurrency flag is accepted for compatibility, but agents should not rely on parallel creation.\",\n \"For POSIX or WSL command chaining, use a custom terminal command like `bash -lc 'cmd1 && cmd2 && cmd3'`.\",\n \"For Windows PowerShell command chaining, use a custom terminal command like `powershell -NoProfile -Command \\\"cmd1; if ($LASTEXITCODE) { exit $LASTEXITCODE }; cmd2\\\"`.\",\n \"From WSL with Windows Pane, invoke through PowerShell and select the saved WSL repo by name or id, for example `powershell.exe -NoProfile -Command 'Set-Location $env:TEMP; runpane panes create --repo \\\"WSL Pane\\\" --name issue-123 --agent <agent> --prompt \\\"Plan this issue\\\" --source agent --no-focus --wait-ready --yes --json'`.\",\n \"Use --wait-ready when an agent needs to verify that an agent terminal started instead of only creating a pane.\",\n \"If readiness returns blocked, inspect blocked.suggestedCommand rather than guessing which prompt to answer.\"\n ]\n },\n \"panes archive\": {\n \"name\": \"panes archive\",\n \"summary\": \"Archive a Pane (session) exactly like the UI Archive action, including safe removal of its Pane-managed git worktree.\",\n \"details\": \"Use this to close out a Pane once its PR has merged. Refuses to archive (unless --force) when the pane's branch has uncommitted, untracked, or unpushed-to-remote changes, so an agent cannot silently discard work. A merged and pushed branch has no unpushed commits, so it archives cleanly. Waits for worktree removal to finish before returning.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane/session id to archive.\"\n },\n {\n \"name\": \"--source\",\n \"value\": \"<user|agent>\",\n \"required\": false,\n \"description\": \"Mutation source; does not change archive safety behavior.\"\n },\n {\n \"name\": \"--force\",\n \"required\": false,\n \"description\": \"Archive even if the pane's branch has uncommitted, untracked, or unpushed changes.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for mutating commands.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n }\n ],\n \"examples\": [\n \"runpane panes archive --pane <pane-id> --source agent --yes --json\",\n \"runpane panes archive --pane <pane-id> --force --yes --json\"\n ],\n \"jsonSchemas\": [\n \"paneArchiveRequest\",\n \"paneArchiveResult\"\n ],\n \"notes\": [\n \"If the result has ok:false with a blocked field, the pane was NOT archived; inspect blocked.code and rerun with --force if discarding the flagged work is intentional.\",\n \"A successful archive waits for the Pane-managed worktree to be removed before returning; check worktreeCleanup in the result for the final outcome.\",\n \"Archiving a main-repo Pane (no Pane-managed worktree) always succeeds immediately since nothing is deleted from disk.\"\n ]\n },\n \"panes pin\": {\n \"name\": \"panes pin\",\n \"summary\": \"Declaratively pin a Pane; pinned is the Pane UI's favorite/pin star.\",\n \"details\": \"Sets the requested pinned state to true without reading and toggling it. Repeating the command is idempotent and pinning never changes focus.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane/session id to pin.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for mutating commands.\"\n },\n {\n \"name\": \"--dry-run\",\n \"required\": false,\n \"description\": \"Validate and preview without mutating.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n }\n ],\n \"examples\": [\n \"runpane panes pin --pane <pane-id> --yes --json\",\n \"runpane panes pin --pane <pane-id> --dry-run --json\"\n ],\n \"jsonSchemas\": [\n \"panePinRequest\",\n \"panePinResult\"\n ],\n \"notes\": [\n \"This is a declarative set, not a toggle: retries leave an already-pinned Pane pinned.\",\n \"Pinning does not focus the Pane.\"\n ]\n },\n \"panes unpin\": {\n \"name\": \"panes unpin\",\n \"summary\": \"Declaratively unpin a Pane; pinned is the Pane UI's favorite/pin star.\",\n \"details\": \"Sets the requested pinned state to false without reading and toggling it. Repeating the command is idempotent and unpinning never changes focus.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane/session id to unpin.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for mutating commands.\"\n },\n {\n \"name\": \"--dry-run\",\n \"required\": false,\n \"description\": \"Validate and preview without mutating.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n }\n ],\n \"examples\": [\n \"runpane panes unpin --pane <pane-id> --yes --json\",\n \"runpane panes unpin --pane <pane-id> --dry-run --json\"\n ],\n \"jsonSchemas\": [\n \"panePinRequest\",\n \"panePinResult\"\n ],\n \"notes\": [\n \"This is a declarative set, not a toggle: retries leave an already-unpinned Pane unpinned.\",\n \"Unpinning does not focus the Pane.\"\n ]\n },\n \"panels list\": {\n \"name\": \"panels list\",\n \"summary\": \"List tool panels inside a Pane session.\",\n \"details\": \"Use this to find the terminal panel id to inspect or control after creating or selecting a Pane session.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Pane/session id.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panels list --pane <pane-id> --json\"\n ],\n \"jsonSchemas\": [\n \"panelListResult\"\n ],\n \"notes\": [\n \"The ids returned here are stable inputs for `panels output` and `panels input`.\"\n ]\n },\n \"panels output\": {\n \"name\": \"panels output\",\n \"summary\": \"Read recent terminal output from a panel.\",\n \"details\": \"Use this to inspect recent terminal output from a terminal-backed panel without loading the full history by default. Live terminal scrollback is returned as bounded recent lines; persisted output fallback is returned as bounded records. Terminal control noise is stripped before returning text.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": true,\n \"description\": \"Tool panel id.\"\n },\n {\n \"name\": \"--limit\",\n \"value\": \"<count>\",\n \"required\": false,\n \"description\": \"Maximum recent output lines or records to read. Defaults to 200.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panels output --panel <panel-id> --limit 200 --json\"\n ],\n \"jsonSchemas\": [\n \"panelOutputResult\"\n ],\n \"notes\": [\n \"Default output is bounded to the latest 200 lines or records. Use a larger --limit only when hasMore is true and more history is needed.\",\n \"Use --json when an agent needs timestamps, record types, returnedCount, or hasMore.\",\n \"The output is intended to be agent-readable, but terminal prompts and shell echoes may still appear; use the newest relevant lines before concluding success.\",\n \"Prefer `panels screen` for a smaller current-state read before increasing output limits.\"\n ]\n },\n \"panels input\": {\n \"name\": \"panels input\",\n \"summary\": \"Send input bytes to a terminal panel.\",\n \"details\": \"Use this to answer prompts or continue an agent inside an existing Pane terminal panel. Input is byte-oriented; choose --input-file for exact control over newlines and control characters.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": true,\n \"description\": \"Terminal panel id.\"\n },\n {\n \"name\": \"--text\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Text bytes to send.\"\n },\n {\n \"name\": \"--input-file\",\n \"value\": \"<path|->\",\n \"required\": false,\n \"description\": \"Read input from a file or stdin.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for this mutating command.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"printf 'Continue\\\\n' | runpane panels input --panel <panel-id> --input-file - --yes\",\n \"printf '\\\\003' | runpane panels input --panel <panel-id> --input-file - --yes\",\n \"runpane panels input --panel <panel-id> --text \\\"simple text\\\" --yes --json\"\n ],\n \"jsonSchemas\": [\n \"panelInputRequest\",\n \"panelInputResult\"\n ],\n \"notes\": [\n \"Input is sent exactly as provided. Include a real newline byte when the terminal should receive Enter; across shells, `--input-file` is safer than `--text \\\"...\\\\n\\\"`.\",\n \"Use `--input-file -` or a temp file for multi-line input, quotes, Ctrl-C, or shell-sensitive text.\",\n \"If interrupting a running process, send Ctrl-C first, validate/read output, then send the next command in a separate `panels input` call so bytes are not dropped.\",\n \"After sending input, validate with `runpane panels output --panel <panel-id> --json` before reporting success.\",\n \"Runpane records action metadata and errors for observability, but should not log full input text by default.\",\n \"For ordinary text plus Enter, prefer `panels submit` so the terminal receives a CR Enter byte.\"\n ]\n },\n \"agents doctor\": {\n \"name\": \"agents doctor\",\n \"summary\": \"Diagnose whether a built-in agent command is available in a Pane repository environment.\",\n \"details\": \"Use this before creating Codex or Claude panes when PATH may differ between macOS/Linux/Windows/WSL or between the wrapper shell and Pane. The check runs through Pane project context, not the wrapper process.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--agent\",\n \"value\": \"<codex|claude>\",\n \"required\": true,\n \"description\": \"Built-in agent command to diagnose.\"\n },\n {\n \"name\": \"--repo\",\n \"value\": \"<selector>\",\n \"required\": false,\n \"description\": \"Repository selector; defaults to the active Pane repo.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane agents doctor --agent <agent> --repo active --json\"\n ],\n \"jsonSchemas\": [\n \"agentDoctorResult\"\n ],\n \"notes\": [\n \"For WSL repos, install the agent inside the WSL distro Pane uses, not only on Windows.\",\n \"This diagnoses built-in agent templates only; custom commands should be validated by creating a pane and reading its screen.\"\n ]\n },\n \"panels screen\": {\n \"name\": \"panels screen\",\n \"summary\": \"Read a compact current-screen view from a terminal panel.\",\n \"details\": \"Use this for token-safe current terminal state. It prefers active alternate-screen/TUI output, then live scrollback, then persisted output. Default output is bounded to 80 lines.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": true,\n \"description\": \"Terminal panel id.\"\n },\n {\n \"name\": \"--limit\",\n \"value\": \"<count>\",\n \"required\": false,\n \"description\": \"Maximum lines to return; defaults to 80.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panels screen --panel <panel-id> --limit 80 --json\"\n ],\n \"jsonSchemas\": [\n \"panelScreenResult\"\n ],\n \"notes\": [\n \"Use this before `panels output` when an agent only needs the latest visible/current state.\",\n \"If hasMore is true and context is missing, rerun with a larger --limit or use `panels output`.\"\n ]\n },\n \"panels submit\": {\n \"name\": \"panels submit\",\n \"summary\": \"Send text to a terminal panel and append a terminal Enter byte.\",\n \"details\": \"Use this for ordinary interactive submissions. The daemon normalizes a final LF or CRLF to CR, or appends CR if no newline is present. Exact byte workflows remain on `panels input`.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": true,\n \"description\": \"Terminal panel id.\"\n },\n {\n \"name\": \"--text\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Text to submit before Enter.\"\n },\n {\n \"name\": \"--input-file\",\n \"value\": \"<path|->\",\n \"required\": false,\n \"description\": \"Read text from a file or stdin before Enter.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for this mutating command.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panels submit --panel <panel-id> --text \\\"2\\\" --yes --json\",\n \"printf \\\"echo hello\\\" | runpane panels submit --panel <panel-id> --input-file - --yes --json\"\n ],\n \"jsonSchemas\": [\n \"panelSubmitRequest\",\n \"panelSubmitResult\"\n ],\n \"notes\": [\n \"The response includes nextCommand for validation. Run it before reporting that the input worked.\",\n \"Use `panels input` for Ctrl-C, escape sequences, or any workflow requiring exact bytes.\"\n ]\n },\n \"panels wait\": {\n \"name\": \"panels wait\",\n \"summary\": \"Wait for a terminal panel to initialize, become ready/idle, or contain text.\",\n \"details\": \"Use this to validate asynchronous terminal behavior without pulling large scrollback. It returns brief readiness state, blocker hints, a compact screen, and a next command.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": false,\n \"arguments\": [\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": true,\n \"description\": \"Terminal panel id.\"\n },\n {\n \"name\": \"--for\",\n \"value\": \"<initialized|ready|idle|text>\",\n \"required\": false,\n \"description\": \"Condition to wait for. Defaults to ready for CLI panels and idle otherwise.\"\n },\n {\n \"name\": \"--contains\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Text required for --for text; implies --for text when --for is omitted.\"\n },\n {\n \"name\": \"--timeout-ms\",\n \"value\": \"<ms>\",\n \"required\": false,\n \"description\": \"Wait timeout; defaults to 30000.\"\n },\n {\n \"name\": \"--interval-ms\",\n \"value\": \"<ms>\",\n \"required\": false,\n \"description\": \"Polling interval; defaults to 500.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n },\n {\n \"name\": \"--pane-dir\",\n \"value\": \"<path>\",\n \"required\": false,\n \"description\": \"Connect to a specific Pane data directory.\"\n }\n ],\n \"examples\": [\n \"runpane panels wait --panel <panel-id> --for ready --timeout-ms 30000 --json\",\n \"runpane panels wait --panel <panel-id> --contains \\\"Ready\\\" --json\"\n ],\n \"jsonSchemas\": [\n \"panelWaitResult\"\n ],\n \"notes\": [\n \"If blocked is present, do not assume success. Use blocked.suggestedCommand or inspect `panels screen`.\",\n \"The default timeout and screen are intentionally small for agent context safety.\"\n ]\n },\n \"panels create\": {\n \"name\": \"panels create\",\n \"summary\": \"Create a reviewer/helper terminal tab inside an existing Pane.\",\n \"details\": \"Use this to add reviewer, helper, or clean-context tabs to an existing Pane. The new panel shares the existing Pane's worktree and branch; it does not create a separate Pane session. Use `panes create` instead when the user wants a separate visible Pane (Pane session) for feature/PR work. Agent/orchestrator-created panels should pass --source agent or --no-focus so the user stays in the implementation tab. The daemon initializes the terminal immediately and can wait for CLI readiness.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--pane\",\n \"value\": \"<pane-id>\",\n \"required\": true,\n \"description\": \"Existing Pane session id to add the panel to.\"\n },\n {\n \"name\": \"--agent\",\n \"value\": \"<codex|claude>\",\n \"required\": false,\n \"description\": \"Built-in agent command template to launch.\"\n },\n {\n \"name\": \"--tool-command\",\n \"value\": \"<command>\",\n \"required\": false,\n \"description\": \"Custom terminal command to launch instead of a built-in agent.\"\n },\n {\n \"name\": \"--title\",\n \"value\": \"<title>\",\n \"required\": false,\n \"description\": \"Panel title override.\"\n },\n {\n \"name\": \"--initial-input\",\n \"value\": \"<text>\",\n \"required\": false,\n \"description\": \"Initial input to send after the tool starts.\"\n },\n {\n \"name\": \"--initial-input-file\",\n \"value\": \"<path|->\",\n \"required\": false,\n \"description\": \"Read initial input from a file or stdin.\"\n },\n {\n \"name\": \"--source\",\n \"value\": \"<user|agent>\",\n \"required\": false,\n \"description\": \"Mutation source; agent implies background creation.\"\n },\n {\n \"name\": \"--no-focus\",\n \"required\": false,\n \"description\": \"Create the panel in the background without changing active panel.\"\n },\n {\n \"name\": \"--focus\",\n \"required\": false,\n \"description\": \"Explicitly focus the created panel.\"\n },\n {\n \"name\": \"--wait-ready\",\n \"required\": false,\n \"description\": \"Wait until the terminal tool is ready.\"\n },\n {\n \"name\": \"--ready-timeout-ms\",\n \"value\": \"<ms>\",\n \"required\": false,\n \"description\": \"Readiness wait timeout.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for this mutating command.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n }\n ],\n \"examples\": [\n \"runpane panels create --pane <pane-id> --agent <agent> --source agent --no-focus --wait-ready --yes --json\",\n \"runpane panels create --pane <pane-id> --tool-command <command> --title <title> --source agent --no-focus --wait-ready --yes --json\"\n ],\n \"jsonSchemas\": [\n \"panelCreateRequest\",\n \"panelCreateResult\"\n ],\n \"notes\": [\n \"Use this for same-pane reviewer loops after PR creation/testing.\",\n \"Panels share the existing Pane's worktree and branch.\",\n \"`panels create` is for visible helper/reviewer tabs, not the agent's default private delegation mechanism.\",\n \"For agent-created panels, prefer --source agent or --no-focus to avoid stealing focus from the user or implementation tab.\"\n ]\n },\n \"panels submit-composer\": {\n \"name\": \"panels submit-composer\",\n \"summary\": \"Submit an agent composer using the panel-appropriate key sequence.\",\n \"details\": \"Use this after sending text into an agent composer when plain Enter is not the right submit action. Agents should use --strategy auto; Pane owns per-agent submit sequences internally and verifies visible composer state when possible.\",\n \"requiresPaneDaemon\": true,\n \"mutates\": true,\n \"arguments\": [\n {\n \"name\": \"--panel\",\n \"value\": \"<panel-id>\",\n \"required\": true,\n \"description\": \"Terminal panel id.\"\n },\n {\n \"name\": \"--strategy\",\n \"value\": \"<auto|codex-ctrl-enter|enter>\",\n \"required\": false,\n \"description\": \"Composer submit key sequence strategy.\"\n },\n {\n \"name\": \"--yes\",\n \"required\": false,\n \"description\": \"Skip confirmation for this mutating command.\"\n },\n {\n \"name\": \"--json\",\n \"required\": false,\n \"description\": \"Print machine-readable output.\"\n }\n ],\n \"examples\": [\n \"runpane panels submit-composer --panel <codex-panel-id> --strategy auto --yes --json\",\n \"runpane panels submit-composer --panel <panel-id> --strategy enter --yes --json\"\n ],\n \"jsonSchemas\": [\n \"panelSubmitComposerRequest\",\n \"panelSubmitComposerResult\"\n ],\n \"notes\": [\n \"Use `panels input` or `panels submit` to write prompt text first; this command only submits the current composer.\",\n \"Use --strategy auto for agent workflows; explicit strategies are diagnostic escape hatches.\",\n \"The JSON result includes sequenceName and verifiedSubmitted. If ok is false, follow blocked.suggestedCommand instead of assuming submission happened.\"\n ]\n }\n },\n \"managedBlock\": [\n \"## Pane\",\n \"\",\n \"The developer is using Pane for this repository. Pane can manage saved repositories and create user-visible Panes with terminal-backed tools for planning, discussion, implementation, and review work.\",\n \"\",\n \"This managed guidance was created by [runpane.com](https://runpane.com) for the [Pane repository](https://github.com/dcouple/Pane). Do not delete or overwrite this block; the developer uses it for their workflow and it should remain committed to the main repository unless they explicitly ask to remove it.\",\n \"\",\n \"Pane mental model: a repository is the saved base repo; a Pane is a user-visible feature/PR workspace (Pane session) that normally maps to one Pane-managed git worktree and branch; a panel/tab is a terminal inside one Pane and shares that Pane's worktree; an agent is the CLI process running in a panel.\",\n \"\",\n \"Default happy path when the user asks you to use Pane or RunPane: run `runpane doctor --json`; read `runpane agent-context --json`; resolve the saved base repository with `runpane repos list --json` or add it once with `runpane repos add --path <repo> --yes --json`; create one visible Pane (Pane session) for the requested feature/PR with a complete command such as `runpane panes create --repo <repo> --name <name> --agent <agent> --prompt \\\"<task>\\\" --source agent --no-focus --wait-ready --yes --json` or the equivalent `--tool-command <command>` form; then validate with `runpane panels wait` or `runpane panels screen` before reporting progress.\",\n \"\",\n \"Use Pane when the user wants visible Panes or co-drivable parallel feature/PR workspaces. Do not use Pane as your default private delegation mechanism; for private background decomposition, use your normal subagent/worktree workflow.\",\n \"\",\n \"Register the main/base repository once. Do not register pre-created git worktrees as separate Pane repositories unless the user explicitly asks.\",\n \"\",\n \"Use `runpane panes create` for separate visible Panes (Pane sessions) for feature/PR work. Use `runpane panels create` for reviewer/helper tabs inside an existing Pane that should share that Pane's worktree.\",\n \"\",\n \"Typical workflow: register the saved base repository once; create one Pane (Pane session) per feature/PR; use panels/tabs inside that Pane for helper or reviewer agents that should share the worktree; archive the Pane after the PR is done to remove it from active Panes and clean up its managed worktree when applicable.\",\n \"\",\n \"Skill routing reference: when the user says `discussion`, `plan`, `simple-plan`, `create-plan`, or `implement`, or asks for the behavior those words imply, treat three references as peer context: Pane's local skill cache under `<PANE_DIR>/skills/`, the Pane Chat orchestrator handoff at `<PANE_DIR>/skills/pane-chat/runpane-orchestrator.md` when present, and the [workflow map](https://github.com/dcouple/skills/raw/main/docs/readme-workflow-map.png).\",\n \"Use those peer references together to choose the phase: discuss/investigate until the work is clear enough to delegate, then ticket/plan/implement/review/PR-test/teach-back as appropriate. The orchestrator and workflow map may point to different skills; reconcile them with the user's request instead of hardcoding a skill list or treating one reference as subordinate.\",\n \"For the Pane implementation source of truth for where the skill cache, cached workflow assets, and Pane Chat bootstrap live, reference [PR #291](https://github.com/dcouple/Pane/pull/291): `main/src/services/skillCacheManager.ts` owns `<PANE_DIR>/skills/`, `.sources/dcouple-skills`, and `pane-chat/runpane-orchestrator.md`; `main/src/services/paneChatManager.ts` owns the tiny bootstrap prompt that tells the selected Pane Chat agent to read that guide.\",\n \"Use GitHub reads against the [Parsa skills folder](https://github.com/dcouple/skills/tree/main/parsa) only to inspect or refresh referenced skill files; do not clone/install the repo unless the user asks.\",\n \"Do not hardcode a specific assistant brand in workflow guidance. Use the Pane agent or custom tool command the user selected, and use `runpane agents doctor --agent <agent> --repo <selector> --json` only when checking a built-in agent template.\",\n \"\",\n \"Start with `runpane doctor --json` before taking Pane actions. Use it to understand wrapper/runtime details, daemon reachability, and the next safe commands.\",\n \"\",\n \"In a Pane repository checkout, if `runpane` is not on PATH, use the built local wrapper with Node 22: `PATH=/opt/homebrew/opt/node@22/bin:$PATH node packages/runpane/dist/cli.js doctor --json`.\",\n \"\",\n \"Use `runpane agent-context --json` for full Pane CLI context. Use `runpane agent-context --command \\\"panels wait\\\" --json` or another command name for detailed schema only when needed.\",\n \"\",\n \"Default to context-safe validation: after creating Panes or sending terminal input, run `runpane panels wait` or `runpane panels screen` before reporting success. Prefer `runpane panels submit` for normal text plus Enter; use `runpane panels input` only for exact bytes such as Ctrl-C or escape sequences.\",\n \"\",\n \"Common commands:\",\n \"- `runpane doctor --json`\",\n \"- `runpane agent-context --json`\",\n \"- `runpane repos list --json`\",\n \"- `runpane repos add --path <repo> --yes --json`\",\n \"- `runpane agents doctor --agent <agent> --repo active --json`\",\n \"- `runpane panes create --repo active --name <name> --agent <agent> --prompt \\\"<task>\\\" --source agent --no-focus --wait-ready --yes --json`\",\n \"- `runpane panels create --pane <pane-id> --agent <agent> --source agent --no-focus --wait-ready --yes --json`\",\n \"- `runpane panels list --pane <pane-id> --json`\",\n \"- `runpane panels screen --panel <panel-id> --limit 80 --json`\",\n \"- `runpane panels wait --panel <panel-id> --for ready --timeout-ms 30000 --json`\",\n \"- `runpane panels submit --panel <panel-id> --text \\\"<answer>\\\" --yes --json`\",\n \"- `runpane panels input --panel <panel-id> --input-file <path|-> --yes --json`\",\n \"\",\n \"WSL note: if `runpane doctor --json` cannot find `/tmp/pane-daemon.../daemon.sock` or `runpane` resolves to a broken Windows shim, Pane may be running on Windows. Try `powershell.exe -NoProfile -Command 'Set-Location $env:TEMP; runpane doctor --json'`, then create Panes through the same PowerShell form using the saved WSL repo name or id. Use `runpane agents doctor --agent <agent> --repo <selector> --json` to diagnose the repo environment Pane will actually use.\"\n ]\n }\n}") diff --git a/packages/runpane-py/src/runpane/local_control.py b/packages/runpane-py/src/runpane/local_control.py index c768e1a7..77e2fd41 100644 --- a/packages/runpane-py/src/runpane/local_control.py +++ b/packages/runpane-py/src/runpane/local_control.py @@ -99,6 +99,31 @@ def run_panes_archive(parsed: Any) -> int: return 0 if result.get("ok") else 1 +def run_panes_pin(parsed: Any, pinned: bool) -> int: + command = "pin" if pinned else "unpin" + if not parsed.pane_id: + raise ValueError(f"runpane panes {command} requires --pane.") + + request = { + "paneId": parsed.pane_id, + "pinned": pinned, + **optional_value("dryRun", True if parsed.dry_run else None), + } + confirm_pane_pin(parsed, request) + result = invoke_daemon( + "runpane:panes:pin", + [request], + pane_dir=parsed.pane_dir, + ) + + if parsed.json: + print_json(result) + else: + print(f"{'Pinned' if result.get('pinned') else 'Unpinned'} {result.get('paneId')}") + + return 0 + + def run_panels_list(parsed: Any) -> int: if not parsed.pane_id: raise ValueError("runpane panels list requires --pane.") @@ -319,6 +344,11 @@ def build_pane_create_request(parsed: Any) -> Dict[str, Any]: payload["readyTimeoutMs"] = parsed.ready_timeout_ms if parsed.concurrency is not None: payload["concurrency"] = parsed.concurrency + if parsed.pinned: + payload["panes"] = [ + {**item, "pinned": True} if isinstance(item, dict) else item + for item in payload.get("panes", []) + ] apply_pane_focus_options(parsed, payload) return payload @@ -335,6 +365,7 @@ def build_pane_create_request(parsed: Any) -> Dict[str, Any]: "name": parsed.name, **optional_value("worktreeName", parsed.worktree_name), **optional_value("baseBranch", parsed.base_branch), + **optional_value("pinned", True if parsed.pinned else None), "tool": build_tool_spec(parsed), }], **optional_value("dryRun", True if parsed.dry_run else None), @@ -432,6 +463,19 @@ def confirm_pane_archive(parsed: Any, request: Dict[str, Any]) -> None: raise ValueError("Cancelled.") +def confirm_pane_pin(parsed: Any, request: Dict[str, Any]) -> None: + if parsed.dry_run or parsed.yes: + return + command = "pin" if request.get("pinned") else "unpin" + if not is_interactive_shell(): + raise ValueError(f"runpane panes {command} mutates Pane state. Rerun with --yes in non-interactive shells.") + + action = "Pin" if request.get("pinned") else "Unpin" + answer = input(f"{action} pane {request.get('paneId')}? [y/N] ").strip().lower() + if answer not in {"y", "yes"}: + raise ValueError("Cancelled.") + + def confirm_panel_create(parsed: Any, request: Dict[str, Any]) -> None: if parsed.yes: return @@ -529,7 +573,8 @@ def print_pane_list_result(result: Dict[str, Any]) -> None: for pane in panes: repo = f" {pane.get('repoName')}" if pane.get("repoName") else "" - print(f"{pane.get('id')}\t{pane.get('name')}\t{pane.get('status')}\t{pane.get('panelCount')} panels\t{pane.get('worktreePath')}{repo}") + pinned = " pinned" if pane.get("pinned") else "" + print(f"{pane.get('id')}\t{pane.get('name')}\t{pane.get('status')}{pinned}\t{pane.get('panelCount')} panels\t{pane.get('worktreePath')}{repo}") def print_pane_create_result(result: Dict[str, Any]) -> None: diff --git a/packages/runpane/src/cli.ts b/packages/runpane/src/cli.ts index 393448d6..8d5719a9 100644 --- a/packages/runpane/src/cli.ts +++ b/packages/runpane/src/cli.ts @@ -26,6 +26,7 @@ import { runPanesArchive, runPanesCreate, runPanesList, + runPanesPin, runReposAdd, runReposList } from './localControl'; @@ -109,6 +110,14 @@ async function dispatchParsedCommand(parsed: ParsedArgs, telemetryContext: Wrapp return runPanesArchive(parsed); } + if (parsed.command === 'panes pin') { + return runPanesPin(parsed, true); + } + + if (parsed.command === 'panes unpin') { + return runPanesPin(parsed, false); + } + if (parsed.command === 'panels list') { return runPanelsList(parsed); } diff --git a/packages/runpane/src/commands.ts b/packages/runpane/src/commands.ts index a6df49fd..9a2695eb 100644 --- a/packages/runpane/src/commands.ts +++ b/packages/runpane/src/commands.ts @@ -50,6 +50,7 @@ export interface ParsedArgs { source?: string; noFocus?: boolean; focus?: boolean; + pinned?: boolean; composerStrategy?: string; force?: boolean; remoteSetupArgs: string[]; @@ -262,6 +263,10 @@ function parseLocalBooleanFlag(flag: string, parsed: ParsedArgs): void { parsed.focus = true; return; } + if (flag === '--pinned') { + parsed.pinned = true; + return; + } if (flag === '--force') { parsed.force = true; return; @@ -414,6 +419,8 @@ function isRunpaneLocalCommand(command: RunpaneCommand): boolean { || command === 'panes list' || command === 'panes create' || command === 'panes archive' + || command === 'panes pin' + || command === 'panes unpin' || command === 'panels create' || command === 'panels list' || command === 'panels output' diff --git a/packages/runpane/src/generated/contract.ts b/packages/runpane/src/generated/contract.ts index c541354a..cf77ea16 100644 --- a/packages/runpane/src/generated/contract.ts +++ b/packages/runpane/src/generated/contract.ts @@ -196,6 +196,30 @@ export const RUNPANE_CONTRACT = { "paneArchiveResult" ] }, + { + "name": "panes pin", + "summary": "Declaratively pin a Pane; pinned is the Pane UI's favorite/pin star and repeated requests are idempotent.", + "usage": [ + "runpane panes pin --pane <pane-id> --yes [--dry-run] [--json]" + ], + "mutates": true, + "jsonSchemas": [ + "panePinRequest", + "panePinResult" + ] + }, + { + "name": "panes unpin", + "summary": "Declaratively unpin a Pane; pinned is the Pane UI's favorite/pin star and repeated requests are idempotent.", + "usage": [ + "runpane panes unpin --pane <pane-id> --yes [--dry-run] [--json]" + ], + "mutates": true, + "jsonSchemas": [ + "panePinRequest", + "panePinResult" + ] + }, { "name": "panels create", "summary": "Create a terminal-backed tool panel inside an existing Pane session.", @@ -522,6 +546,10 @@ export const RUNPANE_CONTRACT = { "name": "--focus", "description": "Explicitly focus the created pane or panel." }, + { + "name": "--pinned", + "description": "Create the pane already pinned (the UI's favorite/pin star)." + }, { "name": "--force", "description": "Archive even if the pane's branch has uncommitted, untracked, or unpushed changes." @@ -690,8 +718,10 @@ export const RUNPANE_CONTRACT = { " runpane panes list [--repo <selector>] [--json]", " runpane panes create --repo <selector> --name <name> --agent <codex|claude> [options]", " runpane panes archive --pane <pane-id> [--force] [--source user|agent] --yes [--json]", + " runpane panes pin --pane <pane-id> --yes [--dry-run] [--json]", + " runpane panes unpin --pane <pane-id> --yes [--dry-run] [--json]", "", - "Run \"runpane help panes list\", \"runpane help panes create\", or \"runpane help panes archive\" for command-specific options." + "Run \"runpane help panes <command>\" for command-specific options." ], "panes list": [ "Usage:", @@ -730,6 +760,7 @@ export const RUNPANE_CONTRACT = { " --source <user|agent> Mark mutation source; agent implies background creation", " --no-focus Create in the background without stealing focus", " --focus Explicitly focus the created pane", + " --pinned Create already pinned (the UI's favorite/pin star)", " --pane-dir <path> Connect to a specific Pane data directory", " --json Print machine-readable output", " --dry-run Validate and preview without creating panes", @@ -749,6 +780,32 @@ export const RUNPANE_CONTRACT = { " --json Print machine-readable output", " --yes Skip confirmation for mutating commands" ], + "panes pin": [ + "Usage:", + " runpane panes pin --pane <pane-id> --yes [--dry-run] [--json]", + "", + "Declaratively pins a Pane using the Pane UI's favorite/pin star. Repeating the command is idempotent and never changes focus. Use --dry-run to validate and preview without mutating.", + "", + "Options:", + " --pane <pane-id> Pane/session id to pin", + " --pane-dir <path> Connect to a specific Pane data directory", + " --json Print machine-readable output", + " --dry-run Validate and preview without pinning the pane", + " --yes Skip confirmation for mutating commands" + ], + "panes unpin": [ + "Usage:", + " runpane panes unpin --pane <pane-id> --yes [--dry-run] [--json]", + "", + "Declaratively unpins a Pane using the Pane UI's favorite/pin star. Repeating the command is idempotent and never changes focus. Use --dry-run to validate and preview without mutating.", + "", + "Options:", + " --pane <pane-id> Pane/session id to unpin", + " --pane-dir <path> Connect to a specific Pane data directory", + " --json Print machine-readable output", + " --dry-run Validate and preview without unpinning the pane", + " --yes Skip confirmation for mutating commands" + ], "panels": [ "Terminal-backed panel commands.", "", @@ -1059,8 +1116,10 @@ export const RUNPANE_CONTRACT = { " runpane panes list [--repo <selector>] [--json]", " runpane panes create --repo <selector> --name <name> --agent <codex|claude> [options]", " runpane panes archive --pane <pane-id> [--force] [--source user|agent] --yes [--json]", + " runpane panes pin --pane <pane-id> --yes [--dry-run] [--json]", + " runpane panes unpin --pane <pane-id> --yes [--dry-run] [--json]", "", - "Run \"runpane help panes list\", \"runpane help panes create\", or \"runpane help panes archive\" for command-specific options." + "Run \"runpane help panes <command>\" for command-specific options." ], "panes list": [ "Usage:", @@ -1099,6 +1158,7 @@ export const RUNPANE_CONTRACT = { " --source <user|agent> Mark mutation source; agent implies background creation", " --no-focus Create in the background without stealing focus", " --focus Explicitly focus the created pane", + " --pinned Create already pinned (the UI's favorite/pin star)", " --pane-dir <path> Connect to a specific Pane data directory", " --json Print machine-readable output", " --dry-run Validate and preview without creating panes", @@ -1118,6 +1178,32 @@ export const RUNPANE_CONTRACT = { " --json", " --yes" ], + "panes pin": [ + "Usage:", + " runpane panes pin --pane <pane-id> --yes [--dry-run] [--json]", + "", + "Declaratively pins a Pane using the Pane UI's favorite/pin star. Repeating the command is idempotent and never changes focus. Use --dry-run to validate and preview without mutating.", + "", + "Options:", + " --pane <pane-id>", + " --pane-dir <path>", + " --json", + " --dry-run", + " --yes" + ], + "panes unpin": [ + "Usage:", + " runpane panes unpin --pane <pane-id> --yes [--dry-run] [--json]", + "", + "Declaratively unpins a Pane using the Pane UI's favorite/pin star. Repeating the command is idempotent and never changes focus. Use --dry-run to validate and preview without mutating.", + "", + "Options:", + " --pane <pane-id>", + " --pane-dir <path>", + " --json", + " --dry-run", + " --yes" + ], "panels": [ "Terminal-backed panel commands.", "", @@ -1513,6 +1599,7 @@ export const RUNPANE_CONTRACT = { "Kick off discussion", "--source", "agent", + "--pinned", "--dry-run", "--yes", "--json" @@ -1536,6 +1623,22 @@ export const RUNPANE_CONTRACT = { "--yes", "--json" ], + [ + "panes", + "pin", + "--pane", + "session-1", + "--yes", + "--json" + ], + [ + "panes", + "unpin", + "--pane", + "session-1", + "--yes", + "--json" + ], [ "panels", "list", @@ -2121,6 +2224,9 @@ export const RUNPANE_CONTRACT = { "sessionPrompt": { "type": "string" }, + "pinned": { + "type": "boolean" + }, "tool": { "oneOf": [ { @@ -2221,7 +2327,8 @@ export const RUNPANE_CONTRACT = { "required": [ "ok", "index", - "name" + "name", + "pinned" ], "properties": { "ok": { @@ -2233,6 +2340,9 @@ export const RUNPANE_CONTRACT = { "name": { "type": "string" }, + "pinned": { + "type": "boolean" + }, "sessionId": { "type": "string" }, @@ -2521,7 +2631,8 @@ export const RUNPANE_CONTRACT = { "status", "worktreePath", "repoId", - "panelCount" + "panelCount", + "pinned" ], "properties": { "id": { @@ -2548,6 +2659,9 @@ export const RUNPANE_CONTRACT = { "panelCount": { "type": "number" }, + "pinned": { + "type": "boolean" + }, "createdAt": { "type": "string" }, @@ -2585,6 +2699,51 @@ export const RUNPANE_CONTRACT = { }, "additionalProperties": false }, + "panePinRequest": { + "type": "object", + "required": [ + "paneId", + "pinned" + ], + "properties": { + "paneId": { + "type": "string" + }, + "pinned": { + "type": "boolean" + }, + "dryRun": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "panePinResult": { + "type": "object", + "required": [ + "ok", + "paneId", + "pinned" + ], + "properties": { + "ok": { + "const": true + }, + "paneId": { + "type": "string" + }, + "pinned": { + "type": "boolean" + }, + "dryRun": { + "const": true + }, + "favoritePinnedAt": { + "type": "string" + } + }, + "additionalProperties": false + }, "paneArchiveResult": { "oneOf": [ { @@ -3655,14 +3814,14 @@ export const RUNPANE_CONTRACT = { "rules": [ "Start with `runpane doctor --json` to understand wrapper, platform, daemon reachability, and the next safe commands before mutating Pane state.", "In a Pane repository checkout, if `runpane` is not on PATH, use the built local wrapper with Node 22, for example `PATH=/opt/homebrew/opt/node@22/bin:$PATH node packages/runpane/dist/cli.js doctor --json`.", - "Happy path for any user request to use Pane/RunPane: run `runpane doctor --json`, read `runpane agent-context --json`, resolve or add the saved base repo, create the requested visible Pane with a complete command such as `runpane panes create --repo <repo> --name <name> --agent <agent> --prompt \"<task>\" --source agent --no-focus --wait-ready --yes --json` (or use `--tool-command <command>` instead of `--agent <agent>`), then validate with `panels wait` or `panels screen`.", + "Happy path for any user request to use Pane/RunPane: run `runpane doctor --json`, read `runpane agent-context --json`, resolve or add the saved base repo, create the requested visible Pane with a complete command such as `runpane panes create --repo <repo> --name <name> --agent <agent> --prompt \"<task>\" --source agent --pinned --no-focus --wait-ready --yes --json` when it should stay visible (or omit `--pinned`, or use `--tool-command <command>` instead of `--agent <agent>`), then validate with `panels wait` or `panels screen`.", "Treat Pane as the user's visible cockpit for watching/co-driving work. Do not create Panes or panels for private delegation unless the user asked for visible Pane orchestration or the result should appear in the Pane app.", "Register the saved base repository once with `repos add`; do not register a pre-created worktree as a separate repo unless the user explicitly asks.", "Use `panes create` for separate visible Panes (Pane sessions) for feature/PR work. Pane creates and owns the worktree/branch for each new Pane.", "Use `panels create` for reviewer/helper/clean-context tabs that should stay inside an existing Pane and share that Pane's worktree.", "For private background decomposition, use your normal subagent/worktree mechanism instead of Pane.", "Skill routing: when the user says `discussion`, `plan`, `simple-plan`, `create-plan`, or `implement`, or asks for those behaviors, treat three references as peer context: Pane's local skill cache under `<PANE_DIR>/skills/`, the Pane Chat orchestrator handoff at `<PANE_DIR>/skills/pane-chat/runpane-orchestrator.md` when present, and the workflow map at https://github.com/dcouple/skills/raw/main/docs/readme-workflow-map.png. For where the local cache and Pane Chat bootstrap live in Pane, reference https://github.com/dcouple/Pane/pull/291. Use GitHub reads against https://github.com/dcouple/skills/tree/main/parsa only to inspect or refresh referenced skill files; do not clone or install it unless the user asks.", - "When an agent creates Panes or panels, pass `--source agent --no-focus --wait-ready --yes --json` unless the user explicitly wants focus moved.", + "When an agent creates Panes or panels, pass `--source agent --no-focus --wait-ready --yes --json` unless the user explicitly wants focus moved; add `--pinned` when declaring a Pane that should remain in the UI's favorite/pin set.", "Use `runpane agent-context --json` for the full agent-facing CLI context, or `runpane agent-context --command <command> --json` for one detailed command definition.", "For `agent-context --command`, use canonical spaced names like `panes create`; copied forms like `panes.create` or `runpane panes create` are accepted too.", "Use `runpane repos list --json` to find the saved repository when unsure after doctor shows the daemon is reachable.", @@ -3743,6 +3902,7 @@ export const RUNPANE_CONTRACT = { "--initial-input-file <path|->", "--from-json <path|->", "--source <user|agent>", + "--pinned", "--no-focus", "--focus", "--wait-ready", @@ -3763,6 +3923,26 @@ export const RUNPANE_CONTRACT = { "--json" ] }, + { + "name": "panes pin", + "summary": "Declaratively pin a Pane (the Pane UI's favorite/pin star) without changing focus.", + "arguments": [ + "--pane <pane-id>", + "--yes", + "--dry-run", + "--json" + ] + }, + { + "name": "panes unpin", + "summary": "Declaratively unpin a Pane (the Pane UI's favorite/pin star) without changing focus.", + "arguments": [ + "--pane <pane-id>", + "--yes", + "--dry-run", + "--json" + ] + }, { "name": "panels create", "summary": "Create reviewer/helper terminal tabs inside an existing Pane; they share that Pane's worktree.", @@ -4250,11 +4430,21 @@ export const RUNPANE_CONTRACT = { "required": false, "description": "Explicitly focus the created pane." }, + { + "name": "--pinned", + "required": false, + "description": "Create the pane already pinned (the Pane UI's favorite/pin star); does not imply focus." + }, { "name": "--yes", "required": false, "description": "Skip confirmation for mutating commands." }, + { + "name": "--dry-run", + "required": false, + "description": "Validate and preview without mutating." + }, { "name": "--json", "required": false, @@ -4292,7 +4482,7 @@ export const RUNPANE_CONTRACT = { "`panes create` is for user-visible Pane orchestration, not the agent's default private delegation mechanism.", "Register the saved base repository once. Pane creates and owns the worktree/branch for each new Pane.", "Use `panels create` instead when a reviewer/helper should share an existing Pane's worktree.", - "Agent-created Panes should pass `--source agent --no-focus --wait-ready --yes --json` unless the user explicitly wants focus moved.", + "Agent-created Panes should pass `--source agent --no-focus --wait-ready --yes --json` unless the user explicitly wants focus moved; add `--pinned` when the Pane should remain in the UI's favorite/pin set.", "The built-in agent templates come from the runpane contract; custom terminal commands can pass agent-specific flags when requested by the user.", "Use --initial-input-file for multi-line prompts or shell-sensitive initial input.", "When the JSON result includes nextCommand, run it to validate that the terminal produced output before reporting success.", @@ -4353,6 +4543,90 @@ export const RUNPANE_CONTRACT = { "Archiving a main-repo Pane (no Pane-managed worktree) always succeeds immediately since nothing is deleted from disk." ] }, + "panes pin": { + "name": "panes pin", + "summary": "Declaratively pin a Pane; pinned is the Pane UI's favorite/pin star.", + "details": "Sets the requested pinned state to true without reading and toggling it. Repeating the command is idempotent and pinning never changes focus.", + "requiresPaneDaemon": true, + "mutates": true, + "arguments": [ + { + "name": "--pane", + "value": "<pane-id>", + "required": true, + "description": "Pane/session id to pin." + }, + { + "name": "--yes", + "required": false, + "description": "Skip confirmation for mutating commands." + }, + { + "name": "--dry-run", + "required": false, + "description": "Validate and preview without mutating." + }, + { + "name": "--json", + "required": false, + "description": "Print machine-readable output." + } + ], + "examples": [ + "runpane panes pin --pane <pane-id> --yes --json", + "runpane panes pin --pane <pane-id> --dry-run --json" + ], + "jsonSchemas": [ + "panePinRequest", + "panePinResult" + ], + "notes": [ + "This is a declarative set, not a toggle: retries leave an already-pinned Pane pinned.", + "Pinning does not focus the Pane." + ] + }, + "panes unpin": { + "name": "panes unpin", + "summary": "Declaratively unpin a Pane; pinned is the Pane UI's favorite/pin star.", + "details": "Sets the requested pinned state to false without reading and toggling it. Repeating the command is idempotent and unpinning never changes focus.", + "requiresPaneDaemon": true, + "mutates": true, + "arguments": [ + { + "name": "--pane", + "value": "<pane-id>", + "required": true, + "description": "Pane/session id to unpin." + }, + { + "name": "--yes", + "required": false, + "description": "Skip confirmation for mutating commands." + }, + { + "name": "--dry-run", + "required": false, + "description": "Validate and preview without mutating." + }, + { + "name": "--json", + "required": false, + "description": "Print machine-readable output." + } + ], + "examples": [ + "runpane panes unpin --pane <pane-id> --yes --json", + "runpane panes unpin --pane <pane-id> --dry-run --json" + ], + "jsonSchemas": [ + "panePinRequest", + "panePinResult" + ], + "notes": [ + "This is a declarative set, not a toggle: retries leave an already-unpinned Pane unpinned.", + "Unpinning does not focus the Pane." + ] + }, "panels list": { "name": "panels list", "summary": "List tool panels inside a Pane session.", diff --git a/packages/runpane/src/localControl.ts b/packages/runpane/src/localControl.ts index b197b4b1..7422f568 100644 --- a/packages/runpane/src/localControl.ts +++ b/packages/runpane/src/localControl.ts @@ -59,6 +59,7 @@ interface PaneCreateItem { worktreeName?: string; baseBranch?: string; sessionPrompt?: string; + pinned?: boolean; tool: PaneToolSpec; } @@ -73,6 +74,7 @@ interface PaneCreateResult { ok: boolean; index: number; name?: string; + pinned: boolean; sessionId?: string; panelId?: string; worktreePath?: string; @@ -105,6 +107,7 @@ interface PaneSummary { repoId: number; repoName?: string; panelCount: number; + pinned: boolean; createdAt?: string; lastActivity?: string; archived?: boolean; @@ -122,6 +125,20 @@ interface PaneArchiveRequest { source?: 'user' | 'agent'; } +interface PanePinRequest { + paneId: string; + pinned: boolean; + dryRun?: boolean; +} + +interface PanePinResult { + ok: true; + paneId: string; + pinned: boolean; + dryRun?: true; + favoritePinnedAt?: string; +} + interface PaneArchiveSafetyCheck { performed: boolean; hasUncommittedChanges?: boolean; @@ -425,6 +442,31 @@ export async function runPanesArchive(parsed: ParsedArgs): Promise<number> { return result.ok ? 0 : 1; } +export async function runPanesPin(parsed: ParsedArgs, pinned: boolean): Promise<number> { + if (!parsed.paneId) { + throw new Error(`runpane panes ${pinned ? 'pin' : 'unpin'} requires --pane.`); + } + + const request: PanePinRequest = { + paneId: parsed.paneId, + pinned, + ...(parsed.dryRun ? { dryRun: true } : {}), + }; + await confirmPanePin(parsed, request); + + const result = await invokeDaemon<PanePinResult>('runpane:panes:pin', [request], { + paneDir: parsed.paneDir, + }); + + if (parsed.json) { + printJson(result); + } else { + console.log(`${result.pinned ? 'Pinned' : 'Unpinned'} ${result.paneId}`); + } + + return 0; +} + export async function runPanelsList(parsed: ParsedArgs): Promise<number> { if (!parsed.paneId) { throw new Error('runpane panels list requires --pane.'); @@ -692,6 +734,9 @@ async function buildPaneCreateRequest(parsed: ParsedArgs): Promise<PaneCreateReq if (parsed.concurrency !== undefined) { request.concurrency = parsed.concurrency; } + if (parsed.pinned) { + request.panes = request.panes.map(item => ({ ...item, pinned: true })); + } applyPaneFocusOptions(parsed, request); return request; } @@ -714,6 +759,7 @@ async function buildPaneCreateRequest(parsed: ParsedArgs): Promise<PaneCreateReq name: parsed.name, worktreeName: parsed.worktreeName, baseBranch: parsed.baseBranch, + pinned: parsed.pinned || undefined, tool, }], dryRun: parsed.dryRun || undefined, @@ -854,6 +900,27 @@ async function confirmPaneArchive(parsed: ParsedArgs, request: PaneArchiveReques } } +async function confirmPanePin(parsed: ParsedArgs, request: PanePinRequest): Promise<void> { + if (parsed.dryRun || parsed.yes) { + return; + } + + const command = request.pinned ? 'pin' : 'unpin'; + if (!isInteractiveShell()) { + throw new Error(`runpane panes ${command} mutates Pane state. Rerun with --yes in non-interactive shells.`); + } + + const rl = createInterface({ input, output }); + try { + const answer = (await rl.question(`${request.pinned ? 'Pin' : 'Unpin'} pane ${request.paneId}? [y/N] `)).trim().toLowerCase(); + if (answer !== 'y' && answer !== 'yes') { + throw new Error('Cancelled.'); + } + } finally { + rl.close(); + } +} + async function confirmPanelCreate(parsed: ParsedArgs, request: PanelCreateRequest): Promise<void> { if (parsed.yes) { return; @@ -993,7 +1060,8 @@ function printPaneListResult(result: PaneListResult): void { for (const pane of result.panes) { const repo = pane.repoName ? ` ${pane.repoName}` : ''; - console.log(`${pane.id}\t${pane.name}\t${pane.status}\t${pane.panelCount} panels\t${pane.worktreePath}${repo}`); + const pinned = pane.pinned ? ' pinned' : ''; + console.log(`${pane.id}\t${pane.name}\t${pane.status}${pinned}\t${pane.panelCount} panels\t${pane.worktreePath}${repo}`); } } @@ -1175,6 +1243,7 @@ function parsePaneCreateItemPayload(value: unknown, index: number): PaneCreateIt worktreeName: optionalString(value.worktreeName), baseBranch: optionalString(value.baseBranch), sessionPrompt: optionalString(value.sessionPrompt), + pinned: typeof value.pinned === 'boolean' ? value.pinned : undefined, tool: parsePaneToolSpecPayload(value.tool, index), }; } diff --git a/scripts/fixtures/runpane-contract.json b/scripts/fixtures/runpane-contract.json index b73e4ec9..c74d66b7 100644 --- a/scripts/fixtures/runpane-contract.json +++ b/scripts/fixtures/runpane-contract.json @@ -116,6 +116,7 @@ "Kick off discussion", "--source", "agent", + "--pinned", "--dry-run", "--yes", "--json" @@ -139,6 +140,22 @@ "--yes", "--json" ], + [ + "panes", + "pin", + "--pane", + "session-1", + "--yes", + "--json" + ], + [ + "panes", + "unpin", + "--pane", + "session-1", + "--yes", + "--json" + ], [ "panels", "list", diff --git a/scripts/test-runpane-contract.js b/scripts/test-runpane-contract.js index e66191c8..ab2e4bb4 100644 --- a/scripts/test-runpane-contract.js +++ b/scripts/test-runpane-contract.js @@ -829,6 +829,7 @@ async function checkFromJsonAcceptsBom() { repo: 'active', panes: [{ name: 'bom-test', + pinned: true, tool: { command: 'echo hello' } @@ -857,6 +858,7 @@ async function checkFromJsonAcceptsBom() { assert.strictEqual(code, 0); assert.strictEqual(capturedNodeRequest.repo, 'active'); assert.strictEqual(capturedNodeRequest.panes[0].name, 'bom-test'); + assert.strictEqual(capturedNodeRequest.panes[0].pinned, true); assert.strictEqual(capturedNodeRequest.dryRun, true); } finally { daemonClient.invokeDaemon = originalInvokeDaemon; @@ -875,6 +877,7 @@ payload = { "repo": "active", "panes": [{ "name": "bom-test", + "pinned": True, "tool": {"command": "echo hello"}, }], } @@ -903,6 +906,7 @@ finally: repo: 'active', panes: [{ name: 'bom-test', + pinned: true, tool: { command: 'echo hello' } @@ -912,6 +916,120 @@ finally: }); } +async function checkPanePinParity() { + const daemonClient = require(path.join(rootDir, 'packages', 'runpane', 'dist', 'daemonClient.js')); + const { parseRunpaneArgs } = require(path.join(rootDir, 'packages', 'runpane', 'dist', 'commands.js')); + const { runPanesCreate, runPanesPin } = require(path.join(rootDir, 'packages', 'runpane', 'dist', 'localControl.js')); + const originalInvokeDaemon = daemonClient.invokeDaemon; + const originalConsoleLog = console.log; + const calls = []; + const stdout = []; + + daemonClient.invokeDaemon = async (channel, args) => { + calls.push({ channel, request: args[0] }); + if (channel === 'runpane:panes:create') { + return { ok: true, repo: {}, items: [] }; + } + return { ok: true, paneId: args[0].paneId, pinned: args[0].pinned }; + }; + console.log = (line) => stdout.push(String(line)); + + try { + await runPanesPin(parseRunpaneArgs(['panes', 'pin', '--pane', 'session-1', '--yes', '--json']), true); + await runPanesPin(parseRunpaneArgs(['panes', 'unpin', '--pane', 'session-1', '--yes', '--json']), false); + await runPanesPin(parseRunpaneArgs(['panes', 'pin', '--pane', 'session-1', '--dry-run', '--json']), true); + await runPanesPin(parseRunpaneArgs(['panes', 'unpin', '--pane', 'session-1', '--dry-run', '--json']), false); + await assert.rejects( + runPanesPin(parseRunpaneArgs(['panes', 'pin', '--pane', 'session-1']), true), + /Rerun with --yes in non-interactive shells/, + ); + await runPanesCreate(parseRunpaneArgs([ + 'panes', 'create', '--repo', 'active', '--name', 'pinned-pane', '--agent', 'codex', + '--pinned', '--dry-run', '--yes', '--json' + ])); + } finally { + daemonClient.invokeDaemon = originalInvokeDaemon; + console.log = originalConsoleLog; + } + + assert.deepStrictEqual(calls.slice(0, 2), [{ + channel: 'runpane:panes:pin', + request: { paneId: 'session-1', pinned: true } + }, { + channel: 'runpane:panes:pin', + request: { paneId: 'session-1', pinned: false } + }]); + assert.deepStrictEqual(calls.slice(2, 4), [{ + channel: 'runpane:panes:pin', + request: { paneId: 'session-1', pinned: true, dryRun: true } + }, { + channel: 'runpane:panes:pin', + request: { paneId: 'session-1', pinned: false, dryRun: true } + }]); + assert.strictEqual(calls[4].channel, 'runpane:panes:create'); + assert.strictEqual(calls[4].request.panes[0].pinned, true); + assert.deepStrictEqual(stdout.slice(0, 4).map(line => JSON.parse(line)), [{ + ok: true, + paneId: 'session-1', + pinned: true + }, { + ok: true, + paneId: 'session-1', + pinned: false + }, { + ok: true, + paneId: 'session-1', + pinned: true + }, { + ok: true, + paneId: 'session-1', + pinned: false + }]); + + const pythonOutput = runPythonSnippet(` +import contextlib +import io +import json +import runpane.local_control as local_control +from runpane.cli import parse_args + +calls = [] +def fake_invoke(channel, args, **kwargs): + calls.append({"channel": channel, "request": args[0]}) + if channel == "runpane:panes:create": + return {"ok": True, "repo": {}, "items": []} + return {"ok": True, "paneId": args[0]["paneId"], "pinned": args[0]["pinned"]} + +local_control.invoke_daemon = fake_invoke +stdout = io.StringIO() +with contextlib.redirect_stdout(stdout): + local_control.run_panes_pin(parse_args(["panes", "pin", "--pane", "session-1", "--yes", "--json"]), True) + local_control.run_panes_pin(parse_args(["panes", "unpin", "--pane", "session-1", "--yes", "--json"]), False) + local_control.run_panes_pin(parse_args(["panes", "pin", "--pane", "session-1", "--dry-run", "--json"]), True) + local_control.run_panes_pin(parse_args(["panes", "unpin", "--pane", "session-1", "--dry-run", "--json"]), False) + local_control.run_panes_create(parse_args([ + "panes", "create", "--repo", "active", "--name", "pinned-pane", "--agent", "codex", + "--pinned", "--dry-run", "--yes", "--json" + ])) + +refused = False +try: + local_control.run_panes_pin(parse_args(["panes", "pin", "--pane", "session-1"]), True) +except ValueError as error: + refused = "Rerun with --yes in non-interactive shells" in str(error) + +print(json.dumps({"calls": calls, "stdout": stdout.getvalue().splitlines(), "refused": refused})) +`); + const python = JSON.parse(pythonOutput); + assert.deepStrictEqual(python.calls, JSON.parse(JSON.stringify(calls))); + const pythonJsonResults = JSON.parse(`[${python.stdout.join('\n').replace(/}\n{/g, '},{')}]`); + assert.deepStrictEqual( + pythonJsonResults.slice(0, 4), + stdout.slice(0, 4).map(line => JSON.parse(line)), + ); + assert.strictEqual(python.refused, true); +} + function checkHelpOutput() { const python = findPython(); const pythonEnv = { @@ -963,6 +1081,8 @@ function checkHelpOutput() { assertIncludes(output, 'Pane session commands.'); assertIncludes(output, 'runpane panes list'); assertIncludes(output, 'runpane panes create'); + assertIncludes(output, 'runpane panes pin'); + assertIncludes(output, 'runpane panes unpin'); } for (const output of [nodePanelsHelp, pyPanelsHelp]) { @@ -1012,6 +1132,9 @@ function compareAgentContextParity() { assert.ok(nodeBrief.rules.some((rule) => rule.includes('creates Panes or panels'))); assert.ok(nodeBrief.tools.some((tool) => tool.name === 'doctor')); assert.ok(nodeBrief.tools.some((tool) => tool.name === 'panes create')); + assert.ok(nodeBrief.tools.some((tool) => tool.name === 'panes pin')); + assert.ok(nodeBrief.tools.some((tool) => tool.name === 'panes unpin')); + assert.ok(nodeBrief.rules.some((rule) => rule.includes('`--pinned`'))); const nodeDetail = JSON.parse(runNode(['agent-context', '--command', 'panes create', '--json'])); const pyDetail = JSON.parse(runPython(['agent-context', '--command', 'panes create', '--json'])); @@ -1022,6 +1145,20 @@ function compareAgentContextParity() { assert.ok(nodeDetail.command.details.includes('do not pre-create a git worktree')); assert.ok(nodeDetail.command.notes.some((note) => note.includes("not the agent's default private delegation mechanism"))); assert.ok(nodeDetail.command.notes.some((note) => note.includes('panels create'))); + assert.ok(nodeDetail.command.arguments.some((argument) => argument.name === '--pinned')); + + const nodePinDetail = JSON.parse(runNode(['agent-context', '--command', 'panes pin', '--json'])); + const pyPinDetail = JSON.parse(runPython(['agent-context', '--command', 'panes pin', '--json'])); + assert.deepStrictEqual(pyPinDetail, nodePinDetail); + assert.strictEqual(nodePinDetail.command.name, 'panes pin'); + assert.ok(nodePinDetail.command.details.includes('idempotent')); + assert.ok(nodePinDetail.command.arguments.some((argument) => argument.name === '--dry-run')); + + const nodeUnpinDetail = JSON.parse(runNode(['agent-context', '--command', 'panes unpin', '--json'])); + const pyUnpinDetail = JSON.parse(runPython(['agent-context', '--command', 'panes unpin', '--json'])); + assert.deepStrictEqual(pyUnpinDetail, nodeUnpinDetail); + assert.strictEqual(nodeUnpinDetail.command.name, 'panes unpin'); + assert.ok(nodeUnpinDetail.command.arguments.some((argument) => argument.name === '--dry-run')); const nodePanelsDetail = JSON.parse(runNode(['agent-context', '--command', 'panels create', '--json'])); const pyPanelsDetail = JSON.parse(runPython(['agent-context', '--command', 'panels create', '--json'])); @@ -1123,6 +1260,7 @@ async function runChecks() { await checkExistingDaemonShortCircuit(); checkWindowsPaneVersionDoesNotLaunchExecutable(); await checkFromJsonAcceptsBom(); + await checkPanePinParity(); checkHelpOutput(); compareAgentContextParity(); await checkNodeReleaseTimeout(); diff --git a/shared/types/generatedRunpaneContract.ts b/shared/types/generatedRunpaneContract.ts index c541354a..cf77ea16 100644 --- a/shared/types/generatedRunpaneContract.ts +++ b/shared/types/generatedRunpaneContract.ts @@ -196,6 +196,30 @@ export const RUNPANE_CONTRACT = { "paneArchiveResult" ] }, + { + "name": "panes pin", + "summary": "Declaratively pin a Pane; pinned is the Pane UI's favorite/pin star and repeated requests are idempotent.", + "usage": [ + "runpane panes pin --pane <pane-id> --yes [--dry-run] [--json]" + ], + "mutates": true, + "jsonSchemas": [ + "panePinRequest", + "panePinResult" + ] + }, + { + "name": "panes unpin", + "summary": "Declaratively unpin a Pane; pinned is the Pane UI's favorite/pin star and repeated requests are idempotent.", + "usage": [ + "runpane panes unpin --pane <pane-id> --yes [--dry-run] [--json]" + ], + "mutates": true, + "jsonSchemas": [ + "panePinRequest", + "panePinResult" + ] + }, { "name": "panels create", "summary": "Create a terminal-backed tool panel inside an existing Pane session.", @@ -522,6 +546,10 @@ export const RUNPANE_CONTRACT = { "name": "--focus", "description": "Explicitly focus the created pane or panel." }, + { + "name": "--pinned", + "description": "Create the pane already pinned (the UI's favorite/pin star)." + }, { "name": "--force", "description": "Archive even if the pane's branch has uncommitted, untracked, or unpushed changes." @@ -690,8 +718,10 @@ export const RUNPANE_CONTRACT = { " runpane panes list [--repo <selector>] [--json]", " runpane panes create --repo <selector> --name <name> --agent <codex|claude> [options]", " runpane panes archive --pane <pane-id> [--force] [--source user|agent] --yes [--json]", + " runpane panes pin --pane <pane-id> --yes [--dry-run] [--json]", + " runpane panes unpin --pane <pane-id> --yes [--dry-run] [--json]", "", - "Run \"runpane help panes list\", \"runpane help panes create\", or \"runpane help panes archive\" for command-specific options." + "Run \"runpane help panes <command>\" for command-specific options." ], "panes list": [ "Usage:", @@ -730,6 +760,7 @@ export const RUNPANE_CONTRACT = { " --source <user|agent> Mark mutation source; agent implies background creation", " --no-focus Create in the background without stealing focus", " --focus Explicitly focus the created pane", + " --pinned Create already pinned (the UI's favorite/pin star)", " --pane-dir <path> Connect to a specific Pane data directory", " --json Print machine-readable output", " --dry-run Validate and preview without creating panes", @@ -749,6 +780,32 @@ export const RUNPANE_CONTRACT = { " --json Print machine-readable output", " --yes Skip confirmation for mutating commands" ], + "panes pin": [ + "Usage:", + " runpane panes pin --pane <pane-id> --yes [--dry-run] [--json]", + "", + "Declaratively pins a Pane using the Pane UI's favorite/pin star. Repeating the command is idempotent and never changes focus. Use --dry-run to validate and preview without mutating.", + "", + "Options:", + " --pane <pane-id> Pane/session id to pin", + " --pane-dir <path> Connect to a specific Pane data directory", + " --json Print machine-readable output", + " --dry-run Validate and preview without pinning the pane", + " --yes Skip confirmation for mutating commands" + ], + "panes unpin": [ + "Usage:", + " runpane panes unpin --pane <pane-id> --yes [--dry-run] [--json]", + "", + "Declaratively unpins a Pane using the Pane UI's favorite/pin star. Repeating the command is idempotent and never changes focus. Use --dry-run to validate and preview without mutating.", + "", + "Options:", + " --pane <pane-id> Pane/session id to unpin", + " --pane-dir <path> Connect to a specific Pane data directory", + " --json Print machine-readable output", + " --dry-run Validate and preview without unpinning the pane", + " --yes Skip confirmation for mutating commands" + ], "panels": [ "Terminal-backed panel commands.", "", @@ -1059,8 +1116,10 @@ export const RUNPANE_CONTRACT = { " runpane panes list [--repo <selector>] [--json]", " runpane panes create --repo <selector> --name <name> --agent <codex|claude> [options]", " runpane panes archive --pane <pane-id> [--force] [--source user|agent] --yes [--json]", + " runpane panes pin --pane <pane-id> --yes [--dry-run] [--json]", + " runpane panes unpin --pane <pane-id> --yes [--dry-run] [--json]", "", - "Run \"runpane help panes list\", \"runpane help panes create\", or \"runpane help panes archive\" for command-specific options." + "Run \"runpane help panes <command>\" for command-specific options." ], "panes list": [ "Usage:", @@ -1099,6 +1158,7 @@ export const RUNPANE_CONTRACT = { " --source <user|agent> Mark mutation source; agent implies background creation", " --no-focus Create in the background without stealing focus", " --focus Explicitly focus the created pane", + " --pinned Create already pinned (the UI's favorite/pin star)", " --pane-dir <path> Connect to a specific Pane data directory", " --json Print machine-readable output", " --dry-run Validate and preview without creating panes", @@ -1118,6 +1178,32 @@ export const RUNPANE_CONTRACT = { " --json", " --yes" ], + "panes pin": [ + "Usage:", + " runpane panes pin --pane <pane-id> --yes [--dry-run] [--json]", + "", + "Declaratively pins a Pane using the Pane UI's favorite/pin star. Repeating the command is idempotent and never changes focus. Use --dry-run to validate and preview without mutating.", + "", + "Options:", + " --pane <pane-id>", + " --pane-dir <path>", + " --json", + " --dry-run", + " --yes" + ], + "panes unpin": [ + "Usage:", + " runpane panes unpin --pane <pane-id> --yes [--dry-run] [--json]", + "", + "Declaratively unpins a Pane using the Pane UI's favorite/pin star. Repeating the command is idempotent and never changes focus. Use --dry-run to validate and preview without mutating.", + "", + "Options:", + " --pane <pane-id>", + " --pane-dir <path>", + " --json", + " --dry-run", + " --yes" + ], "panels": [ "Terminal-backed panel commands.", "", @@ -1513,6 +1599,7 @@ export const RUNPANE_CONTRACT = { "Kick off discussion", "--source", "agent", + "--pinned", "--dry-run", "--yes", "--json" @@ -1536,6 +1623,22 @@ export const RUNPANE_CONTRACT = { "--yes", "--json" ], + [ + "panes", + "pin", + "--pane", + "session-1", + "--yes", + "--json" + ], + [ + "panes", + "unpin", + "--pane", + "session-1", + "--yes", + "--json" + ], [ "panels", "list", @@ -2121,6 +2224,9 @@ export const RUNPANE_CONTRACT = { "sessionPrompt": { "type": "string" }, + "pinned": { + "type": "boolean" + }, "tool": { "oneOf": [ { @@ -2221,7 +2327,8 @@ export const RUNPANE_CONTRACT = { "required": [ "ok", "index", - "name" + "name", + "pinned" ], "properties": { "ok": { @@ -2233,6 +2340,9 @@ export const RUNPANE_CONTRACT = { "name": { "type": "string" }, + "pinned": { + "type": "boolean" + }, "sessionId": { "type": "string" }, @@ -2521,7 +2631,8 @@ export const RUNPANE_CONTRACT = { "status", "worktreePath", "repoId", - "panelCount" + "panelCount", + "pinned" ], "properties": { "id": { @@ -2548,6 +2659,9 @@ export const RUNPANE_CONTRACT = { "panelCount": { "type": "number" }, + "pinned": { + "type": "boolean" + }, "createdAt": { "type": "string" }, @@ -2585,6 +2699,51 @@ export const RUNPANE_CONTRACT = { }, "additionalProperties": false }, + "panePinRequest": { + "type": "object", + "required": [ + "paneId", + "pinned" + ], + "properties": { + "paneId": { + "type": "string" + }, + "pinned": { + "type": "boolean" + }, + "dryRun": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "panePinResult": { + "type": "object", + "required": [ + "ok", + "paneId", + "pinned" + ], + "properties": { + "ok": { + "const": true + }, + "paneId": { + "type": "string" + }, + "pinned": { + "type": "boolean" + }, + "dryRun": { + "const": true + }, + "favoritePinnedAt": { + "type": "string" + } + }, + "additionalProperties": false + }, "paneArchiveResult": { "oneOf": [ { @@ -3655,14 +3814,14 @@ export const RUNPANE_CONTRACT = { "rules": [ "Start with `runpane doctor --json` to understand wrapper, platform, daemon reachability, and the next safe commands before mutating Pane state.", "In a Pane repository checkout, if `runpane` is not on PATH, use the built local wrapper with Node 22, for example `PATH=/opt/homebrew/opt/node@22/bin:$PATH node packages/runpane/dist/cli.js doctor --json`.", - "Happy path for any user request to use Pane/RunPane: run `runpane doctor --json`, read `runpane agent-context --json`, resolve or add the saved base repo, create the requested visible Pane with a complete command such as `runpane panes create --repo <repo> --name <name> --agent <agent> --prompt \"<task>\" --source agent --no-focus --wait-ready --yes --json` (or use `--tool-command <command>` instead of `--agent <agent>`), then validate with `panels wait` or `panels screen`.", + "Happy path for any user request to use Pane/RunPane: run `runpane doctor --json`, read `runpane agent-context --json`, resolve or add the saved base repo, create the requested visible Pane with a complete command such as `runpane panes create --repo <repo> --name <name> --agent <agent> --prompt \"<task>\" --source agent --pinned --no-focus --wait-ready --yes --json` when it should stay visible (or omit `--pinned`, or use `--tool-command <command>` instead of `--agent <agent>`), then validate with `panels wait` or `panels screen`.", "Treat Pane as the user's visible cockpit for watching/co-driving work. Do not create Panes or panels for private delegation unless the user asked for visible Pane orchestration or the result should appear in the Pane app.", "Register the saved base repository once with `repos add`; do not register a pre-created worktree as a separate repo unless the user explicitly asks.", "Use `panes create` for separate visible Panes (Pane sessions) for feature/PR work. Pane creates and owns the worktree/branch for each new Pane.", "Use `panels create` for reviewer/helper/clean-context tabs that should stay inside an existing Pane and share that Pane's worktree.", "For private background decomposition, use your normal subagent/worktree mechanism instead of Pane.", "Skill routing: when the user says `discussion`, `plan`, `simple-plan`, `create-plan`, or `implement`, or asks for those behaviors, treat three references as peer context: Pane's local skill cache under `<PANE_DIR>/skills/`, the Pane Chat orchestrator handoff at `<PANE_DIR>/skills/pane-chat/runpane-orchestrator.md` when present, and the workflow map at https://github.com/dcouple/skills/raw/main/docs/readme-workflow-map.png. For where the local cache and Pane Chat bootstrap live in Pane, reference https://github.com/dcouple/Pane/pull/291. Use GitHub reads against https://github.com/dcouple/skills/tree/main/parsa only to inspect or refresh referenced skill files; do not clone or install it unless the user asks.", - "When an agent creates Panes or panels, pass `--source agent --no-focus --wait-ready --yes --json` unless the user explicitly wants focus moved.", + "When an agent creates Panes or panels, pass `--source agent --no-focus --wait-ready --yes --json` unless the user explicitly wants focus moved; add `--pinned` when declaring a Pane that should remain in the UI's favorite/pin set.", "Use `runpane agent-context --json` for the full agent-facing CLI context, or `runpane agent-context --command <command> --json` for one detailed command definition.", "For `agent-context --command`, use canonical spaced names like `panes create`; copied forms like `panes.create` or `runpane panes create` are accepted too.", "Use `runpane repos list --json` to find the saved repository when unsure after doctor shows the daemon is reachable.", @@ -3743,6 +3902,7 @@ export const RUNPANE_CONTRACT = { "--initial-input-file <path|->", "--from-json <path|->", "--source <user|agent>", + "--pinned", "--no-focus", "--focus", "--wait-ready", @@ -3763,6 +3923,26 @@ export const RUNPANE_CONTRACT = { "--json" ] }, + { + "name": "panes pin", + "summary": "Declaratively pin a Pane (the Pane UI's favorite/pin star) without changing focus.", + "arguments": [ + "--pane <pane-id>", + "--yes", + "--dry-run", + "--json" + ] + }, + { + "name": "panes unpin", + "summary": "Declaratively unpin a Pane (the Pane UI's favorite/pin star) without changing focus.", + "arguments": [ + "--pane <pane-id>", + "--yes", + "--dry-run", + "--json" + ] + }, { "name": "panels create", "summary": "Create reviewer/helper terminal tabs inside an existing Pane; they share that Pane's worktree.", @@ -4250,11 +4430,21 @@ export const RUNPANE_CONTRACT = { "required": false, "description": "Explicitly focus the created pane." }, + { + "name": "--pinned", + "required": false, + "description": "Create the pane already pinned (the Pane UI's favorite/pin star); does not imply focus." + }, { "name": "--yes", "required": false, "description": "Skip confirmation for mutating commands." }, + { + "name": "--dry-run", + "required": false, + "description": "Validate and preview without mutating." + }, { "name": "--json", "required": false, @@ -4292,7 +4482,7 @@ export const RUNPANE_CONTRACT = { "`panes create` is for user-visible Pane orchestration, not the agent's default private delegation mechanism.", "Register the saved base repository once. Pane creates and owns the worktree/branch for each new Pane.", "Use `panels create` instead when a reviewer/helper should share an existing Pane's worktree.", - "Agent-created Panes should pass `--source agent --no-focus --wait-ready --yes --json` unless the user explicitly wants focus moved.", + "Agent-created Panes should pass `--source agent --no-focus --wait-ready --yes --json` unless the user explicitly wants focus moved; add `--pinned` when the Pane should remain in the UI's favorite/pin set.", "The built-in agent templates come from the runpane contract; custom terminal commands can pass agent-specific flags when requested by the user.", "Use --initial-input-file for multi-line prompts or shell-sensitive initial input.", "When the JSON result includes nextCommand, run it to validate that the terminal produced output before reporting success.", @@ -4353,6 +4543,90 @@ export const RUNPANE_CONTRACT = { "Archiving a main-repo Pane (no Pane-managed worktree) always succeeds immediately since nothing is deleted from disk." ] }, + "panes pin": { + "name": "panes pin", + "summary": "Declaratively pin a Pane; pinned is the Pane UI's favorite/pin star.", + "details": "Sets the requested pinned state to true without reading and toggling it. Repeating the command is idempotent and pinning never changes focus.", + "requiresPaneDaemon": true, + "mutates": true, + "arguments": [ + { + "name": "--pane", + "value": "<pane-id>", + "required": true, + "description": "Pane/session id to pin." + }, + { + "name": "--yes", + "required": false, + "description": "Skip confirmation for mutating commands." + }, + { + "name": "--dry-run", + "required": false, + "description": "Validate and preview without mutating." + }, + { + "name": "--json", + "required": false, + "description": "Print machine-readable output." + } + ], + "examples": [ + "runpane panes pin --pane <pane-id> --yes --json", + "runpane panes pin --pane <pane-id> --dry-run --json" + ], + "jsonSchemas": [ + "panePinRequest", + "panePinResult" + ], + "notes": [ + "This is a declarative set, not a toggle: retries leave an already-pinned Pane pinned.", + "Pinning does not focus the Pane." + ] + }, + "panes unpin": { + "name": "panes unpin", + "summary": "Declaratively unpin a Pane; pinned is the Pane UI's favorite/pin star.", + "details": "Sets the requested pinned state to false without reading and toggling it. Repeating the command is idempotent and unpinning never changes focus.", + "requiresPaneDaemon": true, + "mutates": true, + "arguments": [ + { + "name": "--pane", + "value": "<pane-id>", + "required": true, + "description": "Pane/session id to unpin." + }, + { + "name": "--yes", + "required": false, + "description": "Skip confirmation for mutating commands." + }, + { + "name": "--dry-run", + "required": false, + "description": "Validate and preview without mutating." + }, + { + "name": "--json", + "required": false, + "description": "Print machine-readable output." + } + ], + "examples": [ + "runpane panes unpin --pane <pane-id> --yes --json", + "runpane panes unpin --pane <pane-id> --dry-run --json" + ], + "jsonSchemas": [ + "panePinRequest", + "panePinResult" + ], + "notes": [ + "This is a declarative set, not a toggle: retries leave an already-unpinned Pane unpinned.", + "Unpinning does not focus the Pane." + ] + }, "panels list": { "name": "panels list", "summary": "List tool panels inside a Pane session.", diff --git a/shared/types/runpaneOrchestration.ts b/shared/types/runpaneOrchestration.ts index da100402..254b6003 100644 --- a/shared/types/runpaneOrchestration.ts +++ b/shared/types/runpaneOrchestration.ts @@ -86,6 +86,7 @@ export interface RunpanePaneCreateItem { worktreeName?: string; baseBranch?: string; sessionPrompt?: string; + pinned?: boolean; tool: RunpaneToolSpec; } @@ -156,6 +157,7 @@ export interface RunpanePaneCreateSuccessItem { ok: boolean; index: number; name: string; + pinned: boolean; sessionId?: string; paneId?: string; panelId?: string; @@ -198,6 +200,7 @@ export interface RunpanePaneSummary { repoId: number; repoName?: string; panelCount: number; + pinned: boolean; createdAt?: string; lastActivity?: string; archived?: boolean; @@ -213,6 +216,20 @@ export interface RunpanePaneListResult { panes: RunpanePaneSummary[]; } +export interface RunpanePanePinRequest { + paneId: string; + pinned: boolean; + dryRun?: boolean; +} + +export interface RunpanePanePinResult { + ok: true; + paneId: string; + pinned: boolean; + dryRun?: true; + favoritePinnedAt?: string; +} + export interface RunpanePaneArchiveRequest { paneId: string; force?: boolean;