chore: rename Claudeman to Codeman

Full product rename across 109 files (~834 occurrences):
- Env vars: CLAUDEMAN_* → CODEMAN_*
- Data dirs: ~/.claudeman/ → ~/.codeman/, ~/claudeman-cases/ → ~/codeman-cases/
- tmux prefix: claudeman- → codeman-
- localStorage: claudeman-* → codeman-*
- Package/CLI: claudeman → codeman
- GitHub repo: Ark0N/Claudeman → Ark0N/Codeman
- systemd service: claudeman-web → codeman-web
- Class: ClaudemanApp → CodemanApp

Migration infrastructure for seamless transition:
- state-store.ts: auto-migrates data directories on startup
- tmux-manager.ts: dual-prefix detection (legacy claudeman- sessions)
- app.js: localStorage key migration (preserves old keys)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
arkon
2026-02-26 16:44:34 +01:00
co-authored by Claude Opus 4.6
parent 3ccd22161c
commit 3795c45cc1
109 changed files with 711 additions and 679 deletions
@@ -15,7 +15,7 @@ When local echo is enabled, keystrokes accumulate in the `LocalEchoOverlay.pendi
When local echo is enabled, keystrokes accumulate **only** in `LocalEchoOverlay.pendingText` (a client-side string). Nothing reaches the server PTY until Enter is pressed. This creates three failure modes:
1. **Tab switch loses PTY state** — switching sessions saves overlay text to `localEchoTextCache` (a Map), but the actual Claude Code Ink process has no knowledge of what was typed. If respawn or `/clear` fires on that session, the cached text is meaningless.
2. **Session death loses input** — if the session crashes or respawns while text is pending in the overlay, that input is gone (localStorage backup `claudeman_local_echo_pending` only survives page reloads, not session resets).
2. **Session death loses input** — if the session crashes or respawns while text is pending in the overlay, that input is gone (localStorage backup `codeman_local_echo_pending` only survives page reloads, not session resets).
3. **Tab completion impossible** — pressing Tab with pending overlay text sends the raw Tab character to a PTY that has no knowledge of the typed text, so completion fails.
## Goal
+1 -1
View File
@@ -5,7 +5,7 @@
When local echo is enabled, keystrokes accumulate **only** in `LocalEchoOverlay.pendingText` (a client-side string). Nothing reaches the server PTY until Enter is pressed. This creates three failure modes:
1. **Tab switch loses PTY state** — switching sessions saves overlay text to `localEchoTextCache` (a Map), but the actual Claude Code Ink process has no knowledge of what was typed. If respawn or `/clear` fires on that session, the cached text is meaningless.
2. **Session death loses input** — if the session crashes or respawns while text is pending in the overlay, that input is gone (localStorage backup `claudeman_local_echo_pending` only survives page reloads, not session resets).
2. **Session death loses input** — if the session crashes or respawns while text is pending in the overlay, that input is gone (localStorage backup `codeman_local_echo_pending` only survives page reloads, not session resets).
3. **Tab completion impossible** — pressing Tab with pending overlay text sends the raw Tab character to a PTY that has no knowledge of the typed text, so completion fails.
## Goal
+4 -4
View File
@@ -1,6 +1,6 @@
# Browser Testing Guide for Claudeman
# Browser Testing Guide for Codeman
This guide documents the browser testing infrastructure, framework comparison results, and best practices for testing the Claudeman web UI.
This guide documents the browser testing infrastructure, framework comparison results, and best practices for testing the Codeman web UI.
## Quick Start
@@ -14,7 +14,7 @@ npm test -- test/browser-e2e.test.ts
## Framework Comparison Results
We tested three browser automation frameworks against the Claudeman web UI:
We tested three browser automation frameworks against the Codeman web UI:
| Framework | Avg Duration | Best For |
|-----------|--------------|----------|
@@ -114,7 +114,7 @@ await page.waitForSelector('.session-tab', { state: 'visible' });
// Assertions with expect
await expect(page.locator('.header')).toBeVisible();
await expect(page).toHaveTitle('Claudeman');
await expect(page).toHaveTitle('Codeman');
await browser.close();
```
+1 -1
View File
@@ -1,6 +1,6 @@
# Codebase Cleanup Findings
Compiled from parallel analysis of the entire Claudeman codebase by 3 research agents (2026-02-19).
Compiled from parallel analysis of the entire Codeman codebase by 3 research agents (2026-02-19).
## P0 — Bug Fix
+1 -1
View File
@@ -2,7 +2,7 @@
**Date**: 2026-02-18
**Audit by**: 4-agent team (css-analyst, js-analyst, server-analyst, deps-analyst)
**Scope**: First browser load of Claudeman web UI at `/`
**Scope**: First browser load of Codeman web UI at `/`
---

Before

Width:  |  Height:  |  Size: 859 KiB

After

Width:  |  Height:  |  Size: 859 KiB

@@ -1,3 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 60">
<text x="160" y="48" font-family="system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif" font-size="52" font-weight="700" fill="#60a5fa" text-anchor="middle">Claudeman</text>
<text x="160" y="48" font-family="system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif" font-size="52" font-weight="700" fill="#60a5fa" text-anchor="middle">Codeman</text>
</svg>

Before

Width:  |  Height:  |  Size: 249 B

After

Width:  |  Height:  |  Size: 247 B

+1 -1
View File
@@ -2,7 +2,7 @@
## Context
User accesses Claudeman remotely from Thailand to Switzerland over Tailscale (~200-300ms RTT).
User accesses Codeman remotely from Thailand to Switzerland over Tailscale (~200-300ms RTT).
Every keystroke is invisible for 200-300ms before the server echoes it back. This makes typing
painfully slow on mobile. Previous attempts to write directly to xterm.js buffer failed because
Ink (Claude Code's terminal framework) does full-screen redraws that corrupt injected characters.
+57 -57
View File
@@ -1,4 +1,4 @@
# OpenCode Integration Plan for Claudeman
# OpenCode Integration Plan for Codeman
> **Author**: Claude Opus 4.6 | **Date**: 2026-02-26
> **Status**: Draft — Re-reviewed, MVP scope finalized. NOT pushed to GitHub
@@ -36,13 +36,13 @@
23. [Appendix A: OpenCode CLI Reference](#appendix-a-opencode-cli-reference)
24. [Appendix B: OpenCode Plugin Events](#appendix-b-opencode-plugin-events)
25. [Appendix C: OpenCode Permission Config](#appendix-c-opencode-permission-config)
26. [Appendix D: Current Claudeman Session Spawn Flow (Annotated)](#appendix-d-current-claudeman-session-spawn-flow-annotated)
26. [Appendix D: Current Codeman Session Spawn Flow (Annotated)](#appendix-d-current-codeman-session-spawn-flow-annotated)
---
## 1. Executive Summary
This plan details how to integrate [OpenCode](https://opencode.ai) — the popular open-source AI coding CLI (111k+ GitHub stars, 75+ model providers) — into Claudeman as a first-class session type alongside Claude Code and shell sessions.
This plan details how to integrate [OpenCode](https://opencode.ai) — the popular open-source AI coding CLI (111k+ GitHub stars, 75+ model providers) — into Codeman as a first-class session type alongside Claude Code and shell sessions.
### Core Approach
@@ -53,7 +53,7 @@ Extend the existing `SessionMode` type from `'claude' | 'shell'` to `'claude' |
| Strategy | Approach | Complexity | Value |
|----------|----------|------------|-------|
| **A: TUI-in-tmux** | Spawn `opencode` CLI in tmux, render in xterm.js | Medium | Full visual parity with Claude Code |
| **B: Server API bridge** | Run `opencode serve` + proxy its API through Claudeman | High | Structured data, session control, token tracking |
| **B: Server API bridge** | Run `opencode serve` + proxy its API through Codeman | High | Structured data, session control, token tracking |
**Recommended path**: Start with Strategy A (TUI-in-tmux) since it mirrors the existing Claude Code pattern exactly. Then layer Strategy B on top for advanced features like structured token tracking and model switching.
@@ -71,7 +71,7 @@ Extend the existing `SessionMode` type from `'claude' | 'shell'` to `'claude' |
> | **Hooks plugin bridge** | Plugin event names are speculative/unverified. Requires Phase 0 validation that hasn't happened. | Phase 0 plugin verification |
> | **AI idle checker** | Spawns `claude -p` for analysis. Won't work if only OpenCode is installed. | Claude CLI availability or `opencode run` fallback |
>
> **What ships in the MVP**: Phases 0-3 (spawn in tmux, render in xterm.js) + Phase 5 (API routes, mode selector, tab badges, create/kill sessions). Users can interact with OpenCode manually — type prompts, see output, manage sessions from the Claudeman web UI. This alone is the core value: multi-model AI sessions in one management interface.
> **What ships in the MVP**: Phases 0-3 (spawn in tmux, render in xterm.js) + Phase 5 (API routes, mode selector, tab badges, create/kill sessions). Users can interact with OpenCode manually — type prompts, see output, manage sessions from the Codeman web UI. This alone is the core value: multi-model AI sessions in one management interface.
### Why OpenCode?
@@ -79,7 +79,7 @@ Extend the existing `SessionMode` type from `'claude' | 'shell'` to `'claude' |
- **Privacy-first**: Can run fully local via Ollama — no code ever leaves the machine
- **Open source**: MIT licensed, active community (700+ contributors)
- **Client/server**: Built-in `opencode serve` mode enables richer programmatic integration
- **Plugin system**: JS/TS plugins with rich event hooks (including `session.idle` — perfect for Claudeman)
- **Plugin system**: JS/TS plugins with rich event hooks (including `session.idle` — perfect for Codeman)
---
@@ -222,7 +222,7 @@ OPENCODE_CLIENT=... # Client identifier (default: "cli")
## 3. Architecture Comparison: Claude Code vs OpenCode
### Current Claudeman Session Flow (Claude Code)
### Current Codeman Session Flow (Claude Code)
```
POST /api/sessions → new Session({mode: 'claude', mux: TmuxManager})
@@ -230,10 +230,10 @@ POST /api/sessions/:id/interactive → session.startInteractive()
↓
TmuxManager.createSession()
↓
tmux new-session -ds "claudeman-<id>"
tmux new-session -ds "codeman-<id>"
tmux respawn-pane -k -t ... "claude --dangerously-skip-permissions --session-id <id>"
↓
pty.spawn('tmux', ['attach-session', '-t', 'claudeman-<id>'])
pty.spawn('tmux', ['attach-session', '-t', 'codeman-<id>'])
↓
ptyProcess.onData() → emit('terminal') → SSE broadcast → xterm.js
```
@@ -246,10 +246,10 @@ POST /api/sessions/:id/interactive → session.startInteractive()
↓
TmuxManager.createSession()
↓
tmux new-session -ds "claudeman-<id>"
tmux new-session -ds "codeman-<id>"
tmux respawn-pane -k -t ... "opencode --model <model>"
↓
pty.spawn('tmux', ['attach-session', '-t', 'claudeman-<id>'])
pty.spawn('tmux', ['attach-session', '-t', 'codeman-<id>'])
↓
ptyProcess.onData() → emit('terminal') → SSE broadcast → xterm.js
```
@@ -268,7 +268,7 @@ Strategy A (TUI-in-tmux) for terminal rendering
+
opencode serve (background, port 4096+N)
↓
Claudeman proxy routes → GET /session/current, POST /session/message, SSE /events
Codeman proxy routes → GET /session/current, POST /session/message, SSE /events
↓
Structured data for: token tracking, session management, model switching
```
@@ -301,7 +301,7 @@ These systems work identically for OpenCode sessions:
- Terminal data streaming via `ptyProcess.onData()`
- SSE event broadcasting via `broadcast()`
- xterm.js terminal rendering in the browser
- State persistence to `~/.claudeman/state.json`
- State persistence to `~/.codeman/state.json`
- Session CRUD API routes (create, get, delete)
- Tab management in frontend
- Session kill/cleanup logic (`TmuxManager.killSession()`)
@@ -324,7 +324,7 @@ These systems work identically for OpenCode sessions:
| Subagent detection | `BashToolParser` + `SubagentWatcher` | Different tool output format | DEFERRED |
| Ralph completion | `<promise>PHRASE</promise>` tags | Not applicable (needs alternative) | DEFERRED |
| Hooks events | `permission_prompt`, `idle_prompt`, `stop` | `permission.asked`, `session.idle`, `session.status` | DEFERRED |
| Auto-compact | Claudeman sends `/compact` at token threshold | OpenCode has built-in `compaction.auto: true` | DEFERRED |
| Auto-compact | Codeman sends `/compact` at token threshold | OpenCode has built-in `compaction.auto: true` | DEFERRED |
---
@@ -346,7 +346,7 @@ opencode --version
# 3. Test interactive TUI
opencode
# 4. Test in tmux (simulating Claudeman's spawn pattern)
# 4. Test in tmux (simulating Codeman's spawn pattern)
tmux new-session -ds "test-opencode" -c /tmp -x 120 -y 40
tmux set-option -t "test-opencode" remain-on-exit on
tmux respawn-pane -k -t "test-opencode" 'opencode --model anthropic/claude-sonnet-4-5'
@@ -774,10 +774,10 @@ function buildEnvExports(mode: string, sessionId: string, muxName: string, openC
`export LANG=en_US.UTF-8`,
`export LC_ALL=en_US.UTF-8`,
`unset COLORTERM`, // Prevent color issues in tmux
`export CLAUDEMAN_MUX=1`,
`export CLAUDEMAN_SESSION_ID=${sessionId}`,
`export CLAUDEMAN_MUX_NAME=${muxName}`,
`export CLAUDEMAN_API_URL=${process.env.CLAUDEMAN_API_URL || 'http://localhost:3000'}`,
`export CODEMAN_MUX=1`,
`export CODEMAN_SESSION_ID=${sessionId}`,
`export CODEMAN_MUX_NAME=${muxName}`,
`export CODEMAN_API_URL=${process.env.CODEMAN_API_URL || 'http://localhost:3000'}`,
];
if (mode === 'opencode') {
@@ -905,7 +905,7 @@ const mode = cmd.includes('opencode') ? 'opencode' : 'claude';
### Known Limitations (from review)
> **[REVIEW P1-6]** OpenCode does not have `--session-id <claudeman-id>` for initial session creation (only `--session <id>` for continuing existing sessions). This means:
> **[REVIEW P1-6]** OpenCode does not have `--session-id <codeman-id>` for initial session creation (only `--session <id>` for continuing existing sessions). This means:
> - `_claudeSessionId = this.id` correlation (session.ts:405) won't work for OpenCode
> - Subagent-session correlation via Session ID matching won't work
> - Transcript watching via Claude session ID path won't work
@@ -1121,7 +1121,7 @@ private parseTokens(data: string): void {
### Working/Idle State Tracking
For Claude, Claudeman uses spinner characters and keywords. For OpenCode:
For Claude, Codeman uses spinner characters and keywords. For OpenCode:
> **[REVIEW]** The original logic had a bug: `Date.now() - this._lastActivityAt > 100` is checked AFTER setting `_lastActivityAt = Date.now()`, so the condition would never be true (0 > 100 = false). Fixed below.
@@ -1373,7 +1373,7 @@ if (session.mode === 'claude' && session.pid) {
> **[REVIEW C3] Phase reordered**: This was originally Phase 7 but has been moved before Respawn. The plugin bridge provides the reliable `session.idle` event that the respawn controller needs. Without it, respawn relies on output-silence-only detection, which is unreliable with Bubble Tea TUIs (see Review C1). **Do not implement respawn (Phase 7) until this phase is complete and tested.**
### Goal
Bridge Claudeman's hook system with OpenCode's plugin system for rich event forwarding.
Bridge Codeman's hook system with OpenCode's plugin system for rich event forwarding.
### Background: Two Different Approaches
@@ -1383,19 +1383,19 @@ Bridge Claudeman's hook system with OpenCode's plugin system for rich event forw
| Trigger | Hook name matches event type | Event name subscription |
| Key events | `stop`, `idle_prompt`, `permission_prompt`, `elicitation_dialog` | `session.idle`, `permission.asked`, `session.status` |
| Communication | Exit codes + environment variables | Function context + return values |
| Install | Auto-generated by Claudeman | Must be placed in `.opencode/plugins/` |
| Install | Auto-generated by Codeman | Must be placed in `.opencode/plugins/` |
### Claudeman Plugin for OpenCode
### Codeman Plugin for OpenCode
Create a Claudeman plugin that OpenCode loads, which communicates back to Claudeman's API:
Create a Codeman plugin that OpenCode loads, which communicates back to Codeman's API:
**File: `.opencode/plugins/claudeman-bridge.js`** (generated per session)
**File: `.opencode/plugins/codeman-bridge.js`** (generated per session)
```javascript
// This plugin bridges OpenCode events to Claudeman's API
export const claudemanBridge = async ({ project, $ }) => {
const apiUrl = process.env.CLAUDEMAN_API_URL || 'http://localhost:3000';
const sessionId = process.env.CLAUDEMAN_SESSION_ID;
// This plugin bridges OpenCode events to Codeman's API
export const codemanBridge = async ({ project, $ }) => {
const apiUrl = process.env.CODEMAN_API_URL || 'http://localhost:3000';
const sessionId = process.env.CODEMAN_SESSION_ID;
if (!sessionId) return {};
@@ -1440,7 +1440,7 @@ export const claudemanBridge = async ({ project, $ }) => {
### Plugin Installation
When creating an OpenCode session, Claudeman generates this plugin in the project's `.opencode/plugins/` directory:
When creating an OpenCode session, Codeman generates this plugin in the project's `.opencode/plugins/` directory:
```typescript
// In session.ts or a new opencode-hooks.ts:
@@ -1448,8 +1448,8 @@ async function installOpenCodePlugin(workingDir: string, sessionId: string): Pro
const pluginDir = join(workingDir, '.opencode', 'plugins');
await mkdirp(pluginDir);
const pluginContent = generateClaudemanBridgePlugin(sessionId);
await writeFile(join(pluginDir, 'claudeman-bridge.js'), pluginContent);
const pluginContent = generateCodemanBridgePlugin(sessionId);
await writeFile(join(pluginDir, 'codeman-bridge.js'), pluginContent);
}
```
@@ -1458,7 +1458,7 @@ async function installOpenCodePlugin(workingDir: string, sessionId: string): Pro
> **[REVIEW]** Add `session.compacted` to the plugin bridge — it's free and useful for tracking when OpenCode auto-compacts (relevant for token tracking and respawn timing).
```javascript
// Add to claudeman-bridge.js return object:
// Add to codeman-bridge.js return object:
'session.compacted': async (info) => {
await notifyClademan('session_compacted', { info });
},
@@ -1472,12 +1472,12 @@ async function installOpenCodePlugin(workingDir: string, sessionId: string): Pro
### Benefits of the Plugin Bridge
Once installed, Claudeman receives structured events from OpenCode:
Once installed, Codeman receives structured events from OpenCode:
- **`session.idle`** → Definitive idle detection (replaces output-silence guessing)
- **`permission.asked`** → Show permission prompts in Claudeman UI
- **`permission.asked`** → Show permission prompts in Codeman UI
- **`tool.execute.*`** → Tool call tracking (similar to BashToolParser for Claude)
- **`todo.updated`** → OpenCode's built-in todo system → Claudeman can display it
- **`session.error`** → Error surfacing in Claudeman UI
- **`todo.updated`** → OpenCode's built-in todo system → Codeman can display it
- **`session.error`** → Error surfacing in Codeman UI
- **`session.compacted`** → Track auto-compaction events
---
@@ -1664,7 +1664,7 @@ tmux session ─── opencode TUI (interactive, port N/A)
│
├── xterm.js (terminal rendering, Strategy A)
│
Claudeman ──── opencode serve (port 4096+N, background process)
Codeman ──── opencode serve (port 4096+N, background process)
│
├── GET /session/* → structured session data
├── POST /session/message → send prompt programmatically
@@ -1678,7 +1678,7 @@ Claudeman ──── opencode serve (port 4096+N, background process)
> **[REVIEW M8]** Running both `opencode` (TUI) and `opencode serve` in the same project directory creates a **shared SQLite state problem**. The TUI creates sessions in SQLite; the server reads from the same SQLite. But they're separate processes — there's no guarantee of session ID consistency between them.
>
> **Consider alternatives**:
> - (a) Use `opencode serve` INSTEAD of the TUI (not alongside it) — server becomes the single backend, Claudeman renders its own UI
> - (a) Use `opencode serve` INSTEAD of the TUI (not alongside it) — server becomes the single backend, Codeman renders its own UI
> - (b) Use `opencode attach` to connect the TUI to the server, making the server the single source of truth
> - (c) Accept the dual-process model with explicit documentation of limitations
>
@@ -1805,7 +1805,7 @@ if (this.mode === 'opencode' && this._openCodeConfig?.serverPort) {
|------|---------|-------|--------|
| `src/utils/ansi-content-filter.ts` | Strip cosmetic ANSI sequences for idle detection | 4 | DEFERRED |
| `src/completion-detector.ts` | `CompletionDetector` interface + implementations | 7 | DEFERRED |
| `src/opencode-plugin-generator.ts` | Generate `.opencode/plugins/claudeman-bridge.js` | 6 | DEFERRED |
| `src/opencode-plugin-generator.ts` | Generate `.opencode/plugins/codeman-bridge.js` | 6 | DEFERRED |
| `src/opencode-api-client.ts` | Client for OpenCode's REST API (Strategy B) | 8 | DEFERRED |
| `test/opencode-respawn.test.ts` | Tests for OpenCode respawn cycle (port 3156) | 7 | DEFERRED |
| `test/ansi-content-filter.test.ts` | Tests for ANSI content filter | 4 | DEFERRED |
@@ -1898,7 +1898,7 @@ npx vitest run test/opencode-respawn.test.ts
### Safety Rules
- **Never run OpenCode tests that spawn real tmux sessions inside Claudeman** (same safety rule as Claude tests)
- **Never run OpenCode tests that spawn real tmux sessions inside Codeman** (same safety rule as Claude tests)
- **Use MockSession** from `test/respawn-test-utils.ts` for respawn testing
- **Mock the opencode binary** for unit tests (`jest.mock` or stub)
- **Use unique test ports** (3155+) — never port 3000
@@ -1939,12 +1939,12 @@ npx vitest run test/opencode-respawn.test.ts
5. **Should we auto-generate `opencode.json` in the working directory?**
- Option A: Let OpenCode use existing project config (respect user settings)
- Option B: Generate a temporary one with Claudeman's settings
- Recommendation: Option A (use `OPENCODE_CONFIG_CONTENT` env var for Claudeman-specific overrides, don't modify project files)
- Option B: Generate a temporary one with Codeman's settings
- Recommendation: Option A (use `OPENCODE_CONFIG_CONTENT` env var for Codeman-specific overrides, don't modify project files)
6. **How should OpenCode session IDs map to Claudeman session IDs?**
6. **How should OpenCode session IDs map to Codeman session IDs?**
- OpenCode manages its own sessions (SQLite DB)
- We could pass `--session <claudeman-id>` but OpenCode IDs have different format
- We could pass `--session <codeman-id>` but OpenCode IDs have different format
- Recommendation: Let OpenCode manage its own sessions, store the mapping in SessionState
---
@@ -1970,7 +1970,7 @@ Phase 3: Tmux spawn (2-3 hours) ← CRITICAL INTEGRATION POINT
↓
Phase 5: API + frontend (2-3 hours) ← FIRST USER-VISIBLE RESULT
↓
✅ MVP COMPLETE — OpenCode sessions managed from Claudeman web UI
✅ MVP COMPLETE — OpenCode sessions managed from Codeman web UI
═══════════════════════════════════════════════════════
DEFERRED (requires real PTY data + verified APIs)
@@ -1991,7 +1991,7 @@ Phase 8: Server API — DEFERRED, optional advanced feature
| Milestone | Phase | What You Can Do | Scope |
|-----------|-------|-----------------|-------|
| **M1: "It renders"** | 0-3 | OpenCode TUI visible in xterm.js via Claudeman | **MVP** |
| **M1: "It renders"** | 0-3 | OpenCode TUI visible in xterm.js via Codeman | **MVP** |
| **M2: "It's usable"** | 5 | Create OpenCode sessions from web UI, type and interact, manage tabs | **MVP** |
| **M3: "It's observable"** | 4 | Idle/working state detection, ANSI content filter | Deferred |
| **M4: "It's smart"** | 6 | Plugin bridge provides definitive idle/permission/tool events | Deferred |
@@ -2012,7 +2012,7 @@ Phase 8: Server API — DEFERRED, optional advanced feature
## 21. Review Findings
> **Reviewed**: 2026-02-26 by a 4-agent team. Each agent reviewed a different area of the plan against the actual Claudeman codebase.
> **Reviewed**: 2026-02-26 by a 4-agent team. Each agent reviewed a different area of the plan against the actual Codeman codebase.
### Review Team
@@ -2182,7 +2182,7 @@ Environment Variables:
Full list of subscribable events in OpenCode's plugin system:
| Category | Event | Description | Claudeman Relevance |
| Category | Event | Description | Codeman Relevance |
|----------|-------|-------------|---------------------|
| **Command** | `command.executed` | Slash command run | Low |
| **Files** | `file.edited` | File modified | Medium (track changes) |
@@ -2194,7 +2194,7 @@ Full list of subscribable events in OpenCode's plugin system:
| | `message.updated` | Complete message | High (completion detection) |
| | `message.removed` | Message deleted | Low |
| | `message.part.removed` | Part deleted | Low |
| **Permissions** | `permission.asked` | Tool approval needed | **Critical** (show in Claudeman) |
| **Permissions** | `permission.asked` | Tool approval needed | **Critical** (show in Codeman) |
| | `permission.replied` | User responded | High (track approvals) |
| **Server** | `server.connected` | Server started | Medium |
| **Sessions** | `session.idle` | Agent finished working | **Critical** (idle detection!) |
@@ -2254,7 +2254,7 @@ Full list of subscribable events in OpenCode's plugin system:
- `"ask"` — Prompt user for approval
- `"deny"` — Block the action
### Delivery Method for Claudeman
### Delivery Method for Codeman
Use `OPENCODE_CONFIG_CONTENT` environment variable to inject permissions without modifying project files:
@@ -2265,7 +2265,7 @@ opencode --model anthropic/claude-sonnet-4-5
---
## Appendix D: Current Claudeman Session Spawn Flow (Annotated)
## Appendix D: Current Codeman Session Spawn Flow (Annotated)
### Exact Code Path (for reference during implementation)
@@ -2285,15 +2285,15 @@ opencode --model anthropic/claude-sonnet-4-5
→ If none: this._mux.createSession(id, workingDir, 'claude', ...)
4. TmuxManager.createSession() (tmux-manager.ts:225)
→ tmux new-session -ds "claudeman-<shortId>" -c <workingDir> -x 120 -y 40
→ tmux set-option -t "claudeman-<shortId>" remain-on-exit on
→ tmux new-session -ds "codeman-<shortId>" -c <workingDir> -x 120 -y 40
→ tmux set-option -t "codeman-<shortId>" remain-on-exit on
→ Build command: "export PATH=... && export LANG=... && claude --dangerously-skip-permissions --session-id <id>"
→ tmux respawn-pane -k -t "claudeman-<shortId>" '<command>'
→ tmux respawn-pane -k -t "codeman-<shortId>" '<command>'
→ Wait 100ms, configure tmux, get PID
→ Return MuxSession { muxName, pid, mode }
5. Back in startInteractive() (session.ts:~950)
→ pty.spawn('tmux', ['attach-session', '-t', 'claudeman-<shortId>'], {
→ pty.spawn('tmux', ['attach-session', '-t', 'codeman-<shortId>'], {
name: 'xterm-256color',
cols: 120, rows: 40,
env: { LANG, LC_ALL, TERM }
+2 -2
View File
@@ -1,7 +1,7 @@
# Performance Audit: First Page Load
**Date**: 2026-02-18
**Scope**: Browser first-load of Claudeman web UI (`/`)
**Scope**: Browser first-load of Codeman web UI (`/`)
**Method**: Static analysis by 4 parallel audit agents (server, frontend, SSE/xterm, asset pipeline)
---
@@ -40,7 +40,7 @@ Browser hits /
│
│ [FIRST PAINT blocked until ALL CSS downloaded + parsed]
│
├── JS executes: new ClaudemanApp().init()
├── JS executes: new CodemanApp().init()
│ ├── initTerminal() ................... (SYNC: new Terminal() + terminal.open() → canvas creation)
│ ├── connectSSE() → /api/events ....... (SSE → fires 'init' with getLightState())
│ ├── loadState() → /api/status ........ (DUPLICATE #1: same data as SSE init!)
+3 -3
View File
@@ -1,14 +1,14 @@
# Claudeman Performance Investigation Report
# Codeman Performance Investigation Report
**Date**: 2026-02-20
**Scope**: Why Claudeman feels sluggish when multiple Claude tabs are very busy
**Scope**: Why Codeman feels sluggish when multiple Claude tabs are very busy
**Method**: 4-agent parallel analysis of server, PTY pipeline, frontend, and background systems
---
## Executive Summary
When multiple Claude sessions are actively producing heavy terminal output (e.g., building, writing files, running tests), Claudeman's UI becomes sluggish. This investigation identified **14 bottlenecks** across 4 layers of the stack. The root cause is **cumulative event loop blocking** — no single operation is catastrophically slow, but dozens of small synchronous operations run on every PTY data chunk, and with N busy sessions producing chunks every few milliseconds, the event loop gets saturated.
When multiple Claude sessions are actively producing heavy terminal output (e.g., building, writing files, running tests), Codeman's UI becomes sluggish. This investigation identified **14 bottlenecks** across 4 layers of the stack. The root cause is **cumulative event loop blocking** — no single operation is catastrophically slow, but dozens of small synchronous operations run on every PTY data chunk, and with N busy sessions producing chunks every few milliseconds, the event loop gets saturated.
The most impactful findings are ranked by severity below.
+1 -1
View File
@@ -244,4 +244,4 @@ At iterations 5, 10, 20, 30, 50:
---
*This document is part of the Claudeman project. See [CLAUDE.md](../CLAUDE.md) for main documentation.*
*This document is part of the Codeman project. See [CLAUDE.md](../CLAUDE.md) for main documentation.*
+2 -2
View File
@@ -2,7 +2,7 @@
## Overview
This plan details improvements to Claudeman's Ralph Loop system based on best practices from the Ralph Claude Code repository (https://github.com/frankbria/ralph-claude-code).
This plan details improvements to Codeman's Ralph Loop system based on best practices from the Ralph Claude Code repository (https://github.com/frankbria/ralph-claude-code).
## Key Concepts to Implement
@@ -181,7 +181,7 @@ Three states: CLOSED → HALF_OPEN → OPEN
### 3.1 Template Library
- Bug Fix, Feature, Refactoring, Test Coverage, Documentation templates
- Template selector in wizard
- Custom templates in `~/.claudeman/templates/`
- Custom templates in `~/.codeman/templates/`
### 3.2 Tool Permissions
- Configure allowed Claude tools per loop
+8 -8
View File
@@ -19,7 +19,7 @@
8. [Prompt Templates](#prompt-templates)
9. [When to Use (and Not Use)](#when-to-use-and-not-use)
10. [Real-World Examples](#real-world-examples)
11. [Claudeman Implementation](#claudeman-implementation)
11. [Codeman Implementation](#codeman-implementation)
12. [Troubleshooting](#troubleshooting)
---
@@ -148,18 +148,18 @@ The completion phrase pattern is the core contract between Claude and the loop s
### False Positive Prevention
The official implementation (and Claudeman) prevents false positives when completion phrases appear in:
The official implementation (and Codeman) prevents false positives when completion phrases appear in:
- Initial prompts
- Documentation or examples
- Comments
**Solution**: Claudeman uses **occurrence-based detection** to distinguish prompts from actual completions:
**Solution**: Codeman uses **occurrence-based detection** to distinguish prompts from actual completions:
- **1st occurrence**: Store as expected phrase (likely in the prompt)
- **2nd occurrence**: Emit `completionDetected` (actual completion)
- **If loop already active**: Emit immediately (explicit loop start via `/ralph-loop:ralph-loop`)
```typescript
// From claudeman/src/ralph-tracker.ts
// From codeman/src/ralph-tracker.ts
private handleCompletionPhrase(phrase: string): void {
const count = (this._completionPhraseCount.get(phrase) || 0) + 1;
this._completionPhraseCount.set(phrase, count);
@@ -631,9 +631,9 @@ Ask yourself:
---
## Claudeman Implementation
## Codeman Implementation
Claudeman implements Ralph Wiggum tracking via the `RalphTracker` class in `src/ralph-tracker.ts`.
Codeman implements Ralph Wiggum tracking via the `RalphTracker` class in `src/ralph-tracker.ts`.
### Auto-Detection Patterns
@@ -833,7 +833,7 @@ POST /api/sessions/:id/auto-clear
- [Claude Fast - Autonomous Agent Loops](https://claudefa.st/blog/guide/mechanics/autonomous-agent-loops)
- [DeepWiki - Ralph Loop](https://deepwiki.com/anthropics/claude-plugins-official/5.2.2-ralph-loop)
### Related Claudeman Files
### Related Codeman Files
- `src/ralph-tracker.ts` - Core detection engine
- `src/ralph-loop.ts` - Task orchestration
- `src/respawn-controller.ts` - Session cycling
@@ -843,4 +843,4 @@ POST /api/sessions/:id/auto-clear
---
*This documentation is maintained as part of the Claudeman project. For updates, see the main [CLAUDE.md](../CLAUDE.md).*
*This documentation is maintained as part of the Codeman project. For updates, see the main [CLAUDE.md](../CLAUDE.md).*
+1 -1
View File
@@ -4,7 +4,7 @@ Date: 2026-02-17
## Scope
Full audit of the Claudeman notification system covering:
Full audit of the Codeman notification system covering:
- Backend event pipeline (server.ts, hooks-config.ts, team-watcher.ts, subagent-watcher.ts)
- Frontend notification manager (app.js NotificationManager class, 4-layer architecture)
- Settings UI and persistence (localStorage + server backup)
+7 -7
View File
@@ -1,10 +1,10 @@
# Claudeman Notification System - Backend Research Report
# Codeman Notification System - Backend Research Report
Date: 2026-02-17
## Executive Summary
The Claudeman notification system is a **multi-layer, event-driven pipeline** that flows from backend event emitters, through SSE broadcasts, to a frontend `NotificationManager` class. The backend itself has no concept of "notifications" -- it broadcasts structured SSE events, and the frontend decides which events warrant user notification (browser notifications, audio alerts, tab title flashing, in-app notification drawer, tab alert badges).
The Codeman notification system is a **multi-layer, event-driven pipeline** that flows from backend event emitters, through SSE broadcasts, to a frontend `NotificationManager` class. The backend itself has no concept of "notifications" -- it broadcasts structured SSE events, and the frontend decides which events warrant user notification (browser notifications, audio alerts, tab title flashing, in-app notification drawer, tab alert badges).
The system handles ~25 distinct notification-triggering SSE events across 5 categories: hook events, session lifecycle, respawn state machine, Ralph Loop, and UI actions.
@@ -96,14 +96,14 @@ Terminal and output data use separate batching pipelines that bypass `broadcast(
### 2.1 Hook Configuration Generator (Lines 24-67)
The `generateHooksConfig()` function creates `.claude/settings.local.json` entries that make Claude Code POST to Claudeman when hooks fire:
The `generateHooksConfig()` function creates `.claude/settings.local.json` entries that make Claude Code POST to Codeman when hooks fire:
```typescript
const curlCmd = (event: HookEventType) =>
`HOOK_DATA=$(cat 2>/dev/null || echo '{}'); ` +
`curl -s -X POST "$CLAUDEMAN_API_URL/api/hook-event" ` +
`curl -s -X POST "$CODEMAN_API_URL/api/hook-event" ` +
`-H 'Content-Type: application/json' ` +
`-d "{\\"event\\":\\"${event}\\",\\"sessionId\\":\\"$CLAUDEMAN_SESSION_ID\\",\\"data\\":$HOOK_DATA}" ` +
`-d "{\\"event\\":\\"${event}\\",\\"sessionId\\":\\"$CODEMAN_SESSION_ID\\",\\"data\\":$HOOK_DATA}" ` +
`2>/dev/null || true`;
```
@@ -143,8 +143,8 @@ The `sanitizeHookData()` function limits what gets broadcast:
### 2.4 Environment Variables (Lines 70-101)
Two env vars are set per case directory via `updateCaseEnvVars()`:
- `CLAUDEMAN_API_URL` -- server URL (e.g., `http://localhost:3000`)
- `CLAUDEMAN_SESSION_ID` -- session identifier
- `CODEMAN_API_URL` -- server URL (e.g., `http://localhost:3000`)
- `CODEMAN_SESSION_ID` -- session identifier
These are resolved at runtime by the shell, so the hook config is static per case.
+7 -7
View File
@@ -2,7 +2,7 @@
## Overview
Claudeman implements a **4-layer notification system** managed by the `NotificationManager` class (app.js lines 860-1265). The layers are:
Codeman implements a **4-layer notification system** managed by the `NotificationManager` class (app.js lines 860-1265). The layers are:
1. **In-app notification drawer** (Layer 1) - badge + list UI
2. **Document title flashing** (Layer 2) - tab title blinks when hidden
@@ -32,19 +32,19 @@ updateTabTitle() {
this.titleFlashInterval = setInterval(() => {
this.titleFlashState = !this.titleFlashState;
document.title = this.titleFlashState
? `\u26A0\uFE0F (${this.unreadCount}) Claudeman`
? `\u26A0\uFE0F (${this.unreadCount}) Codeman`
: this.originalTitle;
}, TITLE_FLASH_INTERVAL_MS);
// Set immediately
document.title = `\u26A0\uFE0F (${this.unreadCount}) Claudeman`;
document.title = `\u26A0\uFE0F (${this.unreadCount}) Codeman`;
}
}
}
```
The title alternates between:
- Warning emoji + unread count: `"(3) Claudeman"`
- Original title: `"Claudeman"`
- Warning emoji + unread count: `"(3) Codeman"`
- Original title: `"Codeman"`
### What Triggers It
Title flashing starts when `notify()` is called **while the tab is not visible** (`!this.isTabVisible`). The check is at lines 1026-1028:
@@ -126,7 +126,7 @@ It shows a lightning bolt icon on a dark background. The favicon is **static** a
Browser notifications reference `/favicon.ico` as their icon (line 1130):
```js
const notif = new Notification(`Claudeman: ${title}`, {
const notif = new Notification(`Codeman: ${title}`, {
body,
tag,
icon: '/favicon.ico',
@@ -306,7 +306,7 @@ Key detail: Title flash always stops when tab becomes visible, but **unread coun
## 5. Focus/Blur Handling
### No window focus/blur listeners
Claudeman does **not** use `window.addEventListener('focus')` or `window.addEventListener('blur')`. It relies solely on the Page Visibility API (`visibilitychange` + `pageshow`).
Codeman does **not** use `window.addEventListener('focus')` or `window.addEventListener('blur')`. It relies solely on the Page Visibility API (`visibilitychange` + `pageshow`).
This is the correct modern approach. The `focus`/`blur` events are unreliable (fire for devtools, iframe changes, etc.) while `visibilitychange` accurately reflects whether the user can see the tab.
+13 -13
View File
@@ -1,11 +1,11 @@
# Claudeman Frontend Notification System -- Detailed Report
# Codeman Frontend Notification System -- Detailed Report
> Generated: 2026-02-17
> Source files analyzed:
> - `/home/arkon/default/claudeman/src/web/public/app.js` (main frontend, ~15k lines)
> - `/home/arkon/default/claudeman/src/web/public/index.html`
> - `/home/arkon/default/claudeman/src/web/public/styles.css`
> - `/home/arkon/default/claudeman/src/web/public/mobile.css`
> - `/home/arkon/default/codeman/src/web/public/app.js` (main frontend, ~15k lines)
> - `/home/arkon/default/codeman/src/web/public/index.html`
> - `/home/arkon/default/codeman/src/web/public/styles.css`
> - `/home/arkon/default/codeman/src/web/public/mobile.css`
---
@@ -87,8 +87,8 @@ const defaults = {
#### Storage Keys (lines 952-956)
Device-specific localStorage keys prevent mobile settings from overriding desktop settings:
- Desktop: `claudeman-notification-prefs`
- Mobile: `claudeman-notification-prefs-mobile`
- Desktop: `codeman-notification-prefs`
- Mobile: `codeman-notification-prefs-mobile`
#### Version Migrations (lines 928-940)
@@ -260,8 +260,8 @@ Mobile override: full-width with safe area padding (mobile.css lines 1049-1058).
When the tab is not visible and there are unread notifications:
1. `setInterval` at 1500ms toggles between:
- Warning emoji + unread count: `"(3) Claudeman"`
- Original title: `"Claudeman"`
- Warning emoji + unread count: `"(3) Codeman"`
- Original title: `"Codeman"`
2. Set immediately on first notification (no wait for first interval tick)
### Stopping
@@ -311,7 +311,7 @@ sendBrowserNotif() called
### Notification Object (lines 1127-1143)
```js
new Notification(`Claudeman: ${title}`, {
new Notification(`Codeman: ${title}`, {
body,
tag, // Groups same-tag notifications (replaces previous with same tag)
icon: '/favicon.ico',
@@ -341,10 +341,10 @@ In settings (index.html line 993), a `<span class="settings-status" id="notifPer
The settings UI shows a hint (index.html line 996):
```
For remote access, HTTPS is required. Start with: claudeman web --https
For remote access, HTTPS is required. Start with: codeman web --https
```
Browser Notification API requires a secure context (HTTPS or localhost). This hint warns users who access Claudeman remotely over HTTP.
Browser Notification API requires a secure context (HTTPS or localhost). This hint warns users who access Codeman remotely over HTTP.
---
@@ -579,7 +579,7 @@ On mobile devices:
### Mobile Storage Key (lines 952-956)
Mobile uses a separate localStorage key (`claudeman-notification-prefs-mobile`) so that enabling notifications on desktop does not accidentally enable them on a mobile device viewing the same Claudeman instance.
Mobile uses a separate localStorage key (`codeman-notification-prefs-mobile`) so that enabling notifications on desktop does not accidentally enable them on a mobile device viewing the same Codeman instance.
### Mobile Drawer Styling (mobile.css lines 1049-1058)
+10 -10
View File
@@ -17,7 +17,7 @@ The App Settings modal has tabs: Display, Claude CLI, Models, Paths, **Notificat
The Browser row includes an "Ask" button that calls `requestPermission()` and a status badge showing the current `Notification.permission` state.
There is also a hint: _"For remote access, HTTPS is required. Start with: `claudeman web --https`"_
There is also a hint: _"For remote access, HTTPS is required. Start with: `codeman web --https`"_
#### Alerts Section
| Setting | Element ID | Type | Default |
@@ -54,16 +54,16 @@ Notification preferences are stored in **two places simultaneously**:
#### Layer 1: localStorage (primary, device-specific)
- **Storage key**: `claudeman-notification-prefs` (desktop) or `claudeman-notification-prefs-mobile` (mobile)
- **Storage key**: `codeman-notification-prefs` (desktop) or `codeman-notification-prefs-mobile` (mobile)
- Determined by `NotificationManager.getStorageKey()` at line 953, which calls `MobileDetection.getDeviceType()`
- Device type is based on `window.innerWidth`: `<430` = mobile, `430-768` = tablet, `>=768` = desktop
- Read in `loadPreferences()` (line 896), written in `savePreferences()` (line 958)
#### Layer 2: Server-side (`~/.claudeman/settings.json`)
#### Layer 2: Server-side (`~/.codeman/settings.json`)
- On save, notification prefs are bundled with app settings: `{ ...settings, notificationPreferences: notifPrefsToSave }` (line 9475)
- Sent via `PUT /api/settings` to the Fastify server
- Server does a shallow merge: `const merged = { ...existing, ...settings }` then writes to `~/.claudeman/settings.json` (line 3098 of server.ts)
- Server does a shallow merge: `const merged = { ...existing, ...settings }` then writes to `~/.codeman/settings.json` (line 3098 of server.ts)
- The `notificationPreferences` key sits at the top level of the settings JSON alongside app settings
#### Load priority
@@ -109,8 +109,8 @@ On startup, `loadAppSettingsFromServer()` (line 9787) fetches from server and:
### App settings (separate from notification prefs)
App settings use a different device-specific localStorage key:
- Desktop: `claudeman-app-settings`
- Mobile: `claudeman-app-settings-mobile`
- Desktop: `codeman-app-settings`
- Mobile: `codeman-app-settings-mobile`
- Determined by `getSettingsStorageKey()` at line 9562
@@ -213,8 +213,8 @@ const AUTO_CLOSE_NOTIFICATION_MS = 8000; // Auto-close browser notifications
### Separate storage keys - YES
Desktop and mobile use completely separate localStorage keys:
- **Notification prefs**: `claudeman-notification-prefs` vs `claudeman-notification-prefs-mobile`
- **App settings**: `claudeman-app-settings` vs `claudeman-app-settings-mobile`
- **Notification prefs**: `codeman-notification-prefs` vs `codeman-notification-prefs-mobile`
- **App settings**: `codeman-app-settings` vs `codeman-app-settings-mobile`
### Different defaults - YES
@@ -303,7 +303,7 @@ The settings UI shows the current permission state via a status badge:
### HTTPS requirement
Browser notifications require HTTPS for remote access. The settings UI includes a hint: _"For remote access, HTTPS is required. Start with: `claudeman web --https`"_. On localhost, HTTP works fine.
Browser notifications require HTTPS for remote access. The settings UI includes a hint: _"For remote access, HTTPS is required. Start with: `codeman web --https`"_. On localhost, HTTP works fine.
## 7. Audio Setting
@@ -354,7 +354,7 @@ Yes, given the prerequisites above are met. However, due to the category key mis
Notification preferences are **strictly global**. There is no per-session notification configuration.
- The `NotificationManager` is a singleton on the `ClaudemanApp` instance (line 1404)
- The `NotificationManager` is a singleton on the `CodemanApp` instance (line 1404)
- Preferences are loaded once from localStorage (line 878)
- All sessions share the same notification rules
+1 -1
View File
@@ -1,6 +1,6 @@
# Terminal Anti-Flicker System
Claude Code uses [Ink](https://github.com/vadimdemedes/ink) (React for terminals), which redraws the entire screen on every state change. Without special handling, users see constant flickering. Claudeman implements a 6-layer anti-flicker pipeline.
Claude Code uses [Ink](https://github.com/vadimdemedes/ink) (React for terminals), which redraws the entire screen on every state change. Without special handling, users see constant flickering. Codeman implements a 6-layer anti-flicker pipeline.
## Pipeline Overview
+8 -8
View File
@@ -1,4 +1,4 @@
# Claudeman TypeScript Improvement Suggestions
# Codeman TypeScript Improvement Suggestions
**Generated**: February 2026
**Based on**: Research into TypeScript best practices (2024-2025) and codebase analysis
@@ -229,33 +229,33 @@ Replace string-based errors with typed errors:
```typescript
// src/errors.ts
export class ClaudemanError extends Error {
export class CodemanError extends Error {
constructor(
message: string,
public code: string,
public context?: Record<string, unknown>
) {
super(message);
Object.setPrototypeOf(this, ClaudemanError.prototype);
this.name = 'ClaudemanError';
Object.setPrototypeOf(this, CodemanError.prototype);
this.name = 'CodemanError';
}
}
export class SessionError extends ClaudemanError {
export class SessionError extends CodemanError {
constructor(message: string, code: string, public sessionId: string) {
super(message, code, { sessionId });
this.name = 'SessionError';
}
}
export class ValidationError extends ClaudemanError {
export class ValidationError extends CodemanError {
constructor(message: string, public field: string, public value: unknown) {
super(message, 'VALIDATION_ERROR', { field, value });
this.name = 'ValidationError';
}
}
export class ScreenError extends ClaudemanError {
export class ScreenError extends CodemanError {
constructor(message: string, public screenName: string, public operation: string) {
super(message, 'SCREEN_ERROR', { screenName, operation });
this.name = 'ScreenError';
@@ -324,7 +324,7 @@ Enforce ID formats at compile time:
```typescript
type CycleIdFormat = `${string}:cycle-${number}`;
type ScreenSessionName = `claudeman-${string}`;
type ScreenSessionName = `codeman-${string}`;
interface RespawnCycleMetrics {
cycleId: CycleIdFormat; // Enforces format at compile time
+1 -1
View File
@@ -56,7 +56,7 @@ The blue-tinted voice button in the accessory bar is distinctive but subtle. Whe
Confirmed by research: Web Speech API is the right choice.
- **Free, fast (150-300ms interim), trivial complexity**
- Chrome + Safari = ~70% of users, ~95% of Claudeman's target audience (devs on Chrome)
- Chrome + Safari = ~70% of users, ~95% of Codeman's target audience (devs on Chrome)
- Works on localhost without HTTPS
- Accuracy is adequate for English command dictation
- Deepgram streaming (Phase 2 optional) only if accuracy complaints arise