{"_id":"@bluearch/mission-control","_rev":"11-f9f12b216e7155ce7296509390f21a56","name":"@bluearch/mission-control","dist-tags":{"latest":"1.1.13"},"versions":{"1.1.2":{"name":"@bluearch/mission-control","version":"1.1.2","keywords":["claude","claude-code","tui","ink","terminal","agents","ai-agents","tmux-alternative","self-hosted"],"author":{"name":"BlueArch"},"license":"AGPL-3.0-only","_id":"@bluearch/mission-control@1.1.2","maintainers":[{"name":"bluearchgroup","email":"joel@bluearch.io"}],"homepage":"https://xxyjoel.github.io/ba-mission-control","bugs":{"url":"https://github.com/xxyjoel/ba-mission-control/issues"},"bin":{"mc":"bin/mc.mjs"},"dist":{"shasum":"98595ea0142e1bab6bb33dbc4ed5fdc0c2f5e8c9","tarball":"https://registry.npmjs.org/@bluearch/mission-control/-/mission-control-1.1.2.tgz","fileCount":77,"integrity":"sha512-2AhA0247IrS07FhdbqredZOSXIwmldq7reor98qugxezYV31eDEx6/YAcBKUjVMMmzxzgp1XKIjS/prRvw/4AQ==","signatures":[{"sig":"MEUCIQDFyI1ox/71XOhyZTKJhbkUQPKr/MtdPe1Q5OaAMq3euQIgTe/pg02pdBMw5QgmHTf2v8CpRWXRFrENVW4BAjQ9Urk=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":725660},"type":"module","engines":{"node":">=20"},"gitHead":"4cdfec79a13fde15e9710100e464dace52bf1af5","scripts":{"dev":"tsx watch bin/mc.mjs","test":"node scripts/run-tests.mjs","start":"tsx bin/mc.mjs","preversion":"npm test","start:diag":"MC_HEAP_LOG=1 NODE_OPTIONS=--heapsnapshot-near-heap-limit=2 tsx bin/mc.mjs","test:watch":"MC_NO_TRANSCRIPT=1 find tests -name '*.test.*' -type f -exec node --import tsx --test --test-timeout=30000 --test-force-exit --watch {} +","dev:sandbox":"MC_CONFIG_DIR=\"$HOME/.config/claude-mc-dev\" tsx bin/mc.mjs","postinstall":"node scripts/fix-node-pty.mjs","postversion":"git push --follow-tags","verify:pack":"node scripts/verify-pack.mjs","prepublishOnly":"node scripts/prepublish-guard.mjs","dev:sandbox:watch":"MC_CONFIG_DIR=\"$HOME/.config/claude-mc-dev\" tsx watch bin/mc.mjs"},"_npmUser":{"name":"bluearchgroup","email":"joel@bluearch.io"},"repository":{"url":"git+https://github.com/xxyjoel/ba-mission-control.git","type":"git"},"_npmVersion":"11.19.0","description":"BlueArch Mission Control — keyboard-first terminal TUI for managing up to 10 live Claude Code agent sessions.","directories":{},"_nodeVersion":"26.7.0","dependencies":{"ink":"^7.1.0","tsx":"^4.19.2","react":"^19.2.7","node-pty":"1.1.0","@xterm/headless":"6.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"ink-testing-library":"^4.0.0"},"_npmOperationalInternal":{"tmp":"tmp/mission-control_1.1.2_1786926010903_0.9382264047316706","host":"s3://npm-registry-packages-npm-production"}},"1.1.4":{"name":"@bluearch/mission-control","version":"1.1.4","keywords":["claude","claude-code","tui","ink","terminal","agents","ai-agents","tmux-alternative","self-hosted"],"author":{"name":"BlueArch"},"license":"AGPL-3.0-only","_id":"@bluearch/mission-control@1.1.4","maintainers":[{"name":"bluearchgroup","email":"joel@bluearch.io"}],"homepage":"https://xxyjoel.github.io/ba-mission-control","bugs":{"url":"https://github.com/xxyjoel/ba-mission-control/issues"},"bin":{"mc":"bin/mc.mjs"},"dist":{"shasum":"45a7da8993e125e3d1c5cec16e16086466b48bb7","tarball":"https://registry.npmjs.org/@bluearch/mission-control/-/mission-control-1.1.4.tgz","fileCount":77,"integrity":"sha512-S5WO69wyZp6V4geJZ+1m+GoHadRfzZOhLSSw2oRRrW5z5hbJ8FHSYQHXgSqoHbrg6hyWuLmugRiwKC5pkeKo9w==","signatures":[{"sig":"MEQCIEg6kv8ABDNRus5k+KHSWABXQ3bhsKbHQEyahMu28hBEAiAJP3qz7EwR5K0lBxkoncc5FErAH+PjezWn+8x9FzNG4Q==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bluearch%2fmission-control@1.1.4","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":737314},"type":"module","engines":{"node":">=20"},"gitHead":"549a116ac666ea015b78882d5004909233668f3e","scripts":{"dev":"tsx watch bin/mc.mjs","test":"node scripts/run-tests.mjs","start":"tsx bin/mc.mjs","preversion":"npm test","start:diag":"MC_HEAP_LOG=1 NODE_OPTIONS=--heapsnapshot-near-heap-limit=2 tsx bin/mc.mjs","test:watch":"MC_NO_TRANSCRIPT=1 find tests -name '*.test.*' -type f -exec node --import tsx --test --test-timeout=30000 --test-force-exit --watch {} +","dev:sandbox":"MC_CONFIG_DIR=\"$HOME/.config/claude-mc-dev\" tsx bin/mc.mjs","postinstall":"node scripts/fix-node-pty.mjs","postversion":"git push --follow-tags","verify:pack":"node scripts/verify-pack.mjs","prepublishOnly":"node scripts/prepublish-guard.mjs","dev:sandbox:watch":"MC_CONFIG_DIR=\"$HOME/.config/claude-mc-dev\" tsx watch bin/mc.mjs"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:eec374bf-4770-4b13-89e4-e7fc44ccee93"}},"repository":{"url":"git+https://github.com/xxyjoel/ba-mission-control.git","type":"git"},"_npmVersion":"11.19.1","description":"BlueArch Mission Control — keyboard-first terminal TUI to monitor & run dozens of Claude Code agents: 10 live at once, every session saved and resumable.","directories":{},"_nodeVersion":"20.20.2","dependencies":{"ink":"^7.1.0","tsx":"^4.19.2","react":"^19.2.7","node-pty":"1.1.0","@xterm/headless":"6.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"ink-testing-library":"^4.0.0"},"_npmOperationalInternal":{"tmp":"tmp/mission-control_1.1.4_1787935627042_0.7880705628263565","host":"s3://npm-registry-packages-npm-production"}},"1.1.5":{"name":"@bluearch/mission-control","version":"1.1.5","keywords":["claude","claude-code","tui","ink","terminal","agents","ai-agents","tmux-alternative","self-hosted"],"author":{"name":"BlueArch"},"license":"AGPL-3.0-only","_id":"@bluearch/mission-control@1.1.5","maintainers":[{"name":"bluearchgroup","email":"joel@bluearch.io"}],"homepage":"https://xxyjoel.github.io/ba-mission-control","bugs":{"url":"https://github.com/xxyjoel/ba-mission-control/issues"},"bin":{"mc":"bin/mc.mjs"},"dist":{"shasum":"d54e95b50f27f04207c1ba8734730a0879c455e2","tarball":"https://registry.npmjs.org/@bluearch/mission-control/-/mission-control-1.1.5.tgz","fileCount":78,"integrity":"sha512-UWrR314uyQAkqICLGe0JO0fXHNGQs3jcTS3o+wsoJHTJqhluh66+uTivUyxQf12F6hHyOSQCKfhLvXkH2UL2Ow==","signatures":[{"sig":"MEQCICT14MnSmuHUqWYqGxXruZm6pOSX5wlpjGwtzGkpHGpBAiA7RDRQDfEgVWDDzUJty1y5wP5nmIBH0rwtBB84BKglxA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bluearch%2fmission-control@1.1.5","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":743019},"type":"module","engines":{"node":">=20"},"gitHead":"24a3ec6365cd39630d5f770e0faf78f69c6304d4","scripts":{"dev":"tsx watch bin/mc.mjs","test":"node scripts/run-tests.mjs","start":"tsx bin/mc.mjs","preversion":"npm test","start:diag":"MC_HEAP_LOG=1 NODE_OPTIONS=--heapsnapshot-near-heap-limit=2 tsx bin/mc.mjs","test:watch":"MC_NO_TRANSCRIPT=1 find tests -name '*.test.*' -type f -exec node --import tsx --test --test-timeout=30000 --test-force-exit --watch {} +","dev:sandbox":"MC_CONFIG_DIR=\"$HOME/.config/claude-mc-dev\" tsx bin/mc.mjs","postinstall":"node scripts/fix-node-pty.mjs","postversion":"git push --follow-tags","verify:pack":"node scripts/verify-pack.mjs","prepublishOnly":"node scripts/prepublish-guard.mjs","dev:sandbox:watch":"MC_CONFIG_DIR=\"$HOME/.config/claude-mc-dev\" tsx watch bin/mc.mjs"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:eec374bf-4770-4b13-89e4-e7fc44ccee93"}},"repository":{"url":"git+https://github.com/xxyjoel/ba-mission-control.git","type":"git"},"_npmVersion":"11.19.1","description":"BlueArch Mission Control — keyboard-first terminal TUI to monitor & run dozens of Claude Code agents: 10 live at once, every session saved and resumable.","directories":{},"_nodeVersion":"20.20.2","dependencies":{"ink":"^7.1.0","tsx":"^4.19.2","react":"^19.2.7","node-pty":"1.1.0","@xterm/headless":"6.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"ink-testing-library":"^4.0.0"},"_npmOperationalInternal":{"tmp":"tmp/mission-control_1.1.5_1787936110368_0.09619649799902263","host":"s3://npm-registry-packages-npm-production"}},"1.1.6":{"name":"@bluearch/mission-control","version":"1.1.6","keywords":["claude","claude-code","tui","ink","terminal","agents","ai-agents","tmux-alternative","self-hosted"],"author":{"name":"BlueArch"},"license":"AGPL-3.0-only","_id":"@bluearch/mission-control@1.1.6","maintainers":[{"name":"bluearchgroup","email":"joel@bluearch.io"}],"homepage":"https://xxyjoel.github.io/ba-mission-control","bugs":{"url":"https://github.com/xxyjoel/ba-mission-control/issues"},"bin":{"mc":"bin/mc.mjs"},"dist":{"shasum":"55c9c13f64608a0c992413a7720db9bb1f97c73d","tarball":"https://registry.npmjs.org/@bluearch/mission-control/-/mission-control-1.1.6.tgz","fileCount":78,"integrity":"sha512-FMSG0pSqd/SlvZ1o3iYweEQbF0uivMJYcai9xwbsnlAmMNqZf5IPE5q2lOo1FuaJUGoPafU47aOvs9T4gwXADg==","signatures":[{"sig":"MEUCIQDF3tVKAtzna8kuzNX2PMd+TFI4Sa3GZeOnA7af8DuxxgIgCvCnCx+ibCwryr7b/h1AIM2Xdp36fqb4wKspjoGM1cw=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bluearch%2fmission-control@1.1.6","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":743944},"type":"module","engines":{"node":">=20"},"gitHead":"c92c7a1082554d3bbce3f62c0aaaf1a84ea91bc1","scripts":{"dev":"tsx watch bin/mc.mjs","test":"node scripts/run-tests.mjs","start":"tsx bin/mc.mjs","preversion":"npm test","start:diag":"MC_HEAP_LOG=1 NODE_OPTIONS=--heapsnapshot-near-heap-limit=2 tsx bin/mc.mjs","test:watch":"MC_NO_TRANSCRIPT=1 find tests -name '*.test.*' -type f -exec node --import tsx --test --test-timeout=30000 --test-force-exit --watch {} +","dev:sandbox":"MC_CONFIG_DIR=\"$HOME/.config/claude-mc-dev\" tsx bin/mc.mjs","postinstall":"node scripts/fix-node-pty.mjs","postversion":"git push --follow-tags","verify:pack":"node scripts/verify-pack.mjs","prepublishOnly":"node scripts/prepublish-guard.mjs","dev:sandbox:watch":"MC_CONFIG_DIR=\"$HOME/.config/claude-mc-dev\" tsx watch bin/mc.mjs"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:eec374bf-4770-4b13-89e4-e7fc44ccee93"}},"repository":{"url":"git+https://github.com/xxyjoel/ba-mission-control.git","type":"git"},"_npmVersion":"11.19.1","description":"BlueArch Mission Control — keyboard-first terminal TUI to monitor & run dozens of Claude Code agents: 10 live at once, every session saved and resumable.","directories":{},"_nodeVersion":"20.20.2","dependencies":{"ink":"^7.1.0","tsx":"^4.19.2","react":"^19.2.7","node-pty":"1.1.0","@xterm/headless":"6.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"ink-testing-library":"^4.0.0"},"_npmOperationalInternal":{"tmp":"tmp/mission-control_1.1.6_1787944417894_0.6128367429654018","host":"s3://npm-registry-packages-npm-production"}},"1.1.7":{"name":"@bluearch/mission-control","version":"1.1.7","keywords":["claude","claude-code","tui","ink","terminal","agents","ai-agents","tmux-alternative","self-hosted"],"author":{"name":"BlueArch"},"license":"AGPL-3.0-only","_id":"@bluearch/mission-control@1.1.7","maintainers":[{"name":"bluearchgroup","email":"joel@bluearch.io"}],"homepage":"https://xxyjoel.github.io/ba-mission-control","bugs":{"url":"https://github.com/xxyjoel/ba-mission-control/issues"},"bin":{"mc":"bin/mc.mjs"},"dist":{"shasum":"ba4fc2a237a7914a9175cc495d5dafabe0cceab7","tarball":"https://registry.npmjs.org/@bluearch/mission-control/-/mission-control-1.1.7.tgz","fileCount":78,"integrity":"sha512-jfMwhNyA2gsb9id5p38g/Mlr0WrMTZIp7cXz2LHyGISGUVneavQE/If6fefaqCyoHwNiCyILI0jQVFeQ66Rcsg==","signatures":[{"sig":"MEYCIQDqQ2SmdZxvFuAj7u4unSJVjKTjnBnERVDq0X/GEdikngIhAIdaQI5Vq157cVAVia0UWMb2THZqzlzK94oSThmEfd4+","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bluearch%2fmission-control@1.1.7","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":746305},"type":"module","engines":{"node":">=20"},"gitHead":"037a9a7f84499ec40fe8c77a15d8f9c0d74e400c","scripts":{"dev":"tsx watch bin/mc.mjs","test":"node scripts/run-tests.mjs","start":"tsx bin/mc.mjs","preversion":"npm test","start:diag":"MC_HEAP_LOG=1 NODE_OPTIONS=--heapsnapshot-near-heap-limit=2 tsx bin/mc.mjs","test:watch":"MC_NO_TRANSCRIPT=1 find tests -name '*.test.*' -type f -exec node --import tsx --test --test-timeout=30000 --test-force-exit --watch {} +","dev:sandbox":"MC_CONFIG_DIR=\"$HOME/.config/claude-mc-dev\" tsx bin/mc.mjs","postinstall":"node scripts/fix-node-pty.mjs","postversion":"git push --follow-tags","verify:pack":"node scripts/verify-pack.mjs","prepublishOnly":"node scripts/prepublish-guard.mjs","dev:sandbox:watch":"MC_CONFIG_DIR=\"$HOME/.config/claude-mc-dev\" tsx watch bin/mc.mjs"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:eec374bf-4770-4b13-89e4-e7fc44ccee93"}},"repository":{"url":"git+https://github.com/xxyjoel/ba-mission-control.git","type":"git"},"_npmVersion":"11.19.1","description":"BlueArch Mission Control — keyboard-first terminal TUI to monitor & run dozens of Claude Code agents: 10 live at once, every session saved and resumable.","directories":{},"_nodeVersion":"20.20.2","dependencies":{"ink":"^7.1.0","tsx":"^4.19.2","react":"^19.2.7","node-pty":"1.1.0","@xterm/headless":"6.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"ink-testing-library":"^4.0.0"},"_npmOperationalInternal":{"tmp":"tmp/mission-control_1.1.7_1787948010305_0.6330748665009893","host":"s3://npm-registry-packages-npm-production"}},"1.1.8":{"name":"@bluearch/mission-control","version":"1.1.8","keywords":["claude","claude-code","tui","ink","terminal","agents","ai-agents","tmux-alternative","self-hosted"],"author":{"name":"BlueArch"},"license":"AGPL-3.0-only","_id":"@bluearch/mission-control@1.1.8","maintainers":[{"name":"bluearchgroup","email":"joel@bluearch.io"}],"homepage":"https://xxyjoel.github.io/ba-mission-control","bugs":{"url":"https://github.com/xxyjoel/ba-mission-control/issues"},"bin":{"mc":"bin/mc.mjs"},"dist":{"shasum":"aca5dfefdbf2c0481c68dee82352fd3859913a2f","tarball":"https://registry.npmjs.org/@bluearch/mission-control/-/mission-control-1.1.8.tgz","fileCount":79,"integrity":"sha512-OE1vnsxlpKTMqrVhyTyVZDtgknFhFs39NB6pDqgfhzyBZ99G1VW5+dvSdlQKa/LKhrEZhlF5TvFdcSv5l9oT2A==","signatures":[{"sig":"MEUCIFKWpjZ5Q8BKkTlGl7YuAPuShSAhmyrKlo0thXXdLzkhAiEAtlSd5IBqgVvzLjN9HgPfM4RxKeGveY0OG/DS6Yaqaqg=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bluearch%2fmission-control@1.1.8","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":748769},"type":"module","engines":{"node":">=20"},"gitHead":"4e6d7393a177863e5839e22f68c5cfb0f62cc728","scripts":{"dev":"tsx watch bin/mc.mjs","test":"node scripts/run-tests.mjs","start":"tsx bin/mc.mjs","preversion":"npm test","start:diag":"MC_HEAP_LOG=1 NODE_OPTIONS=--heapsnapshot-near-heap-limit=2 tsx bin/mc.mjs","test:watch":"MC_NO_TRANSCRIPT=1 find tests -name '*.test.*' -type f -exec node --import tsx --test --test-timeout=30000 --test-force-exit --watch {} +","dev:sandbox":"MC_CONFIG_DIR=\"$HOME/.config/claude-mc-dev\" tsx bin/mc.mjs","postinstall":"node scripts/fix-node-pty.mjs","postversion":"git push --follow-tags","verify:pack":"node scripts/verify-pack.mjs","prepublishOnly":"node scripts/prepublish-guard.mjs","dev:sandbox:watch":"MC_CONFIG_DIR=\"$HOME/.config/claude-mc-dev\" tsx watch bin/mc.mjs"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:eec374bf-4770-4b13-89e4-e7fc44ccee93"}},"repository":{"url":"git+https://github.com/xxyjoel/ba-mission-control.git","type":"git"},"_npmVersion":"11.19.1","description":"BlueArch Mission Control — keyboard-first terminal TUI to monitor & run dozens of Claude Code agents: 10 live at once, every session saved and resumable.","directories":{},"_nodeVersion":"20.20.2","dependencies":{"ink":"^7.1.0","tsx":"^4.19.2","react":"^19.2.7","node-pty":"1.1.0","@xterm/headless":"6.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"ink-testing-library":"^4.0.0"},"_npmOperationalInternal":{"tmp":"tmp/mission-control_1.1.8_1787954463295_0.4743612718244532","host":"s3://npm-registry-packages-npm-production"}},"1.1.9":{"name":"@bluearch/mission-control","version":"1.1.9","keywords":["claude","claude-code","tui","ink","terminal","agents","ai-agents","tmux-alternative","self-hosted"],"author":{"name":"BlueArch"},"license":"AGPL-3.0-only","_id":"@bluearch/mission-control@1.1.9","maintainers":[{"name":"bluearchgroup","email":"joel@bluearch.io"}],"homepage":"https://xxyjoel.github.io/ba-mission-control","bugs":{"url":"https://github.com/xxyjoel/ba-mission-control/issues"},"bin":{"mc":"bin/mc.mjs"},"dist":{"shasum":"494e638553324fbc2aed32277fc8b3dd6f704766","tarball":"https://registry.npmjs.org/@bluearch/mission-control/-/mission-control-1.1.9.tgz","fileCount":79,"integrity":"sha512-o388pc/AE7wib9cCi0dOGsFC+snMUrEAlwUvX7OhpzbTIibIkbz1j0KLn3XoSXseXIGvi2+vSAEWI79VNaSKyw==","signatures":[{"sig":"MEUCIQDAClB7bttmZYM+gWZOl1aa93mWLTDy5ctdHbhANNHRAAIgKtGgCp+i/mZuHSG4my0/ER3EsCxj4lsx1BR/SUCziJo=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bluearch%2fmission-control@1.1.9","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":748987},"type":"module","engines":{"node":">=20"},"gitHead":"083c2418a8c3d5aefbdefc439533869c5d50a7e0","scripts":{"dev":"tsx watch bin/mc.mjs","test":"node scripts/run-tests.mjs","start":"tsx bin/mc.mjs","preversion":"npm test","start:diag":"MC_HEAP_LOG=1 NODE_OPTIONS=--heapsnapshot-near-heap-limit=2 tsx bin/mc.mjs","test:watch":"MC_NO_TRANSCRIPT=1 find tests -name '*.test.*' -type f -exec node --import tsx --test --test-timeout=30000 --test-force-exit --watch {} +","dev:sandbox":"MC_CONFIG_DIR=\"$HOME/.config/claude-mc-dev\" tsx bin/mc.mjs","postinstall":"node scripts/fix-node-pty.mjs","postversion":"git push --follow-tags","verify:pack":"node scripts/verify-pack.mjs","prepublishOnly":"node scripts/prepublish-guard.mjs","dev:sandbox:watch":"MC_CONFIG_DIR=\"$HOME/.config/claude-mc-dev\" tsx watch bin/mc.mjs"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:eec374bf-4770-4b13-89e4-e7fc44ccee93"}},"repository":{"url":"git+https://github.com/xxyjoel/ba-mission-control.git","type":"git"},"_npmVersion":"11.19.1","description":"BlueArch Mission Control — keyboard-first terminal TUI to monitor & run dozens of Claude Code agents: 10 live at once, every session saved and resumable.","directories":{},"_nodeVersion":"20.20.2","dependencies":{"ink":"^7.1.0","tsx":"^4.19.2","react":"^19.2.7","node-pty":"1.1.0","@xterm/headless":"6.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"ink-testing-library":"^4.0.0"},"_npmOperationalInternal":{"tmp":"tmp/mission-control_1.1.9_1788037201153_0.9461750116659324","host":"s3://npm-registry-packages-npm-production"}},"1.1.10":{"name":"@bluearch/mission-control","version":"1.1.10","keywords":["claude","claude-code","tui","ink","terminal","agents","ai-agents","tmux-alternative","self-hosted"],"author":{"name":"BlueArch"},"license":"AGPL-3.0-only","_id":"@bluearch/mission-control@1.1.10","maintainers":[{"name":"bluearchgroup","email":"joel@bluearch.io"}],"homepage":"https://xxyjoel.github.io/ba-mission-control","bugs":{"url":"https://github.com/xxyjoel/ba-mission-control/issues"},"bin":{"mc":"bin/mc.mjs"},"dist":{"shasum":"4b3c06769a48f63e398ea0d927862a68fe2eb5d0","tarball":"https://registry.npmjs.org/@bluearch/mission-control/-/mission-control-1.1.10.tgz","fileCount":79,"integrity":"sha512-D54Yvoagouvhi8/3fVScz5YeqtggYHBton7D2CLYg1+luGudn8FX1bjDuMQhGW36H0orrKPDtqzQkHH24uhFLA==","signatures":[{"sig":"MEYCIQCMTb+mceqBebzMgbDTElcPZ6xSNq/We7NSc0JjdflEYQIhALOcNNYeht7ONpZcTlxW0+Pt5YMxCA8LZTxssUfcQpJp","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bluearch%2fmission-control@1.1.10","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":750073},"type":"module","engines":{"node":">=20"},"gitHead":"37412ae8783b808a4a7b907a8b64575440085fa3","scripts":{"dev":"tsx watch bin/mc.mjs","test":"node scripts/run-tests.mjs","start":"tsx bin/mc.mjs","preversion":"npm test","start:diag":"MC_HEAP_LOG=1 NODE_OPTIONS=--heapsnapshot-near-heap-limit=2 tsx bin/mc.mjs","test:watch":"MC_NO_TRANSCRIPT=1 find tests -name '*.test.*' -type f -exec node --import tsx --test --test-timeout=30000 --test-force-exit --watch {} +","dev:sandbox":"MC_CONFIG_DIR=\"$HOME/.config/claude-mc-dev\" tsx bin/mc.mjs","postinstall":"node scripts/fix-node-pty.mjs","postversion":"git push --follow-tags","verify:pack":"node scripts/verify-pack.mjs","prepublishOnly":"node scripts/prepublish-guard.mjs","dev:sandbox:watch":"MC_CONFIG_DIR=\"$HOME/.config/claude-mc-dev\" tsx watch bin/mc.mjs"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:eec374bf-4770-4b13-89e4-e7fc44ccee93"}},"repository":{"url":"git+https://github.com/xxyjoel/ba-mission-control.git","type":"git"},"_npmVersion":"11.19.1","description":"BlueArch Mission Control — keyboard-first terminal TUI to monitor & run dozens of Claude Code agents: 10 live at once, every session saved and resumable.","directories":{},"_nodeVersion":"20.20.2","dependencies":{"ink":"^7.1.0","tsx":"^4.19.2","react":"^19.2.7","node-pty":"1.1.0","@xterm/headless":"6.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"ink-testing-library":"^4.0.0"},"_npmOperationalInternal":{"tmp":"tmp/mission-control_1.1.10_1788043983330_0.013659945133393636","host":"s3://npm-registry-packages-npm-production"}},"1.1.11":{"name":"@bluearch/mission-control","version":"1.1.11","keywords":["claude","claude-code","tui","ink","terminal","agents","ai-agents","tmux-alternative","self-hosted"],"author":{"name":"BlueArch"},"license":"AGPL-3.0-only","_id":"@bluearch/mission-control@1.1.11","maintainers":[{"name":"bluearchgroup","email":"joel@bluearch.io"}],"homepage":"https://xxyjoel.github.io/ba-mission-control","bugs":{"url":"https://github.com/xxyjoel/ba-mission-control/issues"},"bin":{"mc":"bin/mc.mjs"},"dist":{"shasum":"001bc197f44f091fbcb9b767615f7799d160d891","tarball":"https://registry.npmjs.org/@bluearch/mission-control/-/mission-control-1.1.11.tgz","fileCount":82,"integrity":"sha512-NMjx3YF4yZLZphvxvUv8oPYm9lePpzE0OdeHfwjQ1odJODw85ZVMV9vkkL1PiYQ4HOOhbyN0C6/rha59ea8cZA==","signatures":[{"sig":"MEQCIGDprUo3BEjJweiX3K8uFM078L6WfBG5NgNIrEhkBo/iAiAQP+LGNQoO5b5IIPbAiFFb3I6hmxi8eXshVyBy7efM+A==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bluearch%2fmission-control@1.1.11","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":763877},"type":"module","engines":{"node":">=20"},"gitHead":"202e82703e92949bbdd8df6aad13e5804db11d0b","scripts":{"dev":"tsx watch bin/mc.mjs","test":"node scripts/run-tests.mjs","start":"tsx bin/mc.mjs","preversion":"npm test","start:diag":"MC_HEAP_LOG=1 NODE_OPTIONS=--heapsnapshot-near-heap-limit=2 tsx bin/mc.mjs","test:watch":"MC_NO_TRANSCRIPT=1 find tests -name '*.test.*' -type f -exec node --import tsx --test --test-timeout=30000 --test-force-exit --watch {} +","dev:sandbox":"MC_CONFIG_DIR=\"$HOME/.config/claude-mc-dev\" tsx bin/mc.mjs","postinstall":"node scripts/fix-node-pty.mjs","postversion":"git push --follow-tags","verify:pack":"node scripts/verify-pack.mjs","prepublishOnly":"node scripts/prepublish-guard.mjs","dev:sandbox:watch":"MC_CONFIG_DIR=\"$HOME/.config/claude-mc-dev\" tsx watch bin/mc.mjs"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:eec374bf-4770-4b13-89e4-e7fc44ccee93"}},"repository":{"url":"git+https://github.com/xxyjoel/ba-mission-control.git","type":"git"},"_npmVersion":"11.19.1","description":"BlueArch Mission Control — keyboard-first terminal TUI to monitor & run dozens of Claude Code agents: 10 live at once, every session saved and resumable.","directories":{},"_nodeVersion":"20.20.2","dependencies":{"ink":"^7.1.0","tsx":"^4.19.2","react":"^19.2.7","node-pty":"1.1.0","@xterm/headless":"6.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"ink-testing-library":"^4.0.0"},"_npmOperationalInternal":{"tmp":"tmp/mission-control_1.1.11_1788121354928_0.6522969741750306","host":"s3://npm-registry-packages-npm-production"}},"1.1.12":{"name":"@bluearch/mission-control","version":"1.1.12","keywords":["claude","claude-code","tui","ink","terminal","agents","ai-agents","tmux-alternative","self-hosted"],"author":{"name":"BlueArch"},"license":"AGPL-3.0-only","_id":"@bluearch/mission-control@1.1.12","maintainers":[{"name":"bluearchgroup","email":"joel@bluearch.io"}],"homepage":"https://xxyjoel.github.io/ba-mission-control","bugs":{"url":"https://github.com/xxyjoel/ba-mission-control/issues"},"bin":{"mc":"bin/mc.mjs"},"dist":{"shasum":"dd1b9b4f3653ee9909fad7c7213d810cd79e2ae0","tarball":"https://registry.npmjs.org/@bluearch/mission-control/-/mission-control-1.1.12.tgz","fileCount":82,"integrity":"sha512-7iz8x3oY1RVZzb0iX3CK0EX3aHAnunTiaY7Zm2TbDxBTP3Br5HnuxexXcWqamjUFePm7fDCdE3kxP9B0tod2YQ==","signatures":[{"sig":"MEYCIQCHQXDnEnhWon/+Vf9oiAA/ogzNYrRL8NQd+9qfPunPugIhAKO+Qc0AGAPbGCF1xRG7UMwx5k6k4qTPCxLdqWEvEA2n","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bluearch%2fmission-control@1.1.12","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":765584},"type":"module","engines":{"node":">=20"},"gitHead":"a2cd557acc8b4aeecea1392a27d84ebb0ce9b841","scripts":{"dev":"tsx watch bin/mc.mjs","test":"node scripts/run-tests.mjs","start":"tsx bin/mc.mjs","preversion":"npm test","start:diag":"MC_HEAP_LOG=1 NODE_OPTIONS=--heapsnapshot-near-heap-limit=2 tsx bin/mc.mjs","test:watch":"MC_NO_TRANSCRIPT=1 find tests -name '*.test.*' -type f -exec node --import tsx --test --test-timeout=30000 --test-force-exit --watch {} +","dev:sandbox":"MC_CONFIG_DIR=\"$HOME/.config/claude-mc-dev\" tsx bin/mc.mjs","postinstall":"node scripts/fix-node-pty.mjs","postversion":"git push --follow-tags","verify:pack":"node scripts/verify-pack.mjs","prepublishOnly":"node scripts/prepublish-guard.mjs","dev:sandbox:watch":"MC_CONFIG_DIR=\"$HOME/.config/claude-mc-dev\" tsx watch bin/mc.mjs"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:eec374bf-4770-4b13-89e4-e7fc44ccee93"}},"repository":{"url":"git+https://github.com/xxyjoel/ba-mission-control.git","type":"git"},"_npmVersion":"11.19.1","description":"BlueArch Mission Control — keyboard-first terminal TUI to monitor & run dozens of Claude Code agents: 10 live at once, every session saved and resumable.","directories":{},"_nodeVersion":"20.20.2","dependencies":{"ink":"^7.1.0","tsx":"^4.19.2","react":"^19.2.7","node-pty":"1.1.0","@xterm/headless":"6.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"ink-testing-library":"^4.0.0"},"_npmOperationalInternal":{"tmp":"tmp/mission-control_1.1.12_1788122024293_0.2908948642282254","host":"s3://npm-registry-packages-npm-production"}},"1.1.13":{"name":"@bluearch/mission-control","version":"1.1.13","type":"module","description":"BlueArch Mission Control — keyboard-first terminal TUI to monitor & run dozens of Claude Code agents: 10 live at once, every session saved and resumable.","license":"AGPL-3.0-only","author":{"name":"BlueArch"},"homepage":"https://xxyjoel.github.io/ba-mission-control","repository":{"type":"git","url":"git+https://github.com/xxyjoel/ba-mission-control.git"},"bugs":{"url":"https://github.com/xxyjoel/ba-mission-control/issues"},"keywords":["claude","claude-code","tui","ink","terminal","agents","ai-agents","tmux-alternative","self-hosted"],"bin":{"mc":"bin/mc.mjs"},"scripts":{"start":"tsx bin/mc.mjs","start:diag":"MC_HEAP_LOG=1 NODE_OPTIONS=--heapsnapshot-near-heap-limit=2 tsx bin/mc.mjs","dev":"tsx watch bin/mc.mjs","dev:sandbox":"MC_CONFIG_DIR=\"$HOME/.config/claude-mc-dev\" tsx bin/mc.mjs","dev:sandbox:watch":"MC_CONFIG_DIR=\"$HOME/.config/claude-mc-dev\" tsx watch bin/mc.mjs","test":"node scripts/run-tests.mjs","verify:pack":"node scripts/verify-pack.mjs","preversion":"npm test","postversion":"git push --follow-tags","prepublishOnly":"node scripts/prepublish-guard.mjs","test:watch":"MC_NO_TRANSCRIPT=1 find tests -name '*.test.*' -type f -exec node --import tsx --test --test-timeout=30000 --test-force-exit --watch {} +","postinstall":"node scripts/fix-node-pty.mjs"},"dependencies":{"@xterm/headless":"6.0.0","ink":"^7.1.0","node-pty":"1.1.0","react":"^19.2.7","tsx":"^4.19.2"},"devDependencies":{"ink-testing-library":"^4.0.0"},"engines":{"node":">=20"},"publishConfig":{"access":"public"},"gitHead":"e326e70179c69732c4377e698047eb8f5f1e1de5","_id":"@bluearch/mission-control@1.1.13","_nodeVersion":"20.20.2","_npmVersion":"11.19.1","dist":{"integrity":"sha512-NisPBPHvVA7ttAg0GJTa3QjIN6/0gGbtXAahRzRxgq6Ej9Pq8sogI/fjAIdFpwU3OqgCFlyJQAjgzUYC/UT+/w==","shasum":"53a718c40fded23ef70d231905ac644b08253794","tarball":"https://registry.npmjs.org/@bluearch/mission-control/-/mission-control-1.1.13.tgz","fileCount":82,"unpackedSize":767044,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bluearch%2fmission-control@1.1.13","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCadT6tFUpdYlJ+zkoGays7+npDMNFj0e5hM9pPOkXrDwIgFEY+xqo+1nR3F4NFAQWey+b/Y0Z3p6IKUEGh0VgZzkA="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:eec374bf-4770-4b13-89e4-e7fc44ccee93"}},"directories":{},"maintainers":[{"name":"bluearchgroup","email":"joel@bluearch.io"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/mission-control_1.1.13_1788988172206_0.4102292896500124"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-17T00:20:10.753Z","modified":"2026-09-09T21:09:32.701Z","1.1.2":"2026-08-17T00:20:11.055Z","1.1.4":"2026-08-28T16:47:07.190Z","1.1.5":"2026-08-28T16:55:10.562Z","1.1.6":"2026-08-28T19:13:38.105Z","1.1.7":"2026-08-28T20:13:30.475Z","1.1.8":"2026-08-28T22:01:03.444Z","1.1.9":"2026-08-29T21:00:01.339Z","1.1.10":"2026-08-29T22:53:03.485Z","1.1.11":"2026-08-30T20:22:35.095Z","1.1.12":"2026-08-30T20:33:44.442Z","1.1.13":"2026-09-09T21:09:32.355Z"},"bugs":{"url":"https://github.com/xxyjoel/ba-mission-control/issues"},"author":{"name":"BlueArch"},"license":"AGPL-3.0-only","homepage":"https://xxyjoel.github.io/ba-mission-control","keywords":["claude","claude-code","tui","ink","terminal","agents","ai-agents","tmux-alternative","self-hosted"],"repository":{"type":"git","url":"git+https://github.com/xxyjoel/ba-mission-control.git"},"description":"BlueArch Mission Control — keyboard-first terminal TUI to monitor & run dozens of Claude Code agents: 10 live at once, every session saved and resumable.","maintainers":[{"name":"bluearchgroup","email":"joel@bluearch.io"}],"readme":"<h1 align=\"center\">BlueArch Mission Control</h1>\n\n<p align=\"center\">\n  <strong>Fleet command for your Claude Code agents.</strong><br>\n  A keyboard-first terminal TUI to monitor &amp; run dozens of <strong>real</strong> <code>claude</code> CLI agents —\n  10 live at once, every session saved and instantly resumable — with live cost,\n  context, and sub-agent tracking. No GUI. No Electron. No prefix keys.\n</p>\n\n<p align=\"center\">\n  <em>Dozens of agents, one keyboard. Open source, self-hosted, and made in the USA. 🇺🇸</em>\n</p>\n\n<p align=\"center\">\n  <a href=\"./LICENSE\"><img alt=\"License: AGPL-3.0\" src=\"https://img.shields.io/badge/license-AGPL--3.0-blue.svg\"></a>\n  <a href=\"https://www.npmjs.com/package/@bluearch/mission-control\"><img alt=\"npm\" src=\"https://img.shields.io/npm/v/@bluearch/mission-control.svg\"></a>\n  <img alt=\"Node 20+\" src=\"https://img.shields.io/badge/node-%3E%3D20-brightgreen.svg\">\n  <img alt=\"Platform: macOS | Linux\" src=\"https://img.shields.io/badge/platform-macOS%20%7C%20Linux-lightgrey.svg\">\n  <img alt=\"PRs welcome\" src=\"https://img.shields.io/badge/PRs-welcome-brightgreen.svg\">\n</p>\n\n<p align=\"center\">\n  <!-- Generated with VHS: `vhs tapes/hero.tape`. Absolute raw URL so it also renders on npmjs.com. -->\n  <img alt=\"Mission Control demo\" src=\"https://raw.githubusercontent.com/xxyjoel/ba-mission-control/main/assets/hero.gif\" width=\"800\">\n</p>\n\nThis is not a mockup. Every session is a real `claude` CLI subprocess — tokens,\ncosts, context, and git status are all measured, not simulated. It runs entirely\non your machine: **no telemetry, no network service, no account beyond your own\nClaude login.** `btop` × `lazygit` aesthetic, built on\n[Ink](https://github.com/vadimdemedes/ink) (React for terminals) with **no build step**.\n\n## Who this is for\n\n- **macOS or Linux developers using [Claude Code](https://claude.com/claude-code)** who run\n  more than one agent at a time and lose track of which one needs them.\n- People who want **fleet-level awareness** — cost, context pressure, sub-agent\n  fan-out, stuck detection, approval prompts — without babysitting each tab.\n- Terminal-first, keyboard-first users who'd rather not run a GUI app or Electron.\n- Anyone who wants their agent tooling to be **open source and self-hosted** —\n  auditable, local-only, and yours to theme.\n\n## Why not tmux or cmux?\n\nMission Control isn't a generic multiplexer or a native app — it's a purpose-built\nTUI that *understands* Claude Code agents. It happily runs **alongside** tmux and\nover SSH.\n\n| | **tmux** | **[cmux](https://github.com/manaflow-ai/cmux)** | **Mission Control** |\n|---|:---:|:---:|:---:|\n| What it is | terminal multiplexer | native macOS GUI app | terminal TUI |\n| Runs anywhere (SSH, any terminal) | ✅ | local macOS | ✅ |\n| Purpose-built for Claude Code agents | ❌ | ✅ | ✅ |\n| Per-agent cost / context / token tracking | ❌ | ❌ | ✅ |\n| Sub-agent fan-out awareness (`⋔`) | ❌ | ❌ | ✅ |\n| Approval / permission routing | ❌ | partial | ✅ |\n| Resume all active sessions | ❌ | ❌ | ✅ |\n| Broadcast slash commands to all agents | ❌ | ❌ | ✅ |\n| No prefix keys, zero config to start | ❌ | ✅ | ✅ |\n| No GUI / no Electron | ✅ | native (no Electron) | ✅ |\n| Open source & self-hosted | ✅ | ✅ | ✅ |\n\n## Quick start\n\nRequires **Node 20+** and platform build tools for the PTY layer (macOS:\nXcode Command Line Tools; Linux: `build-essential`).\n\n```sh\n# Run it now, no install:\nnpx @bluearch/mission-control\n\n# …or install the `mc` command globally:\nnpm install -g @bluearch/mission-control\nmc\n```\n\n> **npm ≥ 11.19 (allow-scripts):** newer npm blocks packages' install scripts\n> until you allowlist them. mc self-heals at boot if its postinstall was\n> skipped, but for a fully clean install allow the three that matter once:\n> `npm config set allow-scripts=@bluearch/mission-control,node-pty,esbuild --location=user`\n> (mc fixes node-pty's spawn-helper permissions; node-pty builds its native\n> PTY binding; esbuild fetches the binary `tsx` runs on.)\n\n> Installing from the repo also works (`npx github:xxyjoel/ba-mission-control`)\n> and tracks `main` instead of the released version. A Homebrew tap is planned.\n\nPrefer to hack on it? See [From source](#from-source) below and\n[CONTRIBUTING.md](./CONTRIBUTING.md).\n\n## Themes\n\n<p align=\"center\">\n  <img alt=\"Cycling themes live — Tokyo Night, Gruvbox, Catppuccin, Amber, Matrix\" src=\"https://raw.githubusercontent.com/xxyjoel/ba-mission-control/main/assets/themes.gif\" width=\"800\">\n</p>\n\nSix built-in palettes ship out of the box — **BlueArch** (default), Tokyo Night,\nGruvbox Dark, Catppuccin Mocha, Solarized Dark, Amber (CRT), and the\ngreen-phosphor **Matrix** theme. Switch live with `:theme <name>` (e.g.\n`:theme matrix`) or in Settings → Colors. Self-hosted means it's yours to\nre-theme — palettes live in `tui/lib/themes.js`.\n\n## Requirements\n\n- macOS (uses POSIX signals for pause/resume — Linux works too; Windows untested)\n- Node 20+\n- `claude` CLI on `$PATH` (Claude Code 2.x). Verify with `claude --version`.\n- A signed-in account — either `ANTHROPIC_API_KEY`, or a stored OAuth\n  session from `claude auth login`. Mission Control runs `claude auth\n  status` on startup and prints the account it found:\n\n  ```\n  [mc] claude: 2.1.142 (Claude Code)\n  [mc] auth · you@example.com · max plan · claude.ai\n  ```\n\n  The same info shows as a chip in the top-right of the header strip and\n  can be re-probed at any time with `:whoami` (alias `:auth`). If the\n  banner says `not signed in`, quit and run `claude auth login`.\n- `git` on `$PATH`\n- A terminal that supports 24-bit color + Unicode box-drawing (modern macOS\n  Terminal / iTerm2 / Alacritty / Ghostty / wezterm all qualify)\n\n## From source\n\nFor contributors, or to run the latest `main`:\n\n```sh\ngit clone https://github.com/xxyjoel/ba-mission-control.git\ncd ba-mission-control\nnpm install\nnpm start\n```\n\nThe TUI takes over the terminal. To leave, press `q` (or `Ctrl-C`).\n\nAfter `npm install`, you can also invoke the bin directly:\n\n```sh\n./bin/mc.mjs\n```\n\n…or link the `mc` command globally:\n\n```sh\nnpm link\nmc\n```\n\nConfigurable env vars:\n\n| Var            | Default        | Effect |\n| -------------- | -------------- | ------ |\n| `CLAUDE_BIN`   | `claude`       | Path to the `claude` CLI |\n| `REPO_PARENTS` | see `server/repos.mjs` | Colon-separated parent dirs scanned for the New Session repo picker. Recursive to depth 3. Overridden by a folder chosen in-app via `:repos` (which persists to settings). |\n| `MC_MOCK`      | _unset_        | Fixture name (e.g. `approval-request`) — when set, every launched session replays a JSONL fixture from `server/fixtures/` instead of spawning a real `claude` subprocess. Use for deterministic Zoom UX iteration without API spend. |\n| `MC_HEAP_LOG`  | _unset_        | When set, logs memory (rss/heap) + per-structure counts every 60 s to `~/.local/state/claude-mc/heap/` for diagnosing long-uptime memory growth. Independent of this, `kill -USR2 <mc pid>` writes a heap snapshot on demand (owner-only, `0600`). Off by default. |\n\nSettings (theme, density, grid columns, **max windows per pane**, ctx\nthreshold, etc) persist to `~/.config/claude-mc/settings.json` — open with\n`Esc` in the TUI.\n\n**Card anatomy.** Each grid tile is a pure-stats dashboard (fixed height, no\nwrapping): title + status, model + branch + git, ctx bar, tok/min sparkline\n(empty at zero throughput) with the claude subprocess's live **CPU% + memory**\nright-aligned (`4% 182M`, one shared `ps` sample per fleet tick), a\n**triage row** (`▸ 5/7 ██████░░ <next action>` — todo burndown from the\nsession's live `TodoWrite` list plus a status-driven next-action verb; blank\nwhen there's nothing actionable — status and its duration already live in the\ncard's corners), the **current item** (`↳ <in-progress todo>`), session vitals\n(small `●<score>` health dot + turns / messages / uptime / time-in-state), and\na cost + tokens foot. It intentionally shows **no session text** — read the\nrunning conversation by zooming (`↵`) or in the fleet log.\n\nThe triage row answers the scan-10-cards question \"does this need me, when, and\nwhat next\": `check back` (working), `ready to review →` (idle, plan complete),\n`needs a nudge →` (idle, plan unfinished), `needs input · answer to continue`\n(waiting) — colored by urgency.\n\n**Paging.** The grid shows at most **Max windows per pane** cards at once\n(Settings → LAYOUT, `windowsPerPane`, default 9 — a 3×3); when more sessions\nare live, or the terminal is too short to fit them, the extra cards spill onto\nadditional panes instead of being clipped. `[` / `]` switch panes; the active\npane follows the focused card.\n\n## What's actually running\n\nEach launched session is a long-lived `claude --print --input-format stream-json\n--output-format stream-json` subprocess pinned to one slot. The fleet manager\nwrites user prompts as JSON-lines to stdin, reads JSON-line events from\nstdout, and maps them onto UI state:\n\n| stream-json event | UI effect |\n| --- | --- |\n| `system.init` | session attached; appended to tail |\n| `stream_event.content_block_delta` (text) | live activity line during a turn |\n| `assistant` (text) | activity line + log entry, usage → tokens, `input_tokens` → context |\n| `assistant` (tool_use) | log entry `▸ <tool>: <summary>` |\n| `user` (tool_result) | log entry `← tool_result <preview>` |\n| `result` | `total_cost_usd` → cost, status back to `idle` |\n\nVerifiability — every action is observable:\n\n- The **per-session log tail** (shown in the zoom overlay and merged into the\n  fleet log) records every spawn / SIGSTOP / SIGCONT / user message / tool call\n  / tool result / turn-complete event with timestamps. (The grid **card** is a\n  pure-stats tile — it no longer renders the tail; see *Card anatomy* below.)\n- The **fleet log pane** at the bottom is a chronological merge of all live\n  agents' tails — like `tail -f` over the whole fleet. Its height is exactly\n  the *Fleet log lines* setting (clamped only on terminals too short to fit).\n- **Costs** are summed from the `total_cost_usd` field claude emits at the\n  end of each turn — not estimated client-side.\n- **Context window** (`ctx`) tracks the *main thread* only: sub-agent (Task)\n  turns carry `isSidechain` and are excluded so the gauge never dips to a\n  sub-agent's smaller context mid-turn. `in` / `out` / `cost` are cumulative\n  session totals (sub-agent spend included) and **reset on `/clear`** alongside\n  `ctx`; they persist across a relaunch only through a proper save & quit.\n- **Tokens `in` counts fresh input only** (`input_tokens + cache_creation`).\n  With prompt caching on, claude re-reads the whole context window from cache on\n  *every* assistant message, so `cache_read_input_tokens` re-counts the same\n  tokens each turn and, if summed into `in`, dwarfs it ~100× (e.g. 67M \"in\" on a\n  60k window). Those re-reads are broken out as **`cache`** (shown in Zoom,\n  billed at the 0.1× cache-read rate in `cost`). `ctx` still reflects the full\n  live window (fresh + cache read), which is the real size on the wire.\n- **Parallel sub-agents** (`Task` / `Workflow` fan-out) are surfaced on the\n  card: when any are in flight the current-item row shows `⋔{n}` (the count, or\n  the single agent's label), and Zoom lists each with its elapsed time. The\n  server pairs each `tool_use` with its returning `tool_result` to know what's\n  live. STUCK is suppressed while sub-agents run — they work on sidechains the\n  main thread can't see, so the parent's activity clock legitimately goes quiet.\n- **Sub-agent token + cost consumption is counted.** Sub-agent turns are written\n  to a separate tree (`<sessionId>/subagents/agent-*.jsonl`) the main tailer\n  never reads, so their tokens, `cost`, and **tok/min** used to be invisible — a\n  fan-out session read near-zero throughput. A dedicated sidechain tailer folds\n  that usage into the parent's `in` / `cache` / `out` / `cost` / tok-min (not\n  `ctx` — sidechains keep their own window). On resume, pre-existing sub-agent\n  files are primed at EOF so historical spend isn't double-counted.\n- **Git state** (branch, dirty count, ahead/behind) is read via `git` calls\n  in the session's cwd on launch and after each turn.\n\n### Session states\n\nEach agent has exactly one status from this six-value enum (`server/agent.mjs`):\n\n| State     | Meaning |\n| ---       | --- |\n| `idle`    | attached, no current activity |\n| `working` | actively processing — streaming text, calling tools, thinking |\n| `waiting` | model delivered a prompt awaiting user approval (a.k.a. \"needs input\") |\n| `paused`  | process frozen via SIGSTOP (user pressed pause) |\n| `error`   | crashed or API failure — auto-restart may retry |\n| `empty`   | slot is vacant, no agent assigned |\n\nThe fleet header (`Header.jsx`) shows live counts for `work / wait / paused\n/ idle / err` plus the over-context-threshold count and an aggregate\n`NOMINAL / AWAITING / DEGRADED` pill.\n\n**Not states (derived indicators):**\n\n- `STUCK Nm` — a red chip on the card when an agent is `working` or\n  `waiting` AND has been silent for ≥ 5 minutes (`agent.stuckMin`). Not a\n  status — the underlying state is still `working`/`waiting`.\n- `ctx high` / `ctx full` — derived from `agent.context` vs the\n  warn-band / threshold settings, not from `status`.\n- **Session Health dot** — when the optional\n  [Session Health Benchmark](https://github.com/) Stop hook is installed, mc\n  reads each project's `.project-health/history.jsonl` and shows a small\n  `●<score><trend>` dot at the left of the card's vitals row (colored by\n  verdict: green HEALTHY / cyan STABLE / yellow / red), plus a `health` segment\n  on the zoom stats line. The dot is simply omitted until the project has logged\n  a scored turn; mc only *reads* the score, it doesn't compute it\n  (`tui/lib/projectHealth.js`). The untrusted verdict *string* is no longer\n  rendered on the card at all — only the numeric score — so it carries no\n  terminal-escape surface (zoom still shows the verdict word, `humanize()`d).\n\n**Status accuracy — hook-driven source of truth.** The session JSONL has no\npermission-prompt event and marks `end_turn` *mid-work*, so a JSONL-only status\nmisreads: it shows `working` when the agent is actually blocked on a tool-approval\nprompt, or `idle` mid-turn. mc fixes this by injecting Claude Code's own lifecycle\nhooks into every spawned session (`--settings` → `server/hooks/emit-status.mjs`\nwrites `PreToolUse` / `Notification` / `Stop` events to\n`~/.local/state/claude-mc/status/<sid>.ndjson`; `server/statusHookTailer.mjs`\ntails it). `PtyAgent.toJSON` then derives status from those events:\n\n- `Notification:permission_prompt` → **`waiting`** (the connector is blind to\n  prompts — this is the \"working when it's actually asking for input\" fix);\n- `PreToolUse` → **`working`**, held until `Stop` (covers the mid-turn `end_turn`\n  flash without terminal scraping);\n- `Stop` / `Notification:idle_prompt` → **`idle`**, winning over a stale connector\n  `working` (compared against a JSONL-only clock, `lastConnectorTs`, so Claude's\n  constantly-repainting TUI can't keep a finished session pinned to `working`).\n\n`detectApprovalPrompt` is kept as an **instant-INPUT fast-path** while a tool is\noutstanding (the `permission_prompt` hook is delayed ~10–20s). Sessions with **no\nhook events yet** — the legacy `FLEET_USE_PTY=0` `Agent`, or a PTY session before\nits first event — fall back to the older terminal-scrape overlay\n(`detectWorking` / `detectApprovalPrompt` in `server/ptyAgent.mjs`). Hooks are\nauto-injected; nothing to configure. *(Takes effect per session on next spawn —\na session already running when mc updated keeps its old status source until\nrelaunched.)*\n\n**Session summaries** (`/compact`, `/compact-restart`) are manual,\nuser-invoked actions — they're not bound to a state transition. There is\nno automatic stage-bound summary today.\n\n## Hotkeys\n\n| Keys | Action |\n| --- | --- |\n| `← ↑ ↓ →`  (or `h j k l`) | Move focus across the grid |\n| `↵` | Zoom focused session — or open New Session if nothing is live |\n| `1`–`9`, `0` | Jump to slot 1–10 (slots 11+ via arrow nav or `:goto <slot>`) |\n| `[` / `]` | Switch to the previous / next **pane** — only when the grid pages (see LAYOUT → *Max windows per pane*) |\n| `Esc` | Open settings menu (or close current overlay) |\n| `,` | Settings menu |\n| `B` | Broadcast modal — types the message into each targeted session **and submits it** (no manual Enter per session) |\n| `D` | Fleet dashboard — sortable one-row-per-slot table for at-scale triage |\n| `n` / `N` / `Ctrl+N` | New session — appended below the last active card (fills a killed-slot hole only when there's no room to append) |\n| `P` / `R` | Pause (SIGSTOP) / Resume (SIGCONT) |\n| `K` | Kill — armed by first press (3s window); confirms on second `K`. `:kill <slot>` follows the same arm/confirm flow; `:kill! <slot>` bypasses. |\n| `A` | Approve — send a generic \"continue\" message to a focused session (useful when an agent stalls asking for confirmation) |\n| `Shift+Tab` | Cycle focused session's permission mode: `plan → auto → acceptEdits` |\n| `!` | Open shell overlay — a persistent `$SHELL` pane for `aws sso login`, `git`, `kubectl`, etc. |\n| `?` | Help |\n| `/` | Filter — type a substring (matches name/branch/model/status); non-matches dim. Press `/` again to clear. |\n| `:` | Command bar (see below) |\n| `Q` | Quit — opens a confirm with an explicit choice: **`[s]` save & quit** (or `Enter`) vs **`[d]` quit, no save** vs `[n]`/`Esc` cancel |\n| `Ctrl-C` | Quit immediately (treated as **no save** — see below) |\n\n### Session save / restore\n\n**Save is opt-in; every other exit is a \"clear.\"** Only a proper **`[s]` save &\nquit** preserves the live conversations *and* their token/cost totals so\n`:resume-all` can rehydrate them with `claude --resume`. Any other exit — `[d]`\nquit-no-save, closing the terminal (SIGHUP), `Ctrl-C`, or a crash — records only\nthe open repo **locations**: `:resume-all` then reopens those repos as **fresh**\nsessions (no history, in/out/cost reset to 0). Per-slot crash recovery during a\nrunning session still resumes the conversation.\n\n| Command | What it does |\n| --- | --- |\n| `:resume-all` | Restart the slots that were **open when mc last closed**. After a *save* quit each is rehydrated via `claude --resume` (conversation + totals restored); after any non-save exit each reopens **fresh** in its repo. The toast reports `resuming N · M fresh`. Killed/closed slots are excluded. On boot, a toast surfaces this if records exist. |\n| `:resume <slot> [slot ...]` | Restore **specific** slots — e.g. `:resume 1 3 5` or `:resume 1,3,5`. Falls back to the focused slot with no args. |\n| `:history [n]` | **View-only** browse of the last N sessions for historical reference. Never bulk-restores (by design). |\n| `:sessions` (alias `:ls`) | List saved sessions (`bySlot`) for the current resumable set. |\n| `:forget <slot>` | Drop one slot's saved state. |\n\nConfigurable in Settings → GENERAL:\n\n- **Auto-resume sessions on startup** (`autoResumeOnStart`, default off) —\n  when on, mc runs `:resume-all` implicitly at boot.\n- **Session history limit** (`sessionHistoryLimit`, default 20) — how many\n  sessions the LITE history (`:history`) remembers.\n\n### Zoom (focused session)\n\n<p align=\"center\">\n  <img alt=\"Zoom view — scrollback, stats and tools panels, live diff\" src=\"https://raw.githubusercontent.com/xxyjoel/ba-mission-control/main/assets/zoom.gif\" width=\"800\">\n</p>\n\n`↵` on a live card opens the Zoom view. Claude's own \"update available\" banner\nis lifted out of the body and shown as a discrete `⬆ update` chip on the right\nof the zoom header so it doesn't encroach on the conversation — toggle with\n**Hide claude update banner in zoom** (`hideClaudeUpdateBanner`, default on) in\nSettings → LAYOUT. Keys available there:\n\n| Keys | Action |\n| --- | --- |\n| `/` | Type a slash command — autocomplete dropdown appears above the composer. `Tab` fills the highlighted name (keeping any args you've typed); `↵` runs it. See below for the catalog. |\n| `⌥↵`  ·  `Ctrl+J` | Newline in composer (plain `↵` submits) |\n| `↑` / `↓` | Recall prior submitted prompt (history nav in composer) |\n| `Ctrl+Y` | Enter **scroll mode** — view scrollback without forwarding keys to claude (the embedded session owns the screen, so mc brackets a dedicated mode rather than fighting it for arrow keys) |\n| `w` / `s` _(scroll mode)_ | Scroll one line back / forward through history |\n| `f` / `b` _(scroll mode)_ | Scroll half a page up / down |\n| `g` / `G` _(scroll mode)_ | Jump to the oldest / newest (live) line |\n| `Esc` _(scroll mode)_ | Exit scroll mode (any other key also exits, returning input to claude) |\n| `Ctrl+U` | Expand / collapse the stats panel (defaults to a compact one-liner) |\n| `Ctrl+K` | Show / hide tool-call events in the log |\n| `Ctrl+Q` | **Close the zoom view** |\n| `Esc`, `Ctrl+T`, `Ctrl+S`, `Shift+Tab` | **Forwarded to claude** — its own cancel/back-out, todos, stash, and permission-mode cycle. mc no longer shadows these (chrome keys are `Ctrl+Q/Y/K/U`, all unused by claude). |\n\n#### Slash commands (in zoom composer)\n\nThese are handled **client-side** — they don't round-trip to the `claude` subprocess (stream-json non-interactive mode doesn't parse slash commands). Everything except `/quit` routes through the same dispatcher that powers the `:cmd` command bar, so the two surfaces share their handler table.\n\n| Command | Effect |\n| --- | --- |\n| `/help` | Open the keymap reference |\n| `/cost` | Toast this session's running cost ($ session + $ week) |\n| `/usage` | Show plan-side rate-limit usage (5h + 7d) |\n| `/perm <mode>` | Change this session's permission mode |\n| `/note <text>` | Drop a local annotation in the chat log |\n| `/approve` | Send a generic \"continue\" reply (same as the `A` hotkey) |\n| `/pause` / `/resume` | SIGSTOP / SIGCONT this session |\n| `/kill` | Terminate this session (SIGTERM) |\n| `/quit` (or `/exit`) | Close the zoom view (same as `Ctrl+Q`) |\n\nA message that doesn't start with `/` is sent through to claude unchanged — slash dispatch only fires on leading-`/` inputs.\n\n### Shell overlay (`!`)\n\nPress `!` from the grid or a focused card to open a persistent `$SHELL` pane — useful for `aws sso login`, `git`, `kubectl`, and any other shell chore that would otherwise force you out to a separate terminal.\n\n- **Open:** `!` (from FleetView or a focused card)\n- **Close:** `Ctrl+Q` — returns you to the grid. Every other key (including `Ctrl+K`, `Ctrl+U`, `Ctrl+J`) forwards straight to the shell.\n- **Keep-warm:** the shell process is long-lived. It survives close/reopen — your history, cwd, and any in-flight `aws sso` device-flow are preserved across toggles. The shell is killed only on app shutdown.\n- **Focused-card `cd`:** when you open the overlay from a focused card, the shell automatically `cd`s into that card's working directory so `git` and `aws` operate on the right repo. Opening from FleetView (no focused card) leaves the shell wherever it last was.\n\nThe overlay chrome matches the Zoom modal: `shell · <$SHELL> · <cwd>` in the header, `⌃Q close · all other keys → shell` in the footer.\n\n### Command bar\n\n| Command | Effect |\n| --- | --- |\n| `:theme <name>` | Cycle palette (any substring match) |\n| `:cols 3\\|4\\|5` | Change grid columns |\n| `:perm <mode>` | Set default permission mode (`default`, `acceptEdits`, `bypassPermissions`, `plan`) |\n| `:model` | Show the focused session's requested vs. resolved model |\n| `:model <id>` | Switch the focused session's model live (restarts the subprocess) |\n| `:model default <id>` | Set the fleet default model for new launches |\n| `:model refresh` | Programmatically probe the live model catalog — see [Model catalog](#model-catalog) |\n| `:kill [slot]` | Kill focused (or specified) session |\n| `:pause` / `:resume` | SIGSTOP / SIGCONT the focused session |\n| `:approve` (or `:a`) | Same as the `A` hotkey |\n| `:resume [slot]` | Rehydrate the saved session in this slot from disk via `claude --resume` |\n| `:sessions` | Show saved sessions (toast) |\n| `:forget <slot>` | Drop the saved session for a slot |\n| `:repos` | Open the folder picker to choose where repos are scanned. The chosen folder **replaces** the built-in defaults. `:repos clear` resets to defaults. |\n| `:update` | Report **claude version drift**: the on-disk `claude --version` vs the version each live session actually launched on (running processes keep their old binary until restarted). Drifted slots get a fleet-log line; converge by quit+relaunch. |\n| `:whoami` (or `:auth`) | Re-probe `claude auth status` and surface email + subscription |\n| `:usage` | Re-read plan-side rate-limit telemetry (`5h` and `7d` quota %) |\n| `:note <text>` (or `:n`) | Inject a local annotation into the focused session's chat log (not sent to claude) |\n| `:slack <url>` | Set the Slack incoming-webhook URL. `:slack clear` removes it. |\n| `:feedback <msg>` | Send feedback to Slack (includes auth + fleet + plan-usage context) |\n| `:request <msg>` | Send customer request to Slack |\n| `:quit` | Exit |\n\n## Model catalog\n\nThe static table in `tui/lib/models.js` is only the **verified pricing\nbook** — models are never hand-added to it. Each entry maps a friendly id\n(`opus-4.8`, `sonnet-4.6`, …) to the CLI model name passed to\n`claude --model`, plus display metadata (context window, per-MTok pricing,\ncolour). Every selector (Settings → GENERAL, the NewSession `←/→` cycler,\n`:model` validation) reads the **live** catalog via `modelIds()`, so models\ndiscovered at runtime are immediately selectable. The default for new\nsessions is **`opus-4.8`** (1M-token context); change it in Settings →\nGENERAL or with `:model default <id>`.\n\n### Automatic discovery\n\nModels are never hand-added — mc syncs from two live sources on boot, in the\nbackground:\n\n1. **Models API inventory** (`GET /v1/models` — the endpoint the Anthropic\n   SDK's `client.models.list()` wraps). Free, complete, and diffed against\n   mc's catalog on every boot when an API credential is in the environment\n   (`ANTHROPIC_API_KEY` or `ANTHROPIC_AUTH_TOKEN`): unknown models are\n   added, known models' real context/output limits are updated, and models\n   the API no longer serves are marked retired (kept for cost history).\n   Skipped silently on claude-CLI-only logins, where no env credential\n   exists.\n2. **claude CLI alias probe** — resolves what `opus` / `sonnet` / `haiku`\n   point at *today* (which the API can't say) and covers credential-less\n   installs. Each probe is a billed turn, so it fires only when\n   `claude --version` differs from the version stamped in\n   `models-cache.json`. That's how Opus 5 appeared with zero code changes\n   when v2.1.220 shipped. Failed probes don't stamp, so discovery retries\n   next boot. A brand-new model *family* (its own alias) reaching\n   credential-less installs needs a one-string addition to `KNOWN_ALIASES`\n   in `tui/lib/modelProbe.js` — the API path needs nothing.\n\nThe **default model is `auto`**: new sessions launch on the newest Opus in\nthe live catalog, so a freshly discovered release becomes the default the\nmoment it lands — no pinned id anywhere. Pin an explicit model in Settings\n(or `:model default <id>`) if you'd rather stay put.\n\n**Sandboxed runs** (`MC_CONFIG_DIR` set — dev sandboxes, tests) skip boot\ndiscovery so throwaway configs never trigger billed probes. To make a\nsandbox behave like a real first-run install — e.g. when trying the\npublished package as a user would — set `MC_SYNC_MODELS=1`:\n\n```sh\nMC_SYNC_MODELS=1 MC_CONFIG_DIR=$(mktemp -d) mc\n```\n\n### Manual refresh (`:model refresh`)\n\nThe `claude` CLI has no \"list models\" command, so mc learns what an alias\nresolves to — and its real context window — by running a one-shot query\nand reading the `modelUsage` block of the JSON result:\n\n```sh\nclaude -p --model opus --output-format json 'hi'\n# → \"modelUsage\": { \"claude-opus-4-8\": { \"contextWindow\": 1000000, … } }\n```\n\n`:model refresh` runs that probe for `opus` / `sonnet` / `haiku`\nconcurrently, then:\n\n- **updates** the context window of any catalog model whose CLI name matches\n  (so per-card ctx% is computed against the true window), and\n- **discovers** any newly-shipped model an alias now points at, adding it to\n  the catalog (pricing inherited from the same family and flagged as\n  estimated until confirmed).\n\nEach probe is a **real, billed turn** (~$0.10–0.15, ~2s), so probes run\nonly on manual `:model refresh` or a detected claude version change (above)\n— never on a steady-state boot. The result is cached to\n`~/.config/claude-mc/models-cache.json` (stamped with the CLI version) and\noverlaid onto the static catalog **offline** on every boot. A discovered\nmodel's pricing is inherited from its newest same-family sibling and\nflagged `estimatedPricing` until a verified row lands in the pricing book.\n\n## Slack feedback / customer requests\n\nIf you set an [incoming-webhook URL](https://api.slack.com/messaging/webhooks)\nvia `:slack <url>`, then `:feedback <message>` and `:request <message>`\nwill POST a structured payload to that channel — auto-tagged with your\naccount email, current fleet state (live sessions, models, branches),\nand plan-side usage (5h / 7d %). The webhook URL is stored in\n`~/.config/claude-mc/settings.json` and never displayed in the UI;\nSettings → FEEDBACK shows only whether one is configured.\n\n## Plan-side usage\n\nThe aggregate strip shows the same numbers Claude Code's `/usage` slash\ncommand reports — read from `~/.claude/abtop-rate-limits.json`:\n\n```\nplan  5h 1% ↻4h 50m  ·  7d 29% ↻2d 19h\n```\n\nThese are **plan-side** percentages (the actual Anthropic quota), not\njust what Mission Control has observed. The file is rewritten by every\n`claude` invocation on the machine; we re-read it every 8 seconds. The\nlocally-tracked `cost·week` figure to the left of it is independent —\nit's the rolling $ spend Mission Control itself has booked.\n\nInside modals, `Esc` closes and `Tab` cycles fields.\n\nErrors and confirmations (launch failures, broadcasts, command results)\nappear as transient toasts above the status bar.\n\n## Project layout\n\n```\nbin/\n  mc.mjs              CLI entry — registers tsx JSX loader, boots tui/main.jsx\ntui/\n  main.jsx            Boot: constructs Fleet, renders <App/>, wires shutdown\n  App.jsx             Top-level: hotkeys, focus, modal routing, fleet sub\n  Header.jsx          Top status strip\n  Aggregate.jsx       Token/cost line + week budget bar + fleet sparkline\n  Card.jsx            One agent tile\n  FleetLog.jsx        Bottom pane — aggregated activity stream\n  StatusBar.jsx       Vim-style status bar\n  modals/\n    Help.jsx          Keymap reference\n    Broadcast.jsx     Send one prompt to N sessions\n    NewSession.jsx    Single-input launcher: fuzzy-match recents or type a path\n    Settings.jsx      btop-style settings (tabs: GENERAL / LAYOUT / COLORS / ALERTS / SAFETY / NOTES)\n    Zoom.jsx          Single-session detail (full tail + ctx bar + msg input)\n    RepoPicker.jsx    Filesystem browser to choose the repo scan folder (:repos)\n  lib/\n    themes.js         6 palettes (BlueArch / Tokyo Night / Gruvbox / Catppuccin / Solarized / Amber)\n    format.js         bar / sparkline / fmtK / fmtMoney / trunc / fmtClock\n    models.js         Claude model metadata (label, maxCtx, kind, costs)\n    settings.js       Schema + defaults + on-disk persistence\n    TextField.jsx     Minimal single-line input (Ink has none built-in)\nserver/\n  fleet.mjs           10-slot fleet manager + pub-sub (EventEmitter)\n  agent.mjs           One claude subprocess wrapper (stream-json I/O)\n  mockAgent.mjs       Fixture-driven Agent stand-in for UX iteration (MC_MOCK)\n  fixtures/           JSONL fixtures replayed by MockAgent\n  git.mjs             branch / dirty / ahead-behind via `git`\n  repos.mjs           Recursive repo scanner for the New Session picker\n```\n\n## Mock mode\n\nFor iterating on Zoom UX without burning real `claude` sessions, set\n`MC_MOCK=<fixture>` before launching. Every session opened in the TUI\nwill then replay the named fixture from `server/fixtures/` instead of\nspawning a real subprocess. Available fixtures:\n\n| Fixture            | Exercises |\n| ------------------ | --------- |\n| `quick-reply`      | text-only assistant turn |\n| `tool-loop`        | assistant → tool_use → tool_result → assistant |\n| `long-thinking`    | extended-thinking block + streamed answer |\n| `approval-request` | tool approval banner; pauses at `waiting`, resumes when you reply (`a`/`r` in zoom) |\n\nExample:\n\n```sh\nMC_MOCK=approval-request npm start\n```\n\nDrop a new fixture by adding `server/fixtures/<name>.jsonl`. The\ndirective schema is documented at the top of `server/mockAgent.mjs`.\n\n`server/` is the data layer — it has no Express anymore. The TUI reads\n`fleet.snapshot()` directly and subscribes to `fleet.on('change', …)`.\n\n## Permission mode\n\nSessions default to `acceptEdits` — claude can read and edit files in the\nsession cwd without prompting, but still blocks unsafe bash. Valid values are\n`default`, `acceptEdits`, `bypassPermissions`, `plan`. Configurable in the\nSettings menu (`Esc`) under **GENERAL → Default permission mode**, or via the\ncommand bar with `:perm <mode>`. The mode is swappable mid-session (which is\nwhy it isn't part of the launcher anymore). `bypassPermissions` removes all\nguardrails — only use it in trusted, scratch directories.\n\n## Resume previous sessions\n\nEach running session is mirrored to `~/.config/claude-mc/sessions.json`\nalong with the claude session UUID. To pick up where you left off:\n\n- **From the command bar** — `:resume <slot>` rehydrates in place.\n- **List what's saved** — `:sessions`. Drop a record with `:forget <slot>`.\n\nUnder the hood we call `claude --resume <session-id>` against the same\nworking directory. claude restores the prior transcript from its own\non-disk session store.\n\n## Quality-of-life features\n\nBuilt on top of the reliability layer below.\n\n### Fleet dashboard (`D` or `:dash`)\n\n<p align=\"center\">\n  <img alt=\"Fleet dashboard — sortable table of all live agents\" src=\"https://raw.githubusercontent.com/xxyjoel/ba-mission-control/main/assets/dashboard.gif\" width=\"800\">\n</p>\n\nA single-screen table — one row per live agent — for triage when you\nhave more than a handful of slots running. Columns: slot, name, model,\nstatus pill, ctx %, tok/min, $ session, age, activity. Sortable:\n\n- `S` cycles the sort column (`slot → status → ctx → tpm → cost → age`)\n- `R` reverses direction\n- `↑` `↓` moves the highlight (the focused slot tracks)\n- `↵` zooms the highlighted slot\n- `D` or `esc` closes\n\nThe header also surfaces today's fleet spend and the configured budget\nwhen one is set — so \"how much have I spent today?\" is one keystroke\nfrom any view.\n\n### Cost guardrails — `:cap` and `:budget`\n\nTwo tiers of cost protection, both off by default (set the values to\nopt in):\n\n- **Per-slot cap** — `:cap <slot> <usd>` rejects further user messages\n  to that slot once its `costSession` crosses the cap. `:cap default\n  <usd>` persists a fleet-wide default applied to every new launch.\n  Raise the cap with the same command to continue (`:cap 3 10` bumps\n  slot 3 to $10).\n- **Daily budget** — `:budget <usd>` blocks NEW launches once today's\n  fleet-wide spend exceeds the cap. Existing sessions keep running.\n  Use `:budget 0` to disable. `:budget` alone shows today's spend.\n\nBoth also have form-editable rows under the SAFETY section of the\nsettings menu (`,` or `esc`).\n\n### Session templates — `:template <name>`\n\nPre-configured bundles of N session launches with model / permission /\nprompt baked in. Bundled defaults at first launch (auto-written to\n`~/.config/claude-mc/templates.json`):\n\n- **`review`** — 3 sessions (Opus architecture + 2 Sonnets) reviewing\n  the same repo in plan mode.\n- **`explore`** — 2-session parallel exploration: Opus deep + Sonnet\n  fast.\n- **`spec-then-implement`** — 2 sessions: Opus writes a spec in plan\n  mode; Sonnet implements in acceptEdits.\n\nUsage:\n\n```sh\n:template                       # list available templates\n:template review                # launch into next N empty slots, using\n                                # focused agent's cwd (or process.cwd)\n:template review ~/my-other-repo\n```\n\nEdit `~/.config/claude-mc/templates.json` to add your own.\n\n### `@file` mention autocomplete (in Zoom composer)\n\nType `@` followed by a partial filename to summon a dropdown of files\nand folders under the session's working directory:\n\n- `↑` `↓` navigates\n- `Tab` or `↵` accepts (folders get a trailing `/` so you can keep\n  descending — same UX as the path autocomplete in New Session)\n- Suppressed while the slash dropdown owns the input — the prefixes\n  (`@` vs `/`) are distinct so they never need to coexist\n\nUseful for \"look at @src/auth/oauth.ts\" without copy-pasting from\nanother terminal.\n\n## Reliability features\n\nThese guard against the failure modes that hit hardest at fleet scale.\n\n- **Quiet at idle (battery-friendly).** An idle fleet coalesces all of its\n  background polling into a single shared wakeup (stretching to 3s when\n  nothing is working), gates the invisible command-bar caret, and detects\n  transcript rotation with one directory stat instead of a per-file sweep.\n  Measured: idle CPU cut ~3× (1.07% → 0.35% of one core, zero agents);\n  reproduce with `node scripts/measure-idle.mjs`. Renders, statuses, and\n  keystroke latency are unchanged — active sessions keep full cadence.\n- **Status truth, regression-proofed.** A card blocked on a question\n  (`AskUserQuestion` / plan approval) reads **INPUT** for the entire wait —\n  the hook channel goes silent while an ask is pending, and the pending\n  prompt now outranks the last \"tool running\" signal however stale it gets.\n  Every real status incident is checked in as a recording under\n  `tests/fixtures/status-corpus/` and replayed through the live pipeline\n  (`tests/status.replay.test.mjs`) with virtualized time, so a fixed lie\n  stays fixed.\n- **Press-K-twice to kill.** A first press arms the kill action for 3\n  seconds and shows a warning toast; the second `K` confirms and SIGTERMs\n  the subprocess. Eliminates the most painful misfire (accidental loss of\n  work). The `:kill` command bar entry follows the same arm-then-confirm\n  flow; `:kill!` (bang) bypasses for explicit batch use.\n- **Auto-restart on transient errors.** When a `claude` subprocess exits\n  unprompted with a non-zero code, the slot retries up to **3 times** with\n  widening backoff (2s → 5s → 15s), restarting via `--resume <session-id>`\n  so the in-progress conversation is preserved. The retry counter resets on\n  the next successful `init` event, so a recovered slot is eligible again\n  on its next independent failure. After 3 failed restarts the slot enters\n  the errored state with `K clears slot` hint.\n- **Quit can't stall.** `q → save` SIGTERMs every child, gives them a 1.5s\n  grace, SIGKILLs any straggler, and exits — a claude wedged on a permission\n  prompt can no longer hold mc's shutdown hostage (previously the quit hung\n  until a force-close, which orphaned that child into claude's daemon).\n- **Background-agent claim detection.** If a session can't resume because\n  claude's daemon holds it as a background agent (the usual leftover of a\n  force-closed terminal), the card says exactly that — with the fix\n  (`claude agents` → stop the orphan, then `:resume` the slot) — instead of\n  silently burning restart attempts on a failure that can't succeed.\n- **Stuck-detection.** When a slot is in `working` or `waiting` status but\n  hasn't emitted any event from the subprocess in the last 5 minutes, the\n  card shows a red `STUCK Nm` chip and a one-shot toast fires (`slot N ·\n  stuck 5m · no events while working`). Re-arms automatically when the\n  slot starts emitting again.\n- **Context-pressure toasts.** Crossing 80% / 90% of the model's max\n  context fires a yellow / red toast (`slot N · context 80% · consider\n  /compact`). Each crossing is fired exactly once; the trigger re-arms\n  when the slot drops back under the threshold (post-compaction).\n- **Title-row context chip.** When the slot's context is above the warn\n  threshold, the status pill on the card title gets a `· 89%` chip in the\n  appropriate urgency color (yellow near, red over). Pairs with the toast\n  but stays visible at-a-glance.\n- **Per-session transcript on disk.** Every inbound `claude` event and\n  every outbound user message is appended as a JSONL line to:\n\n  ```\n  $XDG_STATE_HOME/claude-mc/sessions/<sessionId>.jsonl\n  ```\n\n  (defaults to `~/.local/state/claude-mc/sessions/`). Survives across\n  process restarts, slot reassignments, and reboots — useful for audit,\n  replay, post-hoc grep, and forensics when something looks wrong. Each\n  line is `{ ts, source: 'inbound'|'outbound'|'local', ... }`. Set\n  `MC_NO_TRANSCRIPT=1` to disable.\n\n## Known caveats\n\n- **Single user.** No multi-user sessions, no per-user repos.\n- **Force-killing mc orphans its children.** claude's daemon adopts an\n  orphaned pane as a *background agent*, and that session then refuses\n  `--resume` (\"currently running as a background agent\") until the orphan is\n  stopped via `claude agents`. mc's card surfaces this state with the\n  remediation; prefer `q → save` (which now cannot stall) over killing the\n  terminal.\n- **Pause via SIGSTOP** freezes the process but does not cancel in-flight API\n  requests. The claude subprocess will receive their results when SIGCONT'd.\n- **Permission prompts in `default` mode.** The stream-json wire format does\n  not expose a structured permission event. The `A` hotkey sends a generic\n  \"yes, continue\" message that unblocks most prompts, but for tight\n  control prefer `acceptEdits` or `bypassPermissions`.\n\n### State persisted across runs\n\n- **Settings** (theme, layout, default model + permission mode, thresholds) →\n  `~/.config/claude-mc/settings.json`.\n- **Weekly cost** — every cost delta the fleet reports is folded into an\n  ISO-week bucket in `~/.config/claude-mc/costs-week.json`. The week\n  rotates automatically every Monday 00:00 UTC.\n- **Saved sessions** — per-slot snapshot of cwd / branch / model / session-id\n  in `~/.config/claude-mc/sessions.json` so the next launch can `--resume`.\n\n## Smoke test\n\n```sh\nnpm install\nnpm start\n```\n\nOn first launch the grid is empty — you'll see the header, the aggregate\nstrip, and a \"no sessions running\" hint. Press `n` (or `Ctrl+N`) to open\nthe New Session picker. As you launch sessions, cards appear and\nautosize to fill the row; the grid wraps to multiple rows once you pass\n`settings.gridCols` columns. The bottom fleet log streams events from\nevery live session.\n\n### New Session modal\n\nOne `path` input. As you type, the dropdown below it blends two sources:\n\n- **recents** — the auto-discovered repo list, filtered by case-insensitive\n  substring match on repo name or tildified path. Configure the scan\n  roots with `:repos`.\n- **filesystem completions** — when your query starts with `/` or `~`\n  (or contains a `/`), child directories of the deepest existing parent\n  are appended. Hidden dirs, `node_modules`, `dist`, and `build` are\n  skipped.\n\nHotkeys:\n\n| Key | Action |\n| --- | ------ |\n| `↑` / `↓` | move the highlight through suggestions |\n| `↵` | launch the highlighted suggestion — or, if nothing is highlighted, the typed path as-is (must already exist) |\n| `←` / `→` | cycle the model |\n| `Ctrl+B` | open the filesystem browser (familiar `cd`/`ls`-style nav); `↵` inside the picker launches the highlighted folder |\n| `esc` | cancel |\n\nInside the **filesystem browser** (Ctrl+B): `↑`/`↓` or `k`/`j` move the\nhighlight, `→`/`l` descends into the highlighted folder, `←`/`h` goes\nup a level, `.` picks the folder you're currently in, `↵` launches the\nhighlighted folder immediately. `esc` returns to the path input.\n\nIntentionally absent: mode toggle, create-new (mkdir + git init), resume\nbanner, branch input, permission picker, initial prompt. The launcher\ndoes one thing: pick a repo and launch. Branch follows the repo's\ndefault; permission mode and prompt are post-launch concerns and\nchangeable from inside the running session.\n\nWithout `claude` on `$PATH` the New Session launch will still succeed at the\nfleet level, but the spawned process will exit immediately — you'll see the\nerror in the per-session tail.\n\n## Tests\n\n```sh\nnpm test           # run the suite once\nnpm run test:watch # rerun on file change\n```\n\n`npm test` runs `scripts/run-tests.mjs`, which executes every\n`tests/**/*.test.*` in its own process. In headless CI (`CI=true`) it skips\nthe **real-terminal** suites — the node-pty recipes under `tests/recipes/` and\nthe `*.realparser.test.*` ink-keypress-parser tests — because the GitHub runner\nhas no usable TTY/PTY (node-pty emits no output; ink mis-parses control bytes).\nThose run normally for you locally and on every push (the pre-push hook runs\n`npm test` with `CI` unset). Set `MC_RUN_PTY=1` to force them on anywhere.\n\nThe suite uses Node's built-in `node:test` runner plus\n`ink-testing-library` for component rendering. Coverage includes:\n\n- `tests/detectPrompt.test.mjs` — structured-prompt classification\n  (numbered, checkbox, lettered Option A/B/C, binary fallback, priority\n  rules, the screenshot regression).\n- `tests/Zoom.chips.test.jsx` — chip render + keystroke dispatch\n  (letter and digit keys both work, wire format includes the original\n  marker, multi-select seeds from pre-checked defaults).\n- `tests/RepoPicker.test.jsx` — filesystem browser lists real subdirs\n  (filtering dotfiles / node_modules / files), ↵ picks a child, `.`\n  picks the current folder, esc cancels.\n- `tests/NewSession.test.jsx` — single-input launcher: substring\n  filtering, ↵ launches the highlighted suggestion, ↵ on a typed real\n  path launches it, ←/→ cycles the model, esc closes, nonexistent\n  paths show an error and don't launch.\n- `tests/TextField.test.jsx` — plain `↵` submits; `⌥↵` (ESC+CR) and\n  `Ctrl+J` insert a newline; the meta/shift Return paths are checked\n  before the plain-return submit branch so macOS users don't\n  accidentally submit half-typed messages.\n- `tests/Zoom.input.test.jsx` — stats panel defaults to compact +\n  `Ctrl+S` toggles; PgUp pins the log and shows the \"↓ N below\"\n  indicator; `Ctrl+G` snaps back to live; `↑`/`↓` walk through\n  composer history.\n- `tests/slashCommands.test.mjs` — prefix matcher behind the dropdown:\n  bare `/` returns full catalog, `/p` narrows to /perm /pause /etc.,\n  matching is case-insensitive and ignores args after the first token.\n- `tests/Zoom.slash.test.jsx` — typing `/` surfaces the autocomplete\n  dropdown; `/cost` routes via `onSlashCommand` (not `onSendMessage`);\n  `/quit` closes the modal locally; `Tab` fills the highlighted name;\n  a non-slash message still sends through to claude.\n- `tests/recipes/zoom.recipes.test.jsx`,\n  `tests/recipes/newsession.recipes.test.jsx`,\n  `tests/recipes/repopicker.recipes.test.jsx`,\n  `tests/recipes/pty.recipes.test.jsx` — declarative recipe coverage for\n  each surface (see the section below).\n\n### Recipe-based QA runner\n\nFor new feature coverage, prefer the declarative recipe runner in\n`tests/lib/recipe.js` over hand-writing render / stdin / assert\nboilerplate. A recipe is a JSON-shaped list of steps that drive a\nrendered Ink component and assert on the frame at each step. Each step\ncan `type` characters, `press` a key, `tick` for timers, assert on\n`expectFrame` / `expectNotFrame` substrings, or check\n`expectCallback` arg arrays. On failure the runner throws with the\nfailing step's label, the assertion, and the last frame — so you can\ndiff actual UI against intent.\n\nExample, from `tests/recipes/zoom.recipes.test.jsx`:\n\n```jsx\nimport { runRecipe } from '../lib/recipe.js';\nimport Zoom from '../../tui/modals/Zoom.jsx';\nimport { theme, makeAgent } from '../lib/fixtures.js';\n\ntest('recipe: Ctrl+S expands the stats panel', () => runRecipe({\n  component: Zoom,\n  props: { agent: makeAgent(), theme, threshold: 150000, weekCost: 0,\n           onSendMessage: () => {}, onSlashCommand: () => {},\n           onClose: () => {}, onCyclePerm: () => {} },\n  steps: [\n    { expectFrame: [/ctrl\\+s expand/], expectNotFrame: [/USAGE · SESSION/] },\n    { press: '\\x13' },                  // Ctrl+S\n    { expectFrame: [/CONTEXT/, /USAGE · SESSION/] },\n  ],\n}));\n```\n\nCommon fixtures (`makeAgent`, `chatTail`, the active `theme`) live in\n`tests/lib/fixtures.js` so recipes don't have to redefine boilerplate.\nThe intent: adding \"press X, expect Y\" coverage should feel like\nwriting a JSON document, not a React component.\n\n- `tests/lib/recipe.js` — in-process runner (Ink + `ink-testing-library`).\n- `tests/lib/fixtures.js` — agent / tail / theme helpers.\n- `tests/recipes/*.recipes.test.jsx` — one file per UI surface.\n- `tests/Card.tier.test.jsx` — Card tail filters tier-2 entries by\n  default and shows a hidden-count hint.\n\n### Full-PTY recipe backend\n\nThe in-process runner is fast but renders Ink in a fake stdout — it can't\ncatch terminal-specific bugs (e.g. the macOS `Option+Return` split-read\nwhere `ESC` and `CR` arrive in two reads instead of one). The\n`runRecipePty` backend in `tests/lib/recipe-pty.js` spawns a real\nsubprocess inside a pseudo-terminal (`node-pty`) and pipes its output\nthrough a headless xterm.js Terminal so escape sequences are processed\nexactly the way iTerm/Terminal.app would process them. Same step DSL\n(`type` / `press` / `tick` / `expectFrame` / `expectNotFrame`),\ndifferent backend.\n\n```jsx\nimport { runRecipePty } from '../lib/recipe-pty.js';\n\ntest('counter app: + increments', () => runRecipePty({\n  command: process.execPath,\n  args: ['tests/lib/pty-fixtures/counter-app.mjs'],\n  bootDelayMs: 600,\n  steps: [\n    { expectFrame: [/counter:/, /\\b0\\b/] },\n    { type: '++' },\n    { tick: 80 },\n    { expectFrame: [/counter:[^\\n]*\\b2\\b/] },\n    { press: 'q', expectExit: 1500 },\n  ],\n}));\n```\n\nUse the in-process runner for fast component tests and the PTY runner\nfor \"does this work when wrapped by a real terminal.\" See\n`tests/recipes/pty.recipes.test.jsx` for working examples.\n\n`node-pty` ships a prebuilt `spawn-helper` whose executable bit is\nsometimes dropped by `npm install`. A `postinstall` script\n(`scripts/fix-node-pty.mjs`) restores it automatically — if you see\n`posix_spawnp failed` after a fresh install, run `npm install` again or\nre-run the script manually.\n- `tests/humanize.test.mjs` — ANSI strip, path collapse, UUID shorten,\n  JSON collapse, idempotency.\n- `tests/MockAgent.replay.test.mjs` — every shipped fixture replays\n  cleanly; approval fixture pauses at `waiting` and resumes on `send()`.\n\nWhen iterating on UI without API spend, prefer:\n\n```sh\nMC_MOCK=approval-request npm start\n```\n\n…to exercise the structured-prompt + APPROVE? marker flow against the\ncanned fixture instead of a real `claude` session.\n","readmeFilename":"README.md"}