aicodeman 1.2.2 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (227) hide show
  1. package/README.md +264 -90
  2. package/dist/attachment-magic.d.ts +13 -0
  3. package/dist/attachment-magic.d.ts.map +1 -1
  4. package/dist/attachment-magic.js +47 -0
  5. package/dist/attachment-magic.js.map +1 -1
  6. package/dist/attachment-registry.d.ts.map +1 -1
  7. package/dist/attachment-registry.js +13 -2
  8. package/dist/attachment-registry.js.map +1 -1
  9. package/dist/config/buffer-limits.d.ts +1 -2
  10. package/dist/config/buffer-limits.d.ts.map +1 -1
  11. package/dist/config/buffer-limits.js +1 -2
  12. package/dist/config/buffer-limits.js.map +1 -1
  13. package/dist/config/map-limits.d.ts +11 -0
  14. package/dist/config/map-limits.d.ts.map +1 -1
  15. package/dist/config/map-limits.js +11 -0
  16. package/dist/config/map-limits.js.map +1 -1
  17. package/dist/config/server-timing.d.ts +6 -0
  18. package/dist/config/server-timing.d.ts.map +1 -1
  19. package/dist/config/server-timing.js +9 -0
  20. package/dist/config/server-timing.js.map +1 -1
  21. package/dist/config/terminal-history.d.ts +11 -6
  22. package/dist/config/terminal-history.d.ts.map +1 -1
  23. package/dist/config/terminal-history.js +18 -8
  24. package/dist/config/terminal-history.js.map +1 -1
  25. package/dist/cron/cron-input.d.ts +30 -0
  26. package/dist/cron/cron-input.d.ts.map +1 -0
  27. package/dist/cron/cron-input.js +7 -0
  28. package/dist/cron/cron-input.js.map +1 -0
  29. package/dist/cron/cron-service.d.ts +92 -0
  30. package/dist/cron/cron-service.d.ts.map +1 -0
  31. package/dist/cron/cron-service.js +606 -0
  32. package/dist/cron/cron-service.js.map +1 -0
  33. package/dist/cron/cron-time.d.ts +29 -0
  34. package/dist/cron/cron-time.d.ts.map +1 -0
  35. package/dist/cron/cron-time.js +87 -0
  36. package/dist/cron/cron-time.js.map +1 -0
  37. package/dist/document-thumbnailer.d.ts +1 -1
  38. package/dist/document-thumbnailer.d.ts.map +1 -1
  39. package/dist/document-thumbnailer.js +11 -2
  40. package/dist/document-thumbnailer.js.map +1 -1
  41. package/dist/generated-artifact-attachments.d.ts +26 -0
  42. package/dist/generated-artifact-attachments.d.ts.map +1 -0
  43. package/dist/generated-artifact-attachments.js +61 -0
  44. package/dist/generated-artifact-attachments.js.map +1 -0
  45. package/dist/index.js +11 -0
  46. package/dist/index.js.map +1 -1
  47. package/dist/mux-interface.d.ts +31 -5
  48. package/dist/mux-interface.d.ts.map +1 -1
  49. package/dist/remote-hosts.d.ts +66 -0
  50. package/dist/remote-hosts.d.ts.map +1 -0
  51. package/dist/remote-hosts.js +198 -0
  52. package/dist/remote-hosts.js.map +1 -0
  53. package/dist/services/unified-session-service.d.ts +115 -0
  54. package/dist/services/unified-session-service.d.ts.map +1 -0
  55. package/dist/services/unified-session-service.js +173 -0
  56. package/dist/services/unified-session-service.js.map +1 -0
  57. package/dist/session-cli-builder.d.ts +3 -1
  58. package/dist/session-cli-builder.d.ts.map +1 -1
  59. package/dist/session-cli-builder.js +26 -7
  60. package/dist/session-cli-builder.js.map +1 -1
  61. package/dist/session-pty-exit-breaker.d.ts +66 -0
  62. package/dist/session-pty-exit-breaker.d.ts.map +1 -0
  63. package/dist/session-pty-exit-breaker.js +77 -0
  64. package/dist/session-pty-exit-breaker.js.map +1 -0
  65. package/dist/session.d.ts +24 -1
  66. package/dist/session.d.ts.map +1 -1
  67. package/dist/session.js +94 -11
  68. package/dist/session.js.map +1 -1
  69. package/dist/state-store.d.ts +14 -0
  70. package/dist/state-store.d.ts.map +1 -1
  71. package/dist/state-store.js +49 -0
  72. package/dist/state-store.js.map +1 -1
  73. package/dist/tmux-manager.d.ts +94 -8
  74. package/dist/tmux-manager.d.ts.map +1 -1
  75. package/dist/tmux-manager.js +222 -26
  76. package/dist/tmux-manager.js.map +1 -1
  77. package/dist/types/api.d.ts +11 -0
  78. package/dist/types/api.d.ts.map +1 -1
  79. package/dist/types/api.js.map +1 -1
  80. package/dist/types/app-state.d.ts +5 -0
  81. package/dist/types/app-state.d.ts.map +1 -1
  82. package/dist/types/app-state.js.map +1 -1
  83. package/dist/types/cron.d.ts +88 -0
  84. package/dist/types/cron.d.ts.map +1 -0
  85. package/dist/types/cron.js +13 -0
  86. package/dist/types/cron.js.map +1 -0
  87. package/dist/types/session.d.ts +57 -0
  88. package/dist/types/session.d.ts.map +1 -1
  89. package/dist/types/session.js.map +1 -1
  90. package/dist/web/heic-jpeg-converter.d.ts +24 -0
  91. package/dist/web/heic-jpeg-converter.d.ts.map +1 -0
  92. package/dist/web/heic-jpeg-converter.js +76 -0
  93. package/dist/web/heic-jpeg-converter.js.map +1 -0
  94. package/dist/web/heic-jpeg-worker.d.ts +44 -0
  95. package/dist/web/heic-jpeg-worker.d.ts.map +1 -0
  96. package/dist/web/heic-jpeg-worker.js +78 -0
  97. package/dist/web/heic-jpeg-worker.js.map +1 -0
  98. package/dist/web/ports/cron-port.d.ts +9 -0
  99. package/dist/web/ports/cron-port.d.ts.map +1 -0
  100. package/dist/web/ports/cron-port.js +6 -0
  101. package/dist/web/ports/cron-port.js.map +1 -0
  102. package/dist/web/ports/index.d.ts +1 -0
  103. package/dist/web/ports/index.d.ts.map +1 -1
  104. package/dist/web/public/api-client.c9b1cddc.js.gz +0 -0
  105. package/dist/web/public/app.b0534652.js +38 -0
  106. package/dist/web/public/app.b0534652.js.br +0 -0
  107. package/dist/web/public/app.b0534652.js.gz +0 -0
  108. package/dist/web/public/{constants.1c779517.js → constants.5b18ed64.js} +65 -0
  109. package/dist/web/public/constants.5b18ed64.js.br +0 -0
  110. package/dist/web/public/constants.5b18ed64.js.gz +0 -0
  111. package/dist/web/public/cron-ui.js +328 -0
  112. package/dist/web/public/cron-ui.js.br +0 -0
  113. package/dist/web/public/cron-ui.js.gz +0 -0
  114. package/dist/web/public/image-input.ee16ad88.js.gz +0 -0
  115. package/dist/web/public/index.html +296 -25
  116. package/dist/web/public/index.html.br +0 -0
  117. package/dist/web/public/index.html.gz +0 -0
  118. package/dist/web/public/input-cjk.63794d0b.js +1 -0
  119. package/dist/web/public/input-cjk.63794d0b.js.br +0 -0
  120. package/dist/web/public/input-cjk.63794d0b.js.gz +0 -0
  121. package/dist/web/public/keyboard-accessory.3f91acef.js.gz +0 -0
  122. package/dist/web/public/mobile-handlers.b5a667ef.js.gz +0 -0
  123. package/dist/web/public/mobile.0644e3ac.css +1 -0
  124. package/dist/web/public/mobile.0644e3ac.css.br +0 -0
  125. package/dist/web/public/mobile.0644e3ac.css.gz +0 -0
  126. package/dist/web/public/notification-manager.ca9dc40d.js.gz +0 -0
  127. package/dist/web/public/orchestrator-panel.js.gz +0 -0
  128. package/dist/web/public/{panels-ui.772e92c9.js → panels-ui.ca03853f.js} +25 -17
  129. package/dist/web/public/panels-ui.ca03853f.js.br +0 -0
  130. package/dist/web/public/panels-ui.ca03853f.js.gz +0 -0
  131. package/dist/web/public/ralph-panel.6de2d0f8.js.gz +0 -0
  132. package/dist/web/public/ralph-wizard.13a1831e.js.gz +0 -0
  133. package/dist/web/public/respawn-ui.2d249da9.js.gz +0 -0
  134. package/dist/web/public/sanitize-html.bc7078d6.js.gz +0 -0
  135. package/dist/web/public/session-ui.b51814ef.js +49 -0
  136. package/dist/web/public/session-ui.b51814ef.js.br +0 -0
  137. package/dist/web/public/session-ui.b51814ef.js.gz +0 -0
  138. package/dist/web/public/settings-ui.6c67c125.js +67 -0
  139. package/dist/web/public/settings-ui.6c67c125.js.br +0 -0
  140. package/dist/web/public/settings-ui.6c67c125.js.gz +0 -0
  141. package/dist/web/public/styles.e1ba7849.css +1 -0
  142. package/dist/web/public/styles.e1ba7849.css.br +0 -0
  143. package/dist/web/public/styles.e1ba7849.css.gz +0 -0
  144. package/dist/web/public/subagent-windows.69b60c35.js.gz +0 -0
  145. package/dist/web/public/sw.js.gz +0 -0
  146. package/dist/web/public/terminal-ui.dbf141fa.js +3 -0
  147. package/dist/web/public/terminal-ui.dbf141fa.js.br +0 -0
  148. package/dist/web/public/terminal-ui.dbf141fa.js.gz +0 -0
  149. package/dist/web/public/ultracode-panel.js.gz +0 -0
  150. package/dist/web/public/ultracode-windows.js.gz +0 -0
  151. package/dist/web/public/upload.html.gz +0 -0
  152. package/dist/web/public/vendor/dompurify.min.js.gz +0 -0
  153. package/dist/web/public/vendor/marked.min.js.gz +0 -0
  154. package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
  155. package/dist/web/public/vendor/xterm-addon-serialize.min.js.gz +0 -0
  156. package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
  157. package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
  158. package/dist/web/public/vendor/xterm-zerolag-input.137ad9f0.js.gz +0 -0
  159. package/dist/web/public/vendor/xterm.css.gz +0 -0
  160. package/dist/web/public/vendor/xterm.min.js.gz +0 -0
  161. package/dist/web/public/voice-input.085e9e73.js.gz +0 -0
  162. package/dist/web/routes/case-routes.d.ts.map +1 -1
  163. package/dist/web/routes/case-routes.js +115 -1
  164. package/dist/web/routes/case-routes.js.map +1 -1
  165. package/dist/web/routes/cron-routes.d.ts +11 -0
  166. package/dist/web/routes/cron-routes.d.ts.map +1 -0
  167. package/dist/web/routes/cron-routes.js +70 -0
  168. package/dist/web/routes/cron-routes.js.map +1 -0
  169. package/dist/web/routes/index.d.ts +1 -0
  170. package/dist/web/routes/index.d.ts.map +1 -1
  171. package/dist/web/routes/index.js +1 -0
  172. package/dist/web/routes/index.js.map +1 -1
  173. package/dist/web/routes/respawn-routes.d.ts.map +1 -1
  174. package/dist/web/routes/respawn-routes.js +3 -0
  175. package/dist/web/routes/respawn-routes.js.map +1 -1
  176. package/dist/web/routes/session-routes.d.ts.map +1 -1
  177. package/dist/web/routes/session-routes.js +610 -68
  178. package/dist/web/routes/session-routes.js.map +1 -1
  179. package/dist/web/routes/ws-routes.d.ts.map +1 -1
  180. package/dist/web/routes/ws-routes.js +56 -17
  181. package/dist/web/routes/ws-routes.js.map +1 -1
  182. package/dist/web/schemas.d.ts +113 -0
  183. package/dist/web/schemas.d.ts.map +1 -1
  184. package/dist/web/schemas.js +145 -0
  185. package/dist/web/schemas.js.map +1 -1
  186. package/dist/web/server.d.ts +8 -2
  187. package/dist/web/server.d.ts.map +1 -1
  188. package/dist/web/server.js +75 -13
  189. package/dist/web/server.js.map +1 -1
  190. package/dist/web/session-listener-wiring.d.ts +5 -1
  191. package/dist/web/session-listener-wiring.d.ts.map +1 -1
  192. package/dist/web/session-listener-wiring.js +23 -1
  193. package/dist/web/session-listener-wiring.js.map +1 -1
  194. package/dist/web/sse-events.d.ts +15 -0
  195. package/dist/web/sse-events.d.ts.map +1 -1
  196. package/dist/web/sse-events.js +17 -0
  197. package/dist/web/sse-events.js.map +1 -1
  198. package/dist/web/ws-connection-registry.d.ts +77 -0
  199. package/dist/web/ws-connection-registry.d.ts.map +1 -0
  200. package/dist/web/ws-connection-registry.js +99 -0
  201. package/dist/web/ws-connection-registry.js.map +1 -0
  202. package/package.json +3 -1
  203. package/dist/web/public/app.a8d73c32.js +0 -38
  204. package/dist/web/public/app.a8d73c32.js.br +0 -0
  205. package/dist/web/public/app.a8d73c32.js.gz +0 -0
  206. package/dist/web/public/constants.1c779517.js.br +0 -0
  207. package/dist/web/public/constants.1c779517.js.gz +0 -0
  208. package/dist/web/public/input-cjk.4e950e0a.js +0 -1
  209. package/dist/web/public/input-cjk.4e950e0a.js.br +0 -0
  210. package/dist/web/public/input-cjk.4e950e0a.js.gz +0 -0
  211. package/dist/web/public/mobile.8a9aff7a.css +0 -1
  212. package/dist/web/public/mobile.8a9aff7a.css.br +0 -0
  213. package/dist/web/public/mobile.8a9aff7a.css.gz +0 -0
  214. package/dist/web/public/panels-ui.772e92c9.js.br +0 -0
  215. package/dist/web/public/panels-ui.772e92c9.js.gz +0 -0
  216. package/dist/web/public/session-ui.da3e53f2.js +0 -36
  217. package/dist/web/public/session-ui.da3e53f2.js.br +0 -0
  218. package/dist/web/public/session-ui.da3e53f2.js.gz +0 -0
  219. package/dist/web/public/settings-ui.38feb956.js +0 -61
  220. package/dist/web/public/settings-ui.38feb956.js.br +0 -0
  221. package/dist/web/public/settings-ui.38feb956.js.gz +0 -0
  222. package/dist/web/public/styles.1b3e6147.css +0 -1
  223. package/dist/web/public/styles.1b3e6147.css.br +0 -0
  224. package/dist/web/public/styles.1b3e6147.css.gz +0 -0
  225. package/dist/web/public/terminal-ui.865f21f6.js +0 -3
  226. package/dist/web/public/terminal-ui.865f21f6.js.br +0 -0
  227. package/dist/web/public/terminal-ui.865f21f6.js.gz +0 -0
package/README.md CHANGED
@@ -45,6 +45,7 @@ codeman web
45
45
  <summary><strong>Run as a background service</strong></summary>
46
46
 
47
47
  **Linux (systemd):**
48
+
48
49
  ```bash
49
50
  mkdir -p ~/.config/systemd/user
50
51
  cat > ~/.config/systemd/user/codeman-web.service << EOF
@@ -67,6 +68,7 @@ loginctl enable-linger $USER
67
68
  ```
68
69
 
69
70
  **macOS (launchd):**
71
+
70
72
  ```bash
71
73
  mkdir -p ~/Library/LaunchAgents
72
74
  cat > ~/Library/LaunchAgents/com.codeman.web.plist << EOF
@@ -94,6 +96,7 @@ cat > ~/Library/LaunchAgents/com.codeman.web.plist << EOF
94
96
  EOF
95
97
  launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.codeman.web.plist
96
98
  ```
99
+
97
100
  </details>
98
101
 
99
102
  <details>
@@ -104,10 +107,78 @@ wsl bash -c "curl -fsSL https://raw.githubusercontent.com/Ark0N/Codeman/master/i
104
107
  ```
105
108
 
106
109
  Codeman requires tmux, so Windows users need [WSL](https://learn.microsoft.com/en-us/windows/wsl/install). If you don't have WSL yet: run `wsl --install` in an admin PowerShell, reboot, open Ubuntu, then install your preferred AI coding CLI inside WSL ([Claude Code](https://docs.anthropic.com/en/docs/claude-code), [OpenCode](https://opencode.ai), or [Codex](https://developers.openai.com/codex/cli)). After installing, `http://localhost:3000` is accessible from your Windows browser.
110
+
107
111
  </details>
108
112
 
109
113
  ---
110
114
 
115
+ ## Using Codeman — A Human's Guide
116
+
117
+ A start-to-finish walkthrough for driving Codeman from the browser. If you just installed, this is where to begin.
118
+
119
+ ### 1. Launch the server
120
+
121
+ ```bash
122
+ codeman web # localhost:3000 (loopback only — safe default)
123
+ codeman web --port 8080 # custom port (or set CODEMAN_PORT)
124
+ codeman web --https # self-signed TLS (only needed for remote access)
125
+ codeman web -H 0.0.0.0 # bind LAN — REQUIRES CODEMAN_PASSWORD (see Security)
126
+ ```
127
+
128
+ Open the printed URL. The page is a single dashboard; everything below happens there.
129
+
130
+ ### 2. Create your first session
131
+
132
+ Click **+ New Session** (or **Quick Start**). A session is one AI CLI running in its own tmux-backed terminal. You choose:
133
+
134
+ | Field | What it does |
135
+ | ---------------------------- | ------------------------------------------------------------------------------------------------------------------- |
136
+ | **Working directory / case** | The folder the agent operates in. A "case" is just a named working dir Codeman remembers. |
137
+ | **CLI / run mode** | `Claude` (default), `OpenCode`, `Codex`, `Gemini`, or `Terminal` (plain shell). |
138
+ | **Model** | Per-session model (App Settings → Claude Model). A soft default — `/model` still works in-session. |
139
+ | **Effort / Ultracode** | Reasoning effort (`low`–`max`) or `ultracode` for dynamic multi-agent workflows. Switchable anytime with `/effort`. |
140
+
141
+ Hit start — Codeman spawns the CLI via a real PTY and streams it to your browser over SSE.
142
+
143
+ ### 3. Read the dashboard
144
+
145
+ - **Tabs (top)** — one per session. `Alt+1`-`9` to jump, `Ctrl+Tab` for next, drag to reorder.
146
+ - **Terminal (center)** — a real `xterm.js` terminal; full TUIs render correctly. Type directly and press **Enter** to send. `Shift+Enter` inserts a newline.
147
+ - **Side panels** — Respawn, Ralph, Orchestrator, Cron, Subagents, Settings (toggled from the toolbar).
148
+
149
+ ### 4. Talk to the agent
150
+
151
+ - **Type prompts** straight into the terminal — input is delivered exactly-once even across reconnects (a dropped link never loses or double-sends a prompt).
152
+ - **Paste or drag-and-drop images** directly into the session.
153
+ - **Voice input** — `Ctrl+Shift+V` (Deepgram Nova-3, with auto-silence stop).
154
+ - **Attachments** — register external files/docs and preview Office/PDF inline.
155
+
156
+ ### 5. Make it autonomous
157
+
158
+ | Mode | Use it for | Where |
159
+ | ---------------- | --------------------------------------------------------------------------------------------------------------------------------- | ------------------ |
160
+ | **Respawn** | Long unattended runs — auto-restarts the CLI on idle/limit, with adaptive timing. Presets: `solo-work`, `overnight-autonomous`, … | Respawn tab |
161
+ | **Ralph / Todo** | A self-driving loop that tracks a todo list and keeps working until done. | Ralph tab |
162
+ | **Orchestrator** | Turn one goal into a phased plan and drive it to completion across agents. | Orchestrator panel |
163
+ | **Cron** | Saved, named jobs on a schedule (`once`/`interval`/`daily`/`weekly`) that spawn a session and send a prompt when due. | ⏰ Cron button |
164
+ | **Auto-resume** | Automatically continue after a subscription rate-limit resets. | Respawn tab (top) |
165
+
166
+ ### 6. Reach it from anywhere
167
+
168
+ - **Phone/tablet** — the UI is fully touch-optimized; scan the desktop **QR code** to log in without typing a password.
169
+ - **Outside your network** — `./scripts/tunnel.sh start` opens a Cloudflare tunnel (set `CODEMAN_PASSWORD` first).
170
+ - **SSH** — the `sc` chooser attaches to any session from a terminal (`sc` interactive, `sc 2` quick-attach, `sc -l` list).
171
+
172
+ ### 7. Operate & maintain
173
+
174
+ - **App Settings** — model, effort, theme/skin, notifications, display toggles, per-CLI options.
175
+ - **Self-update** — git-clone installs update in place from **Settings → Updates**.
176
+ - **Deploy your own changes** — see [Development](#development).
177
+
178
+ > ⚠️ **Safety:** if you're working _inside_ a Codeman-managed session (`echo $CODEMAN_MUX` → `1`), never run `tmux kill-session` / `pkill claude` directly — use the web UI or `./scripts/tmux-manager.sh`.
179
+
180
+ ---
181
+
111
182
  ## Mobile-Optimized Web UI
112
183
 
113
184
  The most responsive AI coding agent experience on any phone. Full xterm.js terminal with local echo, swipe navigation, and a touch-optimized interface designed for real remote work — not a desktop UI crammed onto a small screen.
@@ -214,7 +285,7 @@ WATCHING → IDLE DETECTED → SEND UPDATE → /clear → /init → CONTINUE →
214
285
  ```
215
286
 
216
287
  - **Multi-layer idle detection** — completion messages, AI-powered idle check, output silence, token stability
217
- - **Auto-resume on usage limit** *(opt-in, off by default)* — when Claude halts on a subscription limit ("You've hit your limit · resets 3pm"), Codeman parses the reset time, waits it out plus a 2-minute safety buffer, then dismisses the rate-limit dialog and sends `continue` — so an overnight run survives the 5-hour window instead of stalling until morning. Recognizes every Claude Code limit-message format, retries if still limited, survives Codeman restarts, and holds respawn cycles while paused so `/clear` can't wipe the waiting conversation. Enable per session at the top of the Respawn tab
288
+ - **Auto-resume on usage limit** _(opt-in, off by default)_ — when Claude halts on a subscription limit ("You've hit your limit · resets 3pm"), Codeman parses the reset time, waits it out plus a 2-minute safety buffer, then dismisses the rate-limit dialog and sends `continue` — so an overnight run survives the 5-hour window instead of stalling until morning. Recognizes every Claude Code limit-message format, retries if still limited, survives Codeman restarts, and holds respawn cycles while paused so `/clear` can't wipe the waiting conversation. Enable per session at the top of the Respawn tab
218
289
  - **Circuit breaker** — prevents respawn thrashing when Claude is stuck (CLOSED -> HALF_OPEN -> OPEN states, tracks consecutive no-progress and repeated errors)
219
290
  - **Health scoring** — 0-100 health score with component scores for cycle success, circuit breaker state, iteration progress, and stuck recovery
220
291
  - **Built-in presets** — `solo-work` (3s idle, 60min), `subagent-workflow` (45s, 240min), `team-lead` (90s, 480min), `ralph-todo` (8s, 480min), `overnight-autonomous` (10s, 480min)
@@ -260,10 +331,10 @@ The title is templated into the served HTML on first byte, so it's correct from
260
331
 
261
332
  ### Smart Token Management
262
333
 
263
- | Threshold | Action | Result |
264
- |-----------|--------|--------|
334
+ | Threshold | Action | Result |
335
+ | --------------- | --------------- | ---------------------------------- |
265
336
  | **110k tokens** | Auto `/compact` | Context summarized, work continues |
266
- | **140k tokens** | Auto `/clear` | Fresh start with `/init` |
337
+ | **140k tokens** | Auto `/clear` | Fresh start with `/init` |
267
338
 
268
339
  ### Notifications
269
340
 
@@ -298,8 +369,8 @@ PTY Output → 16ms Server Batch → DEC 2026 Wrap → SSE → Client rAF → xt
298
369
  - **Effort & Ultracode** — set a per-session default effort (`low`–`max`) or enable **ultracode** (dynamic multi-agent workflows). Soft defaults only — switchable anytime with `/effort` in-session. Extended-thinking budget is configurable too
299
370
  - **Voice input** — dictate prompts with Deepgram Nova-3 (Web Speech API fallback): toggle recording, auto-silence stop, live level meter (`Ctrl+Shift+V`)
300
371
  - **Image input** — paste or drag-and-drop images straight into a session
301
- - **Gesture control** *(opt-in)* — a MediaPipe hand-tracking overlay to grab/drag session windows and pinch buttons, hands-free. Enable with `CODEMAN_GESTURE=1` + App Settings → Display
302
- - **Multi-monitor span** *(macOS)* — one click opens a browser window maximized across all displays, so floating agent/gesture panels can cross the physical seam
372
+ - **Gesture control** _(opt-in)_ — a MediaPipe hand-tracking overlay to grab/drag session windows and pinch buttons, hands-free. Enable with `CODEMAN_GESTURE=1` + App Settings → Display
373
+ - **Multi-monitor span** _(macOS)_ — one click opens a browser window maximized across all displays, so floating agent/gesture panels can cross the physical seam
303
374
  - **CJK / IME input** — full composition support for Chinese / Japanese / Korean
304
375
  - **OS notifications & hostname-aware titles** — desktop alerts and tab titles are prefixed `codeman:<host>` so multi-host setups stay unambiguous
305
376
 
@@ -372,14 +443,14 @@ Every **60 seconds**, the server automatically rotates to a fresh token. The pre
372
443
 
373
444
  The design is informed by ["Demystifying the (In)Security of QR Code-based Login"](https://www.usenix.org/conference/usenixsecurity25/presentation/zhang-xin) (USENIX Security 2025), which found 47 of the top-100 websites vulnerable to QR auth attacks due to 6 critical design flaws across 42 CVEs. Codeman addresses all six:
374
445
 
375
- | USENIX Flaw | Mitigation |
376
- |-------------|------------|
377
- | **Flaw-1**: Missing single-use enforcement | Token atomically consumed on first scan — replays always fail |
378
- | **Flaw-2**: Long-lived tokens | 60s TTL with 90s grace, auto-rotation via timer |
379
- | **Flaw-3**: Predictable token generation | `crypto.randomBytes(32)` — 256-bit entropy. Short codes use rejection sampling to eliminate modulo bias |
380
- | **Flaw-4**: Client-side token generation | Server-side only — tokens never leave the server until embedded in the QR |
381
- | **Flaw-5**: Missing status notification | Desktop toast: *"Device [IP] authenticated via QR (Safari). Not you? [Revoke]"* — real-time QRLjacking detection |
382
- | **Flaw-6**: Inadequate session binding | IP + User-Agent stored for audit. Manual session revocation via API. HttpOnly + Secure + SameSite=lax cookies |
446
+ | USENIX Flaw | Mitigation |
447
+ | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------- |
448
+ | **Flaw-1**: Missing single-use enforcement | Token atomically consumed on first scan — replays always fail |
449
+ | **Flaw-2**: Long-lived tokens | 60s TTL with 90s grace, auto-rotation via timer |
450
+ | **Flaw-3**: Predictable token generation | `crypto.randomBytes(32)` — 256-bit entropy. Short codes use rejection sampling to eliminate modulo bias |
451
+ | **Flaw-4**: Client-side token generation | Server-side only — tokens never leave the server until embedded in the QR |
452
+ | **Flaw-5**: Missing status notification | Desktop toast: _"Device [IP] authenticated via QR (Safari). Not you? [Revoke]"_ — real-time QRLjacking detection |
453
+ | **Flaw-6**: Inadequate session binding | IP + User-Agent stored for audit. Manual session revocation via API. HttpOnly + Secure + SameSite=lax cookies |
383
454
 
384
455
  #### Timing-Safe Lookup
385
456
 
@@ -404,23 +475,23 @@ When someone authenticates via QR, the desktop shows a notification toast with t
404
475
 
405
476
  #### Threat Coverage
406
477
 
407
- | Threat | Why it doesn't work |
408
- |--------|-------------------|
409
- | **QR screenshot shared** | Single-use: consumed on first scan. 60s TTL: expired before the attacker can act. Desktop notification alerts you immediately. |
410
- | **Replay attack** | Atomic single-use consumption + 60s TTL. Old URLs always return 401. |
411
- | **Cloudflare edge logs** | Short code is an opaque 6-char lookup key, not the real 256-bit token. Single-use means replaying from logs always fails. |
412
- | **Brute force** | 56.8 billion combinations, ~2 valid at any time, dual-layer rate limiting blocks well before statistical feasibility. |
413
- | **QRLjacking** | 60s rotation forces real-time relay. Desktop toast provides instant detection. Self-hosted single-user context makes phishing implausible. |
414
- | **Timing attack** | Hash-based Map lookup — no string comparison timing leak. |
415
- | **Session cookie theft** | HttpOnly + Secure + SameSite=lax + 24h TTL. Manual revocation at `POST /api/auth/revoke`. |
478
+ | Threat | Why it doesn't work |
479
+ | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------ |
480
+ | **QR screenshot shared** | Single-use: consumed on first scan. 60s TTL: expired before the attacker can act. Desktop notification alerts you immediately. |
481
+ | **Replay attack** | Atomic single-use consumption + 60s TTL. Old URLs always return 401. |
482
+ | **Cloudflare edge logs** | Short code is an opaque 6-char lookup key, not the real 256-bit token. Single-use means replaying from logs always fails. |
483
+ | **Brute force** | 56.8 billion combinations, ~2 valid at any time, dual-layer rate limiting blocks well before statistical feasibility. |
484
+ | **QRLjacking** | 60s rotation forces real-time relay. Desktop toast provides instant detection. Self-hosted single-user context makes phishing implausible. |
485
+ | **Timing attack** | Hash-based Map lookup — no string comparison timing leak. |
486
+ | **Session cookie theft** | HttpOnly + Secure + SameSite=lax + 24h TTL. Manual revocation at `POST /api/auth/revoke`. |
416
487
 
417
488
  #### How It Compares
418
489
 
419
- | Platform | Model | Comparison |
420
- |----------|-------|------------|
421
- | **Discord** | Long-lived token, no confirmation, [repeatedly exploited](https://owasp.org/www-community/attacks/Qrljacking) | Codeman: single-use + TTL + notification |
422
- | **WhatsApp Web** | Phone confirms "Link device?", ~60s rotation | Comparable rotation; WhatsApp adds explicit confirmation (acceptable tradeoff for single-user) |
423
- | **Signal** | Ephemeral public key, E2E encrypted channel | Stronger crypto, but [exploited by Russian state actors in 2025](https://cloud.google.com/blog/topics/threat-intelligence/russia-targeting-signal-messenger) via social engineering despite it |
490
+ | Platform | Model | Comparison |
491
+ | ---------------- | ------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
492
+ | **Discord** | Long-lived token, no confirmation, [repeatedly exploited](https://owasp.org/www-community/attacks/Qrljacking) | Codeman: single-use + TTL + notification |
493
+ | **WhatsApp Web** | Phone confirms "Link device?", ~60s rotation | Comparable rotation; WhatsApp adds explicit confirmation (acceptable tradeoff for single-user) |
494
+ | **Signal** | Ephemeral public key, E2E encrypted channel | Stronger crypto, but [exploited by Russian state actors in 2025](https://cloud.google.com/blog/topics/threat-intelligence/russia-targeting-signal-messenger) via social engineering despite it |
424
495
 
425
496
  > Full design rationale, security analysis, and implementation details: [`docs/qr-auth-plan.md`](docs/qr-auth-plan.md)
426
497
 
@@ -428,20 +499,20 @@ When someone authenticates via QR, the desktop shows a notification toast with t
428
499
 
429
500
  ## Security
430
501
 
431
- Codeman launches sessions with `--dangerously-skip-permissions`, so the web UI is by design a remote-code-execution surface for whoever can reach it — the whole security model exists to control *who* that is. Recent hardening (v0.9.0 + v0.9.5) closes the browser-driven attack paths that bite self-hosted dev tools. Full model: [`docs/security-architecture.md`](docs/security-architecture.md). **Found a vulnerability?** See [`SECURITY.md`](SECURITY.md) for private disclosure and the list of known limitations.
502
+ Codeman launches sessions with `--dangerously-skip-permissions`, so the web UI is by design a remote-code-execution surface for whoever can reach it — the whole security model exists to control _who_ that is. Recent hardening (v0.9.0 + v0.9.5) closes the browser-driven attack paths that bite self-hosted dev tools. Full model: [`docs/security-architecture.md`](docs/security-architecture.md). **Found a vulnerability?** See [`SECURITY.md`](SECURITY.md) for private disclosure and the list of known limitations.
432
503
 
433
504
  ### Network & access
434
505
 
435
- - **Loopback by default** — binds `127.0.0.1`, reachable only from the same machine, so the no-password default is safe out of the box. Binding a non-loopback host without `CODEMAN_PASSWORD` *starts but prints a loud warning* with three concrete fixes (set a password, loopback + an authenticated tunnel, or explicitly acknowledge with `--allow-unauthenticated-network`)
506
+ - **Loopback by default** — binds `127.0.0.1`, reachable only from the same machine, so the no-password default is safe out of the box. Binding a non-loopback host without `CODEMAN_PASSWORD` _starts but prints a loud warning_ with three concrete fixes (set a password, loopback + an authenticated tunnel, or explicitly acknowledge with `--allow-unauthenticated-network`)
436
507
  - **Optional auth, real sessions** — HTTP Basic via `CODEMAN_USERNAME` (default `admin`) / `CODEMAN_PASSWORD`. Success issues an opaque 256-bit `codeman_session` cookie (`randomBytes(32)`) — validated server-side, not client-signed, so it can't be forged offline (24h TTL, auto-extend, device-context audit log)
437
- - **Per-IP rate limiting** — 10 failed attempts → `429` with `Retry-After` (15-min decay). A valid cookie or correct password recovers *immediately* even while an attacker hammers the same IP — important because all tunnel traffic shares one loopback IP. QR auth has its own separate limiter
508
+ - **Per-IP rate limiting** — 10 failed attempts → `429` with `Retry-After` (15-min decay). A valid cookie or correct password recovers _immediately_ even while an attacker hammers the same IP — important because all tunnel traffic shares one loopback IP. QR auth has its own separate limiter
438
509
 
439
510
  ### Always-on browser hardening (v0.9.5)
440
511
 
441
512
  These run for **every** request — before auth, even on the default no-password loopback install:
442
513
 
443
514
  - **Host-header allowlist → blocks DNS rebinding.** A custom domain rebound to `127.0.0.1` is rejected with `403 host not allowed` before any handler runs. Allowed: `localhost`, any IP literal, the bind host, `.ts.net` / `.trycloudflare.com` / `.cfargotunnel.com`, the active managed tunnel, and `CODEMAN_ALLOWED_HOSTS` (add custom reverse-proxy domains here — comma-separated; exact host or leading-dot `.suffix` for subdomains)
444
- - **Cross-site Origin / CSRF guard.** On state-changing methods (`POST`/`PUT`/`PATCH`/`DELETE`) the `Origin` must pass the same allowlist, else `403 cross-site request blocked`. A *missing* Origin is allowed (so `curl`, the CLI, and Claude Code hooks keep working); only a present-but-foreign or opaque `null` origin is rejected
515
+ - **Cross-site Origin / CSRF guard.** On state-changing methods (`POST`/`PUT`/`PATCH`/`DELETE`) the `Origin` must pass the same allowlist, else `403 cross-site request blocked`. A _missing_ Origin is allowed (so `curl`, the CLI, and Claude Code hooks keep working); only a present-but-foreign or opaque `null` origin is rejected
445
516
  - **Raw `text/plain` bodies.** The global parser no longer JSON-parses `text/plain`, closing the CORS "simple request" CSRF vector where a cross-site `fetch` could smuggle JSON into a write route with no preflight
446
517
  - **WebSocket origin validation.** The terminal WS upgrade runs the same Host + Origin check and closes with code `4003` on failure (anti-CSWSH)
447
518
  - **XSS-escaped agent output.** AI-derived strings (tool names, command arguments, subagent descriptions) are HTML-escaped at every injection site before rendering in the subagent / activity panels
@@ -479,74 +550,177 @@ Single-digit selection (1-9), color-coded status, token counts, auto-refresh. De
479
550
 
480
551
  > Ctrl bindings also accept Cmd on macOS.
481
552
 
482
- | Shortcut | Action |
483
- |----------|--------|
484
- | `Ctrl/Cmd+W` | Kill active session |
485
- | `Ctrl/Cmd+Tab` | Next session |
486
- | `Alt/Option+[` / `Alt/Option+]` | Previous / next session |
487
- | `Alt/Option+1`-`Alt/Option+9` | Switch to tab N (physical keys, so macOS Option layouts work) |
488
- | `Ctrl+Shift+{` / `Ctrl+Shift+}` | Move active tab left / right |
489
- | `Ctrl/Cmd+L` | Clear terminal |
490
- | `Ctrl+Shift+R` | Restore terminal size |
491
- | `Ctrl+Shift+V` | Toggle voice input |
492
- | `Ctrl/Cmd +` / `-` | Font size |
493
- | `Ctrl/Cmd+?` | Keyboard help |
494
- | `Shift+Enter` | Insert newline (sent to terminal) |
495
- | `Escape` | Close panels & modals |
553
+ | Shortcut | Action |
554
+ | ------------------------------- | ------------------------------------------------------------- |
555
+ | `Ctrl/Cmd+W` | Kill active session |
556
+ | `Ctrl/Cmd/Option+K` | Find open session or start a new one |
557
+ | `Ctrl/Cmd+Tab` | Next session |
558
+ | `Alt/Option+[` / `Alt/Option+]` | Previous / next session |
559
+ | `Alt/Option+1`-`Alt/Option+9` | Switch to tab N (physical keys, so macOS Option layouts work) |
560
+ | `Ctrl+Shift+{` / `Ctrl+Shift+}` | Move active tab left / right |
561
+ | `Ctrl/Cmd+L` | Clear terminal |
562
+ | `Ctrl+Shift+R` | Restore terminal size |
563
+ | `Ctrl+Shift+V` | Toggle voice input |
564
+ | `Ctrl/Cmd +` / `-` | Font size |
565
+ | `Ctrl/Cmd+?` | Keyboard help |
566
+ | `Shift+Enter` | Insert newline (sent to terminal) |
567
+ | `Escape` | Close panels & modals |
568
+
569
+ ---
570
+
571
+ ## Driving Codeman from an Agent — Programmatic Guide
572
+
573
+ For AI agents and automation that control Codeman without a browser: an agent that spins up worker sessions, a CI bot, or **Claude Code running _inside_ a Codeman session orchestrating other sessions**. Everything the UI does is HTTP + a CLI, so an agent can do it too.
574
+
575
+ ### Detect that you're inside Codeman
576
+
577
+ When a CLI runs in a Codeman-managed session, these environment variables are set — read them instead of hardcoding anything:
578
+
579
+ | Variable | Meaning |
580
+ | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
581
+ | `CODEMAN_MUX=1` | You're in a managed tmux session. **Never** `tmux kill-session` / `pkill claude` / `pkill tmux` — you'll kill yourself or a sibling. |
582
+ | `CODEMAN_API_URL` | Base URL of the API (e.g. `https://127.0.0.1:3000`). Use it for every call below. |
583
+ | `CODEMAN_SESSION_ID` | _Your own_ session id. Use it to avoid acting on yourself. |
584
+ | `CODEMAN_HOOK_SECRET_FILE` | Path to the hook secret (required on `/api/hook-event` while a managed tunnel is up). |
585
+
586
+ ### Rules of the road (read before you POST)
587
+
588
+ 1. **Single-line input only.** Programmatic input is sent as literal text **+ Enter** in one shot. Multi-line strings break the agent TUI (Ink) — send one line, or split into multiple calls.
589
+ 2. **Make input idempotent.** Include a stable `clientId` and a monotonic per-session `seq` on `POST …/input`. The server de-duplicates, so a retry after a dropped connection can't double-deliver a prompt.
590
+ 3. **Auth.** If `CODEMAN_PASSWORD` is set, send HTTP Basic auth (user `admin` or `CODEMAN_USERNAME`) or a `codeman_session` cookie. The default loopback install is passwordless. A missing `Origin` header is allowed, so plain `curl` works; cross-site browser origins are rejected (CSRF guard).
591
+ 4. **Response envelope.** Most endpoints return `{ "success": true, "data": … }` (errors: `{ "success": false, "error", "errorCode" }`). A few legacy GETs return bare bodies — **handle both** (`body.data ?? body`).
592
+ 5. **`/api/v1/*`** is a stable alias of `/api/*`.
593
+
594
+ ### Recipes
595
+
596
+ ```bash
597
+ API="${CODEMAN_API_URL:-http://127.0.0.1:3000}"
598
+ # (add -u admin:"$CODEMAN_PASSWORD" to each call if a password is set)
599
+
600
+ # 1. See what's running
601
+ curl -s "$API/api/sessions" | jq '.data // .'
602
+
603
+ # 2. Spin up a worker session (a "case" = named working dir)
604
+ curl -s -X POST "$API/api/quick-start" \
605
+ -H 'Content-Type: application/json' \
606
+ -d '{"caseName":"refactor-auth","mode":"claude","effort":"high"}' | jq
607
+
608
+ # 3. Send a prompt into a session (exactly-once: clientId + seq)
609
+ curl -s -X POST "$API/api/sessions/$SID/input" \
610
+ -H 'Content-Type: application/json' \
611
+ -d '{"input":"Run the test suite and summarize failures","useMux":true,"clientId":"agent-1","seq":1}'
612
+
613
+ # 4. Read the terminal back
614
+ curl -s "$API/api/sessions/$SID/output" | jq -r '.data // .'
615
+
616
+ # 5. Stream live events (session output, agent activity, status)
617
+ curl -sN "$API/api/events" # Server-Sent Events
618
+
619
+ # 6. Schedule recurring work (cron-style job)
620
+ curl -s -X POST "$API/api/cron/jobs" \
621
+ -H 'Content-Type: application/json' \
622
+ -d '{"name":"nightly-deps","agentType":"claude","workingDir":"/home/me/proj",
623
+ "promptMode":"inline_text","promptText":"Update dependencies and open a PR",
624
+ "inputMode":"typed","scheduleType":"daily","dailyTime":"03:00",
625
+ "enabled":true,"concurrencyPolicy":"warn_only"}' | jq
626
+
627
+ # 7. Inspect background sub-agents and their transcripts
628
+ curl -s "$API/api/subagents" | jq '.data // .'
629
+ curl -s "$API/api/subagents/$AID/transcript" | jq -r '.data // .'
630
+
631
+ # 8. Whole-system snapshot (sessions, settings, respawn, stats)
632
+ curl -s "$API/api/status" | jq
633
+ ```
634
+
635
+ ### Or use the bundled CLI
636
+
637
+ The same operations are available as commands (`codeman <cmd>`, aliases in parentheses) — handy from a shell tool inside a session:
638
+
639
+ ```bash
640
+ codeman session start -d /path/to/repo # (s) start a session
641
+ codeman session list # list sessions
642
+ codeman session logs <id> # tail output
643
+ codeman task add "fix the failing test" # (t) queue a task
644
+ codeman ralph start --min-hours 8 # (r) launch the autonomous loop
645
+ codeman attach <path> # attach a Claude hook context
646
+ ```
647
+
648
+ ### Hooks (events flowing _back_ to Codeman)
649
+
650
+ Codeman registers Claude Code hooks that `POST /api/hook-event` (`permission_prompt`, `idle_prompt`, `stop`, `task_completed`, …) so the dashboard reacts in real time. This endpoint is auth-exempt on loopback but, under a managed tunnel, requires the `X-Codeman-Hook-Secret` header (read it from `$CODEMAN_HOOK_SECRET_FILE`). You normally don't call this by hand — Codeman wires it up — but it's how the autonomy layers "see" what the agent is doing.
651
+
652
+ > Full endpoint list and request/response shapes follow.
496
653
 
497
654
  ---
498
655
 
499
656
  ## API
500
657
 
501
- REST over Fastify — **~140 handlers across 15 route modules**, plus an SSE stream and a WebSocket terminal channel. A representative subset:
658
+ REST over Fastify — **~160 handlers across 18 route modules**, plus an SSE stream and a WebSocket terminal channel. All responses use the `ApiResponse<T>` envelope (`{success, data}` / `{success, error, errorCode}`); `/api/v1/*` is a stable alias. A representative subset:
502
659
 
503
660
  ### Sessions
504
- | Method | Endpoint | Description |
505
- |--------|----------|-------------|
506
- | `GET` | `/api/sessions` | List all |
507
- | `POST` | `/api/quick-start` | Create case + start session |
508
- | `DELETE` | `/api/sessions/:id` | Delete session |
509
- | `POST` | `/api/sessions/:id/input` | Send input |
661
+
662
+ | Method | Endpoint | Description |
663
+ | -------- | -------------------------- | ---------------------------------------------------------------------------------- |
664
+ | `GET` | `/api/sessions` | List all |
665
+ | `POST` | `/api/quick-start` | Create case + start session (`{caseName?, mode?, effort?, envOverrides?}`) |
666
+ | `POST` | `/api/sessions/:id/input` | Send input (`{input, useMux?, clientId?, seq?}` — `clientId`+`seq` = exactly-once) |
667
+ | `GET` | `/api/sessions/:id/output` | Read terminal output |
668
+ | `DELETE` | `/api/sessions/:id` | Delete session |
510
669
 
511
670
  ### Respawn
512
- | Method | Endpoint | Description |
513
- |--------|----------|-------------|
671
+
672
+ | Method | Endpoint | Description |
673
+ | ------ | ---------------------------------- | -------------------------- |
514
674
  | `POST` | `/api/sessions/:id/respawn/enable` | Enable with config + timer |
515
- | `POST` | `/api/sessions/:id/respawn/stop` | Stop controller |
516
- | `PUT` | `/api/sessions/:id/respawn/config` | Update config |
675
+ | `POST` | `/api/sessions/:id/respawn/stop` | Stop controller |
676
+ | `PUT` | `/api/sessions/:id/respawn/config` | Update config |
517
677
 
518
678
  ### Ralph / Todo
519
- | Method | Endpoint | Description |
520
- |--------|----------|-------------|
521
- | `GET` | `/api/sessions/:id/ralph-state` | Get loop state + todos |
522
- | `POST` | `/api/sessions/:id/ralph-config` | Configure tracking |
679
+
680
+ | Method | Endpoint | Description |
681
+ | ------ | -------------------------------- | ---------------------- |
682
+ | `GET` | `/api/sessions/:id/ralph-state` | Get loop state + todos |
683
+ | `POST` | `/api/sessions/:id/ralph-config` | Configure tracking |
523
684
 
524
685
  ### Orchestrator
525
- | Method | Endpoint | Description |
526
- |--------|----------|-------------|
527
- | `POST` | `/api/orchestrator/start` | Start orchestration from a goal |
528
- | `POST` | `/api/orchestrator/approve` | Approve the generated plan |
529
- | `GET` | `/api/orchestrator/status` | Current phase + progress |
530
- | `POST` | `/api/orchestrator/stop` | Stop and clean up |
686
+
687
+ | Method | Endpoint | Description |
688
+ | ------ | --------------------------- | ------------------------------- |
689
+ | `POST` | `/api/orchestrator/start` | Start orchestration from a goal |
690
+ | `POST` | `/api/orchestrator/approve` | Approve the generated plan |
691
+ | `GET` | `/api/orchestrator/status` | Current phase + progress |
692
+ | `POST` | `/api/orchestrator/stop` | Stop and clean up |
693
+
694
+ ### Cron (scheduled jobs)
695
+
696
+ | Method | Endpoint | Description |
697
+ | ---------------- | ---------------------------- | ----------------------- |
698
+ | `GET` / `POST` | `/api/cron/jobs` | List / create cron jobs |
699
+ | `PUT` / `DELETE` | `/api/cron/jobs/:id` | Update / delete a job |
700
+ | `PUT` | `/api/cron/jobs/:id/enabled` | Enable / disable |
701
+ | `POST` | `/api/cron/jobs/:id/run` | Run now |
702
+ | `GET` | `/api/cron/jobs/:id/runs` | Run history |
531
703
 
532
704
  ### Subagents
533
- | Method | Endpoint | Description |
534
- |--------|----------|-------------|
535
- | `GET` | `/api/subagents` | List all background agents |
536
- | `GET` | `/api/subagents/:id` | Agent info and status |
537
- | `GET` | `/api/subagents/:id/transcript` | Full activity transcript |
538
- | `DELETE` | `/api/subagents/:id` | Kill agent process |
705
+
706
+ | Method | Endpoint | Description |
707
+ | -------- | ------------------------------- | -------------------------- |
708
+ | `GET` | `/api/subagents` | List all background agents |
709
+ | `GET` | `/api/subagents/:id` | Agent info and status |
710
+ | `GET` | `/api/subagents/:id/transcript` | Full activity transcript |
711
+ | `DELETE` | `/api/subagents/:id` | Kill agent process |
539
712
 
540
713
  ### System
541
- | Method | Endpoint | Description |
542
- |--------|----------|-------------|
543
- | `GET` | `/api/events` | SSE stream |
544
- | `GET` | `/api/status` | Full app state |
545
- | `POST` | `/api/hook-event` | Hook callbacks |
546
- | `GET` | `/api/system/update/check` | Check for a new release |
547
- | `POST` | `/api/system/update` | Self-update (git-clone installs) |
548
- | `POST` | `/api/clipboard` | Push text to all connected browsers (`{text}`) |
549
- | `GET` | `/api/sessions/:id/run-summary` | Timeline + stats |
714
+
715
+ | Method | Endpoint | Description |
716
+ | ------ | ------------------------------- | ---------------------------------------------- |
717
+ | `GET` | `/api/events` | SSE stream |
718
+ | `GET` | `/api/status` | Full app state |
719
+ | `POST` | `/api/hook-event` | Hook callbacks |
720
+ | `GET` | `/api/system/update/check` | Check for a new release |
721
+ | `POST` | `/api/system/update` | Self-update (git-clone installs) |
722
+ | `POST` | `/api/clipboard` | Push text to all connected browsers (`{text}`) |
723
+ | `GET` | `/api/sessions/:id/run-summary` | Timeline + stats |
550
724
 
551
725
  ---
552
726
 
@@ -624,14 +798,14 @@ See [CLAUDE.md](./CLAUDE.md) for full documentation.
624
798
 
625
799
  The codebase went through a comprehensive 7-phase refactoring that eliminated god objects, centralized configuration, and established modular architecture:
626
800
 
627
- | Phase | What changed | Impact |
628
- |-------|-------------|--------|
629
- | **Performance** | Cached endpoints, SSE adaptive batching, buffer chunking | Sub-16ms terminal latency |
630
- | **Route extraction** | `server.ts` split into 15 domain route modules + auth middleware + port interfaces | **−67%** server.ts LOC (6,736 → 2,254) |
631
- | **Domain splitting** | `types.ts` → 16 domain files, `ralph-tracker` → 7 files, `respawn-controller` → 5 files, `session` → 6 files | No more god files |
632
- | **Frontend modules** | `app.js` → 18 extracted modules across infra, domain & feature layers | app.js core down to **~3.4K LOC** |
633
- | **Config consolidation** | ~70 scattered magic numbers → 10 domain-focused config files | Zero cross-file duplicates |
634
- | **Test infrastructure** | Shared mock library, 12 route test files, consolidated MockSession | Testable route handlers via `app.inject()` |
801
+ | Phase | What changed | Impact |
802
+ | ------------------------ | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------ |
803
+ | **Performance** | Cached endpoints, SSE adaptive batching, buffer chunking | Sub-16ms terminal latency |
804
+ | **Route extraction** | `server.ts` split into 15 domain route modules + auth middleware + port interfaces | **−67%** server.ts LOC (6,736 → 2,254) |
805
+ | **Domain splitting** | `types.ts` → 16 domain files, `ralph-tracker` → 7 files, `respawn-controller` → 5 files, `session` → 6 files | No more god files |
806
+ | **Frontend modules** | `app.js` → 18 extracted modules across infra, domain & feature layers | app.js core down to **~3.4K LOC** |
807
+ | **Config consolidation** | ~70 scattered magic numbers → 10 domain-focused config files | Zero cross-file duplicates |
808
+ | **Test infrastructure** | Shared mock library, 12 route test files, consolidated MockSession | Testable route handlers via `app.inject()` |
635
809
 
636
810
  Full details: [`docs/archive/code-structure-findings.md`](docs/archive/code-structure-findings.md)
637
811
 
@@ -1,5 +1,18 @@
1
1
  /**
2
2
  * @fileoverview Parses terminal magic links that request attachment cards.
3
3
  */
4
+ export interface TerminalAttachmentRequest {
5
+ path: string;
6
+ source: 'external' | 'codex-generated';
7
+ }
8
+ export interface ParseTerminalAttachmentOptions {
9
+ /**
10
+ * Enable the Codex `Saved to: file://...` scanner. Only codex-mode sessions
11
+ * may set this — the relaxed codex-generated trust policy must never be
12
+ * reachable from other modes' (prompt-injectable) terminal output.
13
+ */
14
+ codexArtifacts?: boolean;
15
+ }
4
16
  export declare function parseAttachmentMagicLinks(data: string): string[];
17
+ export declare function parseTerminalAttachmentRequests(data: string, options?: ParseTerminalAttachmentOptions): TerminalAttachmentRequest[];
5
18
  //# sourceMappingURL=attachment-magic.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"attachment-magic.d.ts","sourceRoot":"","sources":["../src/attachment-magic.ts"],"names":[],"mappings":"AAAA;;GAEG;AAOH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAqBhE"}
1
+ {"version":3,"file":"attachment-magic.d.ts","sourceRoot":"","sources":["../src/attachment-magic.ts"],"names":[],"mappings":"AAAA;;GAEG;AAUH,MAAM,WAAW,yBAAyB;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,UAAU,GAAG,iBAAiB,CAAC;CACxC;AAED,MAAM,WAAW,8BAA8B;IAC7C;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAEhE;AAED,wBAAgB,+BAA+B,CAC7C,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,8BAAmC,GAC3C,yBAAyB,EAAE,CAe7B"}
@@ -2,9 +2,30 @@
2
2
  * @fileoverview Parses terminal magic links that request attachment cards.
3
3
  */
4
4
  import { isAbsolute } from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
5
6
  import { isSupportedAttachmentExtension } from './attachment-registry.js';
7
+ import { stripAnsi } from './utils/index.js';
6
8
  const MAGIC_LINK_RE = /codeman:\/\/attach\?([^\s<>"']+)/g;
9
+ const CODEX_SAVED_FILE_RE = /\bSaved to:\s*(file:\/\/[^\s<>"']+)/gi;
7
10
  export function parseAttachmentMagicLinks(data) {
11
+ return parseMagicAttachmentRequests(data).map((request) => request.path);
12
+ }
13
+ export function parseTerminalAttachmentRequests(data, options = {}) {
14
+ const results = [];
15
+ const seen = new Set();
16
+ const requests = options.codexArtifacts
17
+ ? [...parseMagicAttachmentRequests(data), ...parseCodexGeneratedArtifactRequests(data)]
18
+ : parseMagicAttachmentRequests(data);
19
+ for (const request of requests) {
20
+ const key = `${request.source}:${request.path}`;
21
+ if (seen.has(key))
22
+ continue;
23
+ seen.add(key);
24
+ results.push(request);
25
+ }
26
+ return results;
27
+ }
28
+ function parseMagicAttachmentRequests(data) {
8
29
  const results = [];
9
30
  const seen = new Set();
10
31
  for (const match of data.matchAll(MAGIC_LINK_RE)) {
@@ -26,6 +47,32 @@ export function parseAttachmentMagicLinks(data) {
26
47
  // Ignore malformed terminal text. Magic links are advisory.
27
48
  }
28
49
  }
50
+ return results.map((path) => ({ path, source: 'external' }));
51
+ }
52
+ function parseCodexGeneratedArtifactRequests(data) {
53
+ const results = [];
54
+ const seen = new Set();
55
+ // Codex styles its TUI output — strip ANSI first so a trailing SGR reset
56
+ // (e.g. `...mockup.png\x1b[0m`) doesn't ride into the captured URL and break
57
+ // the extension allowlist check.
58
+ for (const match of stripAnsi(data).matchAll(CODEX_SAVED_FILE_RE)) {
59
+ const rawUrl = trimTrailingPunctuation(match[1] || '');
60
+ try {
61
+ const filePath = fileURLToPath(rawUrl);
62
+ if (!isAbsolute(filePath))
63
+ continue;
64
+ const extension = filePath.split('.').pop()?.toLowerCase() || '';
65
+ if (!isSupportedAttachmentExtension(extension))
66
+ continue;
67
+ if (seen.has(filePath))
68
+ continue;
69
+ seen.add(filePath);
70
+ results.push({ path: filePath, source: 'codex-generated' });
71
+ }
72
+ catch {
73
+ // Ignore malformed terminal text. Generated-artifact links are advisory.
74
+ }
75
+ }
29
76
  return results;
30
77
  }
31
78
  function trimTrailingPunctuation(value) {
@@ -1 +1 @@
1
- {"version":3,"file":"attachment-magic.js","sourceRoot":"","sources":["../src/attachment-magic.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,8BAA8B,EAAE,MAAM,0BAA0B,CAAC;AAE1E,MAAM,aAAa,GAAG,mCAAmC,CAAC;AAE1D,MAAM,UAAU,yBAAyB,CAAC,IAAY;IACpD,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAE/B,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC;QACjD,MAAM,KAAK,GAAG,uBAAuB,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QACtD,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC,KAAK,CAAC,CAAC;YAC1C,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YACpC,IAAI,CAAC,QAAQ,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC;gBAAE,SAAS;YACjD,MAAM,SAAS,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;YACjE,IAAI,CAAC,8BAA8B,CAAC,SAAS,CAAC;gBAAE,SAAS;YACzD,IAAI,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC;gBAAE,SAAS;YACjC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YACnB,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACzB,CAAC;QAAC,MAAM,CAAC;YACP,4DAA4D;QAC9D,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,SAAS,uBAAuB,CAAC,KAAa;IAC5C,OAAO,KAAK,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;AACzC,CAAC"}
1
+ {"version":3,"file":"attachment-magic.js","sourceRoot":"","sources":["../src/attachment-magic.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"attachment-registry.d.ts","sourceRoot":"","sources":["../src/attachment-registry.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAQH,OAAO,KAAK,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAIlF,MAAM,MAAM,gBAAgB,GAAG,UAAU,GAAG,UAAU,CAAC;AAEvD,MAAM,WAAW,gBAAgB;IAC/B,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,sBAAsB,CAAC;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,gBAAgB,CAAC;CAC1B;AAED,MAAM,WAAW,4BAA6B,SAAQ,uBAAuB;IAC3E,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,gBAAgB,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,qBAAa,2BAA4B,SAAQ,KAAK;IAGlD,QAAQ,CAAC,UAAU,EAAE,MAAM;gBAD3B,OAAO,EAAE,MAAM,EACN,UAAU,GAAE,MAAY;CAIpC;AAMD,cAAM,kBAAkB;IACtB,OAAO,CAAC,gBAAgB,CAAoD;IAE5E,QAAQ,CAAC,MAAM,EAAE,gBAAgB,GAAG,IAAI;IAexC,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS;IAI1E,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS;IASjF,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;CAGtC;AAED,eAAO,MAAM,kBAAkB,oBAA2B,CAAC;AAE3D,wBAAgB,8BAA8B,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAEzE;AAED,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,sBAAsB,CAQ3E;AAED,wBAAgB,qBAAqB,CACnC,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,MAAM,GACnB;IACD,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;CACtB,CAOA;AAED,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAEvF;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,gBAAgB,GAAG,4BAA4B,CAe9F;AAED,sDAAsD;AACtD,MAAM,WAAW,iCAAiC;IAChD;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;;;;;;;OAQG;IACH,yBAAyB,CAAC,EAAE,OAAO,CAAC;CACrC;AAED,wBAAsB,0BAA0B,CAC9C,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,MAAM,EACrB,OAAO,GAAE,iCAAsC,GAC9C,OAAO,CAAC,4BAA4B,CAAC,CAoEvC"}
1
+ {"version":3,"file":"attachment-registry.d.ts","sourceRoot":"","sources":["../src/attachment-registry.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAQH,OAAO,KAAK,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAelF,MAAM,MAAM,gBAAgB,GAAG,UAAU,GAAG,UAAU,CAAC;AAEvD,MAAM,WAAW,gBAAgB;IAC/B,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,sBAAsB,CAAC;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,gBAAgB,CAAC;CAC1B;AAED,MAAM,WAAW,4BAA6B,SAAQ,uBAAuB;IAC3E,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,gBAAgB,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,qBAAa,2BAA4B,SAAQ,KAAK;IAGlD,QAAQ,CAAC,UAAU,EAAE,MAAM;gBAD3B,OAAO,EAAE,MAAM,EACN,UAAU,GAAE,MAAY;CAIpC;AAMD,cAAM,kBAAkB;IACtB,OAAO,CAAC,gBAAgB,CAAoD;IAE5E,QAAQ,CAAC,MAAM,EAAE,gBAAgB,GAAG,IAAI;IAexC,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS;IAI1E,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS;IASjF,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;CAGtC;AAED,eAAO,MAAM,kBAAkB,oBAA2B,CAAC;AAE3D,wBAAgB,8BAA8B,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAEzE;AAED,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,sBAAsB,CAQ3E;AAED,wBAAgB,qBAAqB,CACnC,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,MAAM,GACnB;IACD,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;CACtB,CAOA;AAED,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAEvF;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,gBAAgB,GAAG,4BAA4B,CAe9F;AAED,sDAAsD;AACtD,MAAM,WAAW,iCAAiC;IAChD;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;;;;;;;OAQG;IACH,yBAAyB,CAAC,EAAE,OAAO,CAAC;CACrC;AAED,wBAAsB,0BAA0B,CAC9C,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,MAAM,EACrB,OAAO,GAAE,iCAAsC,GAC9C,OAAO,CAAC,4BAA4B,CAAC,CAoEvC"}