const MUXSH_JS: &str = "#!/usr/bin/env node\n// Copyright 2026, AgentMux Corp.\n// SPDX-License-Identifier: Apache-2.0\n//\n// muxsh \u{2014} AgentMux\'s own successor to Wave Terminal\'s `wsh` CLI (which\n// AgentMux inherited and fully retired 2026-04-12,\n// docs/specs/archive/SPEC_RETIRE_WSH_2026_04_12.md \u{2014} zero real usage of the\n// imported 20-subcommand surface). Not that CLI revived: a purpose-built\n// collection over MuxAPI, scoped per\n// docs/specs/SPEC_MUXSH_FULL_COLLECTION_2026_09_16.md \u{2014} every subcommand\n// below maps to a real, verified-unsigned (`$AGENTMUX_AUTH_KEY`-gated, not\n// agent-identity-signed) REST route; a few `wsh` commands that looked\n// buildable on paper (`pane close`, `secret list`) turned out to need either\n// new backend work or a capability a terminal structurally doesn\'t have \u{2014}\n// see that spec\'s \u{a7}5.5/\u{a7}5.6 for why they\'re not here.\n//\n// Grammar: `muxsh <noun> <verb> [args] [flags]`, with `open`/`web`/`view`/\n// `edit`/`run` as bare top-level shortcuts (the spec\'s \u{a7}2.1 \"iconic gesture\"\n// exception) rather than nested under a noun.\n//\n// Same architecture and auth as every muxsh-family core: thin Node wrapper,\n// $AGENTMUX_LOCAL_URL + $AGENTMUX_AUTH_KEY from the pane env\n// (lib/muxclient.mjs), no MCP, no new transport.\n//\n// Deployed to ~/.agentmux/shell/muxsh.mjs; shell `muxsh` functions delegate\n// here. From a tool-spawned subshell call the core directly:\n// node ~/.agentmux/shell/muxsh.mjs open <file>\n//\n// Exit codes: 0 success, 1 usage/environment error, 2 the server rejected\n// the request or is unreachable.\n\nimport { resolve as resolvePath } from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\n\nimport { agentmuxFetch, readAgentmuxEnv } from \"./lib/muxclient.mjs\";\n\nconst HELP = `muxsh \u{2014} a terminal CLI over MuxAPI (no GUI required)\n\nusage:\n muxsh open <file> open a file in an editor pane\n muxsh web <url> open a URL in a browser pane\n muxsh view <path-or-url> open the right pane type automatically\n (URL -> browser, media file -> media, folder/ -> files, else -> editor)\n muxsh edit <file> like \'open\', but always forces the editor view\n --title <t> pane/tab title\n --split <right|left|down|up> split direction relative to the calling pane (default: right)\n --collapse-tree [open/edit/view-as-editor only] collapse the file-tree sidebar\n --floating open in a floating window instead of a docked split\n --no-focus open without focusing the new pane\n\n muxsh pane list list tabs/panes in the calling pane\'s workspace\n --json machine-readable output\n\n muxsh run <cmd...> run a background shell, print its shell_id\n muxsh run --status <shell_id> check whether a shell is still running\n muxsh run --stop <shell_id> stop a running shell\n\n muxsh config edit open settings.json in an editor pane\n muxsh config path [data|config|logs|shared]\n print the requested AgentMux directory (default: data)\n\n muxsh agent list list agents reachable from this instance\n --json machine-readable output\n muxsh agent send <name> <message...>\n send a message to a running agent\n\n muxsh help this text\n\nRequires $AGENTMUX_LOCAL_URL and $AGENTMUX_AUTH_KEY (present in any\nAgentMux-opened pane). Every command operates on the instance this pane\nbelongs to; cross-instance operation is not implemented here.\n\nNot here: \'muxsh pane close\' and \'muxsh secret list\' both looked buildable\non paper but turned out to need either new backend work or an agent\nidentity a terminal doesn\'t have \u{2014} see\ndocs/specs/SPEC_MUXSH_FULL_COLLECTION_2026_09_16.md \u{a7}5.5/\u{a7}5.6.\n\'muxopen <agent>\' (a separate, already-shipped tool) launches an agent into\na pane \u{2014} not duplicated here as \'muxsh agent open\'.`;\n\nconst EDITOR_ONLY_FLAGS = [\"--collapse-tree\"];\nconst SPLIT_DIRECTIONS = [\"right\", \"left\", \"down\", \"up\"];\nconst MEDIA_EXTENSIONS = [\n \".png\", \".jpg\", \".jpeg\", \".gif\", \".webp\", \".svg\", \".bmp\", \".ico\",\n \".mp4\", \".webm\", \".mov\", \".avi\",\n \".mp3\", \".wav\", \".ogg\", \".flac\",\n \".pdf\",\n];\n// Not derived from a canonical AgentMux list (open question, SPEC_MUXSH_FULL_COLLECTION_2026_09_16.md \u{a7}7.4) \u{2014}\n// hand-rolled and may drift from whatever the frontend\'s own pane-type resolution considers \"media\".\nconst URL_PATTERN = /^\\w+:\\/\\//;\n\nconst CONFIG_PATH_ENV_VARS = {\n data: \"AGENTMUX_DATA_DIR\",\n config: \"AGENTMUX_CONFIG_DIR\",\n logs: \"AGENTMUX_LOG_DIR\",\n shared: \"AGENTMUX_SHARED_DIR\",\n};\n\n// \u{2500}\u{2500} Parsing \u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\n\n/** Parse the shared open/web/view/edit flag set (--title/--split/--collapse-tree/--floating/--no-focus).\n *\n * `isEditor` gates `--collapse-tree` (`tree_expanded` is documented in\n * `rpc_types/block.rs` as \"editor only\"). Codex review (PR #3281, P2): an\n * earlier version of this gated on the *subcommand name* (\"open\" vs \"web\"),\n * which incorrectly accepted --collapse-tree for `muxsh view` on a media\n * target \u{2014} `view` resolves to `command: \"open\"` for BOTH editor and media,\n * but only editor should accept this flag. Gating on the actually-resolved\n * view (via this boolean) instead of the subcommand name fixes both `view`\n * and any future caller that reuses this parser.\n *\n * Pure. Returns `{title, split, collapseTree, floating, focus}` or `{error}`. */\nfunction parsePaneOpenFlags(argv, startIndex, isEditor) {\n let title = null;\n let split = null;\n let collapseTree = false;\n let floating = false;\n let focus = true;\n\n for (let i = startIndex; i < argv.length; i++) {\n const arg = argv[i];\n if (arg === \"--title\") {\n title = argv[++i];\n if (!title) return { error: \"--title requires a value\" };\n } else if (arg === \"--split\") {\n split = argv[++i];\n if (!split) return { error: \"--split requires a value\" };\n if (!SPLIT_DIRECTIONS.includes(split)) {\n return { error: `--split must be one of right/left/down/up, got \'${split}\'` };\n }\n } else if (arg === \"--collapse-tree\") {\n collapseTree = true;\n } else if (arg === \"--floating\") {\n floating = true;\n } else if (arg === \"--no-focus\") {\n focus = false;\n } else {\n return { error: `unknown argument \'${arg}\'` };\n }\n\n if (!isEditor && EDITOR_ONLY_FLAGS.includes(arg)) {\n return { error: `\'${arg}\' is only valid for an editor pane (\'muxsh open\'/\'muxsh edit\', or \'muxsh view\' on a non-media, non-url target)` };\n }\n }\n return { title, split, collapseTree, floating, focus };\n}\n\n/** Guess the pane.open `view` for `muxsh view <path-or-url>`. Pure.\n *\n * A folder is recognised only by its shape (a trailing separator, `.`, `..`\n * or `~`), since telling any other folder apart would take a stat. */\nexport function guessView(target) {\n if (URL_PATTERN.test(target)) return \"browser\";\n if (/[\\\\/]$/.test(target) || target === \".\" || target === \"..\" || target === \"~\") return \"files\";\n const lower = target.toLowerCase();\n if (MEDIA_EXTENSIONS.some((ext) => lower.endsWith(ext))) return \"media\";\n return \"editor\";\n}\n\n/** Parse argv (already stripped of node + script path).\n *\n * Returns `{help}` | `{error}` | a command-specific shape discriminated by\n * `.command`. Pure \u{2014} no I/O, no process.exit.\n */\nexport function parseArgs(argv) {\n if (argv.length === 0) return { help: true, exitCode: 1 };\n if (argv[0] === \"help\" || argv[0] === \"--help\" || argv[0] === \"-h\") {\n return { help: true, exitCode: 0 };\n }\n\n const head = argv[0];\n\n if (head === \"open\" || head === \"web\" || head === \"edit\") {\n const target = argv[1];\n if (!target || target.startsWith(\"-\")) {\n return { error: `\'muxsh ${head}\' requires a ${head === \"web\" ? \"url\" : \"file path\"} as its first argument` };\n }\n const isEditor = head === \"open\" || head === \"edit\";\n const flags = parsePaneOpenFlags(argv, 2, isEditor);\n if (flags.error) return flags;\n // \'edit\' is \'open\' forcing the editor view, everywhere else identical.\n const subcommand = head === \"edit\" ? \"open\" : head;\n if (subcommand === \"open\") {\n return { command: \"open\", subcommand, file: target, ...flags };\n }\n return { command: \"web\", subcommand, url: target, ...flags };\n }\n\n if (head === \"view\") {\n const target = argv[1];\n if (!target || target.startsWith(\"-\")) {\n return { error: \"\'muxsh view\' requires a file path or url as its first argument\" };\n }\n const view = guessView(target);\n const flags = parsePaneOpenFlags(argv, 2, view === \"editor\");\n if (flags.error) return flags;\n if (view === \"browser\") {\n return { command: \"web\", subcommand: \"web\", url: target, ...flags };\n }\n if (view === \"files\") {\n // srv resolves `~` itself, but a relative folder means nothing\n // outside this shell\'s working directory, so it goes absolute here.\n const folder = target.startsWith(\"~\") ? target : resolvePath(target);\n return { command: \"open\", subcommand: \"open\", file: folder, view, ...flags };\n }\n // \"media\" and \"editor\" both use the pane.open editor-shaped request\n // (view/file), matching what buildPaneOpenBody already emits for \"open\".\n return { command: \"open\", subcommand: \"open\", file: target, view, ...flags };\n }\n\n if (head === \"pane\") {\n if (argv[1] !== \"list\") {\n return { error: `unknown \'muxsh pane\' verb \'${argv[1] ?? \"\"}\' (expected \'list\')` };\n }\n const json = argv.slice(2).includes(\"--json\");\n const extra = argv.slice(2).filter((a) => a !== \"--json\");\n if (extra.length > 0) return { error: `unknown argument \'${extra[0]}\'` };\n return { command: \"pane-list\", json };\n }\n\n if (head === \"run\") {\n if (argv[1] === \"--status\" || argv[1] === \"--stop\") {\n const verb = argv[1] === \"--status\" ? \"run-status\" : \"run-stop\";\n const shellId = argv[2];\n if (!shellId) return { error: `\'muxsh run ${argv[1]}\' requires a shell id` };\n if (argv.length > 3) return { error: `unknown argument \'${argv[3]}\'` };\n return { command: verb, shellId };\n }\n const cmdParts = argv.slice(1);\n if (cmdParts.length === 0) return { error: \"\'muxsh run\' requires a command\" };\n return { command: \"run-create\", cmd: cmdParts.join(\" \") };\n }\n\n if (head === \"config\") {\n if (argv[1] === \"edit\") {\n if (argv.length > 2) return { error: `unknown argument \'${argv[2]}\'` };\n return { command: \"config-edit\" };\n }\n if (argv[1] === \"path\") {\n // Zero further args validly defaults to \"data\" \u{2014} checked against\n // CONFIG_PATH_ENV_VARS, not treated as a missing-verb error.\n const kind = argv[2] ?? \"data\";\n if (argv.length > 3) return { error: `unknown argument \'${argv[3]}\'` };\n if (!Object.hasOwn(CONFIG_PATH_ENV_VARS, kind)) {\n return { error: `unknown \'muxsh config path\' kind \'${kind}\' (expected data/config/logs/shared)` };\n }\n return { command: \"config-path\", kind };\n }\n return { error: `unknown \'muxsh config\' verb \'${argv[1] ?? \"\"}\' (expected \'edit\' or \'path\')` };\n }\n\n if (head === \"agent\") {\n if (argv[1] === \"list\") {\n const json = argv.slice(2).includes(\"--json\");\n const extra = argv.slice(2).filter((a) => a !== \"--json\");\n if (extra.length > 0) return { error: `unknown argument \'${extra[0]}\'` };\n return { command: \"agent-list\", json };\n }\n if (argv[1] === \"send\") {\n const name = argv[2];\n const messageParts = argv.slice(3);\n if (!name) return { error: \"\'muxsh agent send\' requires an agent name\" };\n if (messageParts.length === 0) return { error: \"\'muxsh agent send\' requires a message\" };\n return { command: \"agent-send\", name, message: messageParts.join(\" \") };\n }\n return { error: `unknown \'muxsh agent\' verb \'${argv[1] ?? \"\"}\' (expected \'list\' or \'send\')` };\n }\n\n return { error: `unknown command \'${head}\'` };\n}\n\n// \u{2500}\u{2500} pane.open (open / web / view / edit) \u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\n\n/** Build the /api/v1/pane/open request body for a parsed open/web/view command.\n *\n * `env` carries `AGENTMUX_BLOCKID`/`AGENTMUX_TABID` \u{2014} injected into every\n * terminal pane for exactly this purpose (`blockcontroller/shell/lifecycle.rs`).\n * Without a `split_reference_block_id`, the server\'s `resolve_placement`\n * (`server/app_api/pane.rs`) always falls back to plain \"insert\" regardless\n * of `split_direction` \u{2014} so `split_direction` is only meaningful, and is\n * therefore only sent, when a block id is actually known. Mirrors exactly\n * how the `OpenEditor` MCP tool derives this from `AGENTMUX_BLOCKID`\n * (`crates/mcp/src/main.rs`). Ignored when floating (`floating` panes are\n * documented as ignoring `split_direction`/`split_reference_block_id`\n * entirely \u{2014} `rpc_types/block.rs`), so they\'re omitted rather than sent and\n * silently discarded. Pure, for testability.\n */\nexport function buildRequestBody(parsed, env = {}) {\n const body = { focus: parsed.focus, floating: parsed.floating };\n if (parsed.title) body.title = parsed.title;\n\n const blockId = env.AGENTMUX_BLOCKID;\n const tabId = env.AGENTMUX_TABID;\n if (tabId) body.tab_id = tabId;\n if (blockId && !parsed.floating) {\n body.split_direction = parsed.split ?? \"right\";\n body.split_reference_block_id = blockId;\n }\n\n if (parsed.command === \"open\") {\n body.view = parsed.view ?? \"editor\";\n body.file = parsed.file;\n if (parsed.collapseTree && body.view === \"editor\") body.tree_expanded = false;\n } else {\n body.view = \"browser\";\n body.url = parsed.url;\n }\n return body;\n}\n\n/** Render the success line(s) for a /api/v1/pane/open response body.\n * Pure \u{2014} returns the text rather than printing, for testability. */\nexport function renderResult(body) {\n return [`opened: ${body.view} pane`, ` block ${body.block_id}`, ` tab ${body.tab_id}`].join(\"\\n\");\n}\n\n// \u{2500}\u{2500} pane list \u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\n\n/** Render `GET /api/v1/tabs` response as a human-readable outline. Pure. */\n/** Render `GET /api/v1/tabs`\'s real response shape \u{2014} `{tabs: [{tab_id, name,\n * pane_count}]}` (`service/introspect.rs::agent_tabs`). Pure.\n *\n * Codex review (PR #3281, P2): an earlier version of this assumed a richer,\n * never-verified shape (`tab_name`/`active`/a nested `panes` array with\n * per-pane `view`/`block_id`/`title`) that the route has never produced \u{2014}\n * caught because the test that \"proved\" it used a hand-invented fixture\n * instead of the real response shape. There is no per-pane detail in what\n * this route actually returns; `pane_count` is the closest available\n * signal. */\nexport function renderTabs(tabs) {\n if (!Array.isArray(tabs) || tabs.length === 0) return \"no tabs found\";\n return tabs\n .map((tab) => `tab ${tab.name || tab.tab_id} (${tab.pane_count} pane${tab.pane_count === 1 ? \"\" : \"s\"}) ${tab.tab_id}`)\n .join(\"\\n\");\n}\n\n// \u{2500}\u{2500} run \u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\n\nexport function renderShellCreate(body) {\n return `shell_id: ${body.shell_id}`;\n}\n\nexport function renderShellStatus(body) {\n const lines = [`running: ${body.running}`];\n if (body.exit_code !== undefined && body.exit_code !== null) lines.push(`exit_code: ${body.exit_code}`);\n lines.push(`line_count: ${body.line_count}`);\n return lines.join(\"\\n\");\n}\n\nexport function renderShellStop(body) {\n return `stopped: ${body.stopped}`;\n}\n\n/** Build the /api/v1/shell/create request body. Pure \u{2014} separated out (rather\n * than inlined in main()) specifically so muxsh.contract.test.mjs can assert\n * its field names against docs/specs/app-api-manifest.json, same reason\n * buildRequestBody exists for pane.open. */\nexport function buildShellCreateBody(parsed, env = {}) {\n return { agent_block_id: env.AGENTMUX_BLOCKID, cmd: parsed.cmd };\n}\n\nexport function buildShellStatusBody(parsed) {\n return { shell_id: parsed.shellId };\n}\n\nexport function buildShellStopBody(parsed) {\n return { shell_id: parsed.shellId };\n}\n\n// \u{2500}\u{2500} agent list / send \u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\n\n/** Render `GET /agentmux/discovery`\'s host.agents into a human-readable list. Pure. */\n/** Render GET /agentmux/discovery\'s full response (`server/mod.rs::handle_discovery`).\n *\n * Codex review (PR #3281, P2): an earlier version only read `host.agents`,\n * omitting `host.cross_channel` (other AgentMux channels on this same host \u{2014}\n * a real, `agent send`-reachable tier) and `lan` (mDNS peer instances, each\n * carrying its own flat `agents: string[]`, per `LanInstance` in\n * `lan_discovery.rs` \u{2014} not a richer per-agent object, verified against\n * source rather than assumed this time) entirely, so `muxsh agent list`\n * silently under-reported what `muxsh agent send` can actually reach.\n */\nexport function renderAgentList(discovery) {\n const lines = [];\n for (const a of discovery?.host?.agents ?? []) {\n lines.push(`${a.addressable ? \"*\" : \" \"} ${a.name}${a.addressable ? \"\" : \" (not addressable)\"}`);\n }\n for (const a of discovery?.host?.cross_channel ?? []) {\n lines.push(`* ${a.name} (channel: ${a.channel})`);\n }\n for (const peer of discovery?.lan ?? []) {\n for (const name of peer.agents ?? []) {\n lines.push(`* ${name} (lan: ${peer.hostname})`);\n }\n }\n if (lines.length === 0) return \"no agents found\";\n return lines.join(\"\\n\");\n}\n\nexport function renderAgentSend(body) {\n return body.success ? \"sent\" : `not sent: ${body.error ?? \"unknown error\"}`;\n}\n\n/** Build the /agentmux/reactive/inject request body. Pure \u{2014} see\n * buildShellCreateBody\'s doc comment for why this is a separate, exported\n * function rather than inlined in main(). */\nexport function buildAgentSendBody(parsed) {\n return { target_agent: parsed.name, message: parsed.message };\n}\n\n// \u{2500}\u{2500} main \u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\n\nfunction fail(msg, code = 1) {\n process.stderr.write(`muxsh: ${msg}\\n`);\n process.exit(code);\n}\n\nasync function apiCall(url, authKey, path, opts, notFoundHint) {\n let result;\n try {\n result = await agentmuxFetch(url, authKey, path, opts);\n } catch (e) {\n fail(`cannot reach ${url}: ${e.message ?? e}`, 2);\n }\n let body;\n try {\n body = await result.resp.json();\n } catch {\n body = {};\n }\n if (!result.ok) {\n const hint = result.status === 404 && notFoundHint\n ? ` (HTTP 404 \u{2014} this AgentMux instance may predate ${notFoundHint})`\n : \"\";\n fail(`${body.error ?? `HTTP ${result.status}`}${hint}`, 2);\n }\n return body;\n}\n\nexport async function main() {\n const parsed = parseArgs(process.argv.slice(2));\n\n if (parsed.help) {\n console.log(HELP);\n process.exit(parsed.exitCode);\n }\n if (parsed.error) fail(`${parsed.error}\\n\\n${HELP}`);\n\n if (parsed.command === \"config-path\") {\n const envVar = CONFIG_PATH_ENV_VARS[parsed.kind];\n const value = process.env[envVar];\n if (!value) fail(`${envVar} is not set in this pane\'s environment`);\n console.log(value);\n return;\n }\n\n const { url, authKey } = readAgentmuxEnv();\n if (!url || !authKey) {\n fail(\n \"AGENTMUX_LOCAL_URL / AGENTMUX_AUTH_KEY not set \u{2014} run from a pane \" +\n \"AgentMux opened (agent or shell), or export them from one.\",\n );\n }\n\n switch (parsed.command) {\n case \"open\":\n case \"web\": {\n const body = await apiCall(url, authKey, \"/api/v1/pane/open\", {\n method: \"POST\",\n body: buildRequestBody(parsed, process.env),\n }, \"/api/v1/pane/open\");\n console.log(renderResult(body));\n return;\n }\n case \"config-edit\": {\n const configDir = process.env.AGENTMUX_CONFIG_DIR;\n if (!configDir) fail(\"AGENTMUX_CONFIG_DIR is not set in this pane\'s environment\");\n const openParsed = { command: \"open\", subcommand: \"open\", file: `${configDir}/settings.json`, title: \"settings.json\", split: null, collapseTree: false, floating: false, focus: true };\n const body = await apiCall(url, authKey, \"/api/v1/pane/open\", {\n method: \"POST\",\n body: buildRequestBody(openParsed, process.env),\n }, \"/api/v1/pane/open\");\n console.log(renderResult(body));\n return;\n }\n case \"pane-list\": {\n const blockId = process.env.AGENTMUX_BLOCKID ?? \"\";\n const body = await apiCall(url, authKey, `/api/v1/tabs?block_id=${encodeURIComponent(blockId)}`, {}, \"/api/v1/tabs\");\n if (parsed.json) {\n console.log(JSON.stringify(body, null, 2));\n } else {\n console.log(renderTabs(body.tabs ?? body));\n }\n return;\n }\n case \"run-create\": {\n if (!process.env.AGENTMUX_BLOCKID) fail(\"AGENTMUX_BLOCKID is not set in this pane\'s environment \u{2014} \'muxsh run\' needs it to attribute the shell\");\n const body = await apiCall(url, authKey, \"/api/v1/shell/create\", {\n method: \"POST\",\n body: buildShellCreateBody(parsed, process.env),\n }, \"/api/v1/shell/create\");\n console.log(renderShellCreate(body));\n return;\n }\n case \"run-status\": {\n const body = await apiCall(url, authKey, \"/api/v1/shell/status\", {\n method: \"POST\",\n body: buildShellStatusBody(parsed),\n }, \"/api/v1/shell/status\");\n console.log(renderShellStatus(body));\n return;\n }\n case \"run-stop\": {\n const body = await apiCall(url, authKey, \"/api/v1/shell/stop\", {\n method: \"POST\",\n body: buildShellStopBody(parsed),\n }, \"/api/v1/shell/stop\");\n console.log(renderShellStop(body));\n return;\n }\n case \"agent-list\": {\n const body = await apiCall(url, authKey, \"/agentmux/discovery\", {}, \"/agentmux/discovery\");\n if (parsed.json) {\n console.log(JSON.stringify(body, null, 2));\n } else {\n console.log(renderAgentList(body));\n }\n return;\n }\n case \"agent-send\": {\n const body = await apiCall(url, authKey, \"/agentmux/reactive/inject\", {\n method: \"POST\",\n body: buildAgentSendBody(parsed),\n }, \"/agentmux/reactive/inject\");\n // /agentmux/reactive/inject returns HTTP 200 even when delivery\n // fails (success: false, e.g. \"agent not found\") \u{2014} apiCall\'s\n // !result.ok check alone can\'t catch this, so it\'s checked here\n // explicitly. Without this, a script relying on muxsh\'s own\n // exit-code contract (Codex, PR #3281 P1) would see exit 0 for\n // an undelivered message.\n if (!body.success) fail(renderAgentSend(body), 2);\n console.log(renderAgentSend(body));\n return;\n }\n default:\n fail(`internal error: unhandled command \'${parsed.command}\'`);\n }\n}\n\nif (import.meta.url === pathToFileURL(process.argv[1] ?? \"\").href) {\n main().catch((e) => fail(e.message ?? String(e), 2));\n}\n";Expand description
muxsh — open editor/browser panes from a terminal
(REPORT_WSH_STYLE_CLI_FOR_AGENT_APP_API_2026_09_16.md,
SPEC_MUXSH_CLI_2026_09_16.md). Deployed beside its three siblings; shell
muxsh functions delegate here.