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
+30 -30
View File
@@ -9,13 +9,13 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
| Dev server | `npx tsx src/index.ts web` | | Dev server | `npx tsx src/index.ts web` |
| Type check | `tsc --noEmit` | | Type check | `tsc --noEmit` |
| Single test | `npx vitest run test/<file>.test.ts` | | Single test | `npx vitest run test/<file>.test.ts` |
| Production | `npm run build && systemctl --user restart claudeman-web` | | Production | `npm run build && systemctl --user restart codeman-web` |
## CRITICAL: Session Safety ## CRITICAL: Session Safety
**You may be running inside a Claudeman-managed tmux session.** Before killing ANY tmux or Claude process: **You may be running inside a Codeman-managed tmux session.** Before killing ANY tmux or Claude process:
1. Check: `echo $CLAUDEMAN_TMUX` - if `1`, you're in a managed session 1. Check: `echo $CODEMAN_TMUX` - if `1`, you're in a managed session
2. **NEVER** run `tmux kill-session`, `pkill tmux`, or `pkill claude` without confirming 2. **NEVER** run `tmux kill-session`, `pkill tmux`, or `pkill claude` without confirming
3. Use the web UI or `./scripts/tmux-manager.sh` instead of direct kill commands 3. Use the web UI or `./scripts/tmux-manager.sh` instead of direct kill commands
@@ -33,13 +33,13 @@ The production server caches static files for 1 hour (`maxAge: '1h'` in `server.
When user says "COM": When user says "COM":
1. Increment version in BOTH `package.json` AND `CLAUDE.md` (verify they match with `grep version package.json && grep Version CLAUDE.md`) 1. Increment version in BOTH `package.json` AND `CLAUDE.md` (verify they match with `grep version package.json && grep Version CLAUDE.md`)
2. Run: `git add -A && git commit -m "chore: bump version to X.XXXX" && git push && npm run build && systemctl --user restart claudeman-web` 2. Run: `git add -A && git commit -m "chore: bump version to X.XXXX" && git push && npm run build && systemctl --user restart codeman-web`
**Version**: 0.1650 (must match `package.json`) **Version**: 0.1650 (must match `package.json`)
## Project Overview ## Project Overview
Claudeman is a Claude Code session manager with web interface and autonomous Ralph Loop. Spawns Claude CLI via PTY, streams via SSE, supports respawn cycling for 24+ hour autonomous runs. Codeman is a Claude Code session manager with web interface and autonomous Ralph Loop. Spawns Claude CLI via PTY, streams via SSE, supports respawn cycling for 24+ hour autonomous runs.
**Tech Stack**: TypeScript (ES2022/NodeNext, strict mode), Node.js, Fastify, node-pty, xterm.js **Tech Stack**: TypeScript (ES2022/NodeNext, strict mode), Node.js, Fastify, node-pty, xterm.js
@@ -63,24 +63,24 @@ npx tsx src/index.ts web --https # With TLS (only needed for remote access)
npm run typecheck # Type check npm run typecheck # Type check
tsc --noEmit --watch # Continuous type checking tsc --noEmit --watch # Continuous type checking
# Testing (NEVER run full suite from inside Claudeman — kills tmux sessions) # Testing (NEVER run full suite from inside Codeman — kills tmux sessions)
# npx vitest run # ALL tests — DANGEROUS inside Claudeman # npx vitest run # ALL tests — DANGEROUS inside Codeman
npx vitest run test/<file>.test.ts # Single file (SAFE) npx vitest run test/<file>.test.ts # Single file (SAFE)
npx vitest run -t "pattern" # Tests matching name npx vitest run -t "pattern" # Tests matching name
npm run test:coverage # With coverage report npm run test:coverage # With coverage report
# Production # Production
npm run build npm run build
systemctl --user restart claudeman-web systemctl --user restart codeman-web
journalctl --user -u claudeman-web -f journalctl --user -u codeman-web -f
``` ```
## Common Gotchas ## Common Gotchas
- **`npm run dev` is NOT the web server** — it shows CLI help. Use `npx tsx src/index.ts web` - **`npm run dev` is NOT the web server** — it shows CLI help. Use `npx tsx src/index.ts web`
- **Single-line prompts only** — `writeViaMux()` sends text and Enter separately; multi-line breaks Ink - **Single-line prompts only** — `writeViaMux()` sends text and Enter separately; multi-line breaks Ink
- **Don't kill tmux sessions blindly** — Check `$CLAUDEMAN_TMUX` first; you might be inside one - **Don't kill tmux sessions blindly** — Check `$CODEMAN_TMUX` first; you might be inside one
- **Never run full test suite** — `npx vitest run` spawns/kills tmux sessions and will crash your Claudeman session. Run individual test files only. - **Never run full test suite** — `npx vitest run` spawns/kills tmux sessions and will crash your Codeman session. Run individual test files only.
- **Global regex `lastIndex` sharing** — `ANSI_ESCAPE_PATTERN_FULL/SIMPLE` have `g` flag; use `createAnsiPatternFull/Simple()` factory functions for fresh instances in loops - **Global regex `lastIndex` sharing** — `ANSI_ESCAPE_PATTERN_FULL/SIMPLE` have `g` flag; use `createAnsiPatternFull/Simple()` factory functions for fresh instances in loops
- **DEC 2026 sync blocks** — Never discard incomplete sync blocks (START without END); buffer up to 50ms then flush. See `app.js:extractSyncSegments()` - **DEC 2026 sync blocks** — Never discard incomplete sync blocks (START without END); buffer up to 50ms then flush. See `app.js:extractSyncSegments()`
- **Terminal writes during buffer load** — Live SSE writes are queued while `_isLoadingBuffer` is true to prevent interleaving with historical data - **Terminal writes during buffer load** — Live SSE writes are queued while `_isLoadingBuffer` is true to prevent interleaving with historical data
@@ -103,7 +103,7 @@ journalctl --user -u claudeman-web -f
| `src/mux-factory.ts` | Create tmux multiplexer instance | | `src/mux-factory.ts` | Create tmux multiplexer instance |
| `src/tmux-manager.ts` | tmux session management | | `src/tmux-manager.ts` | tmux session management |
| `src/session-manager.ts` | Session lifecycle, cleanup | | `src/session-manager.ts` | Session lifecycle, cleanup |
| `src/state-store.ts` | State persistence to `~/.claudeman/state.json` | | `src/state-store.ts` | State persistence to `~/.codeman/state.json` |
| `src/respawn-controller.ts` | State machine for autonomous cycling | | `src/respawn-controller.ts` | State machine for autonomous cycling |
| `src/ralph-tracker.ts` | Detects `<promise>PHRASE</promise>`, todos | | `src/ralph-tracker.ts` | Detects `<promise>PHRASE</promise>`, todos |
| `src/ralph-loop.ts` | Autonomous task execution loop (polls queue, assigns tasks) | | `src/ralph-loop.ts` | Autonomous task execution loop (polls queue, assigns tasks) |
@@ -120,7 +120,7 @@ journalctl --user -u claudeman-web -f
| `src/bash-tool-parser.ts` | Parses Claude's bash tool invocations from output | | `src/bash-tool-parser.ts` | Parses Claude's bash tool invocations from output |
| `src/transcript-watcher.ts` | Watches Claude's transcript files for changes | | `src/transcript-watcher.ts` | Watches Claude's transcript files for changes |
| `src/hooks-config.ts` | Manages `.claude/settings.local.json` hook configuration | | `src/hooks-config.ts` | Manages `.claude/settings.local.json` hook configuration |
| `src/session-lifecycle-log.ts` | Append-only JSONL audit log at `~/.claudeman/session-lifecycle.jsonl` | | `src/session-lifecycle-log.ts` | Append-only JSONL audit log at `~/.codeman/session-lifecycle.jsonl` |
| `src/image-watcher.ts` | Watches for image file creation (screenshots, etc.) | | `src/image-watcher.ts` | Watches for image file creation (screenshots, etc.) |
| `src/file-stream-manager.ts` | Manages `tail -f` processes for live log viewing | | `src/file-stream-manager.ts` | Manages `tail -f` processes for live log viewing |
| `src/plan-orchestrator.ts` | Multi-agent plan generation with research and planning phases | | `src/plan-orchestrator.ts` | Multi-agent plan generation with research and planning phases |
@@ -169,7 +169,7 @@ journalctl --user -u claudeman-web -f
1. Session spawns `claude --dangerously-skip-permissions` via node-pty 1. Session spawns `claude --dangerously-skip-permissions` via node-pty
2. PTY output buffered, ANSI stripped, parsed for JSON messages 2. PTY output buffered, ANSI stripped, parsed for JSON messages
3. WebServer broadcasts to SSE clients at `/api/events` 3. WebServer broadcasts to SSE clients at `/api/events`
4. State persists to `~/.claudeman/state.json` via StateStore 4. State persists to `~/.codeman/state.json` via StateStore
### Key Patterns ### Key Patterns
@@ -214,11 +214,11 @@ The frontend is a single 16K-line vanilla JS file with these key systems:
### Security ### Security
- **HTTP Basic Auth**: Optional via `CLAUDEMAN_USERNAME`/`CLAUDEMAN_PASSWORD` env vars - **HTTP Basic Auth**: Optional via `CODEMAN_USERNAME`/`CODEMAN_PASSWORD` env vars
- **CORS**: Restricted to localhost only - **CORS**: Restricted to localhost only
- **Security headers**: X-Content-Type-Options, X-Frame-Options, CSP; HSTS if HTTPS - **Security headers**: X-Content-Type-Options, X-Frame-Options, CSP; HSTS if HTTPS
- **Path validation** (`schemas.ts`): Strict allowlist regex, no shell metacharacters, no traversal, must be absolute - **Path validation** (`schemas.ts`): Strict allowlist regex, no shell metacharacters, no traversal, must be absolute
- **Env var allowlist**: Only `CLAUDE_CODE_*` prefixes allowed; blocks `PATH`, `LD_PRELOAD`, `NODE_OPTIONS`, `CLAUDEMAN_*` keys - **Env var allowlist**: Only `CLAUDE_CODE_*` prefixes allowed; blocks `PATH`, `LD_PRELOAD`, `NODE_OPTIONS`, `CODEMAN_*` keys
- **File streaming TOCTOU protection**: `FileStreamManager` calls `realpathSync()` twice (at validation and before spawn) to catch symlink swaps - **File streaming TOCTOU protection**: `FileStreamManager` calls `realpathSync()` twice (at validation and before spawn) to catch symlink swaps
### SSE Event Categories ### SSE Event Categories
@@ -269,9 +269,9 @@ The frontend is a single 16K-line vanilla JS file with these key systems:
| File | Purpose | | File | Purpose |
|------|---------| |------|---------|
| `~/.claudeman/state.json` | Sessions, settings, tokens, respawn config | | `~/.codeman/state.json` | Sessions, settings, tokens, respawn config |
| `~/.claudeman/mux-sessions.json` | Tmux session metadata for recovery | | `~/.codeman/mux-sessions.json` | Tmux session metadata for recovery |
| `~/.claudeman/settings.json` | User preferences | | `~/.codeman/settings.json` | User preferences |
## Default Settings ## Default Settings
@@ -281,7 +281,7 @@ UI defaults are set in `src/web/public/app.js` using `??` fallbacks. To change d
## Testing ## Testing
**CRITICAL: You are running inside a Claudeman-managed tmux session.** Never run `npx vitest run` (full suite) — it spawns/kills tmux sessions and will crash your own session. Instead: **CRITICAL: You are running inside a Codeman-managed tmux session.** Never run `npx vitest run` (full suite) — it spawns/kills tmux sessions and will crash your own session. Instead:
```bash ```bash
# Safe: run individual test files # Safe: run individual test files
@@ -290,7 +290,7 @@ npx vitest run test/<specific-file>.test.ts
# Safe: run tests matching a pattern # Safe: run tests matching a pattern
npx vitest run -t "pattern" npx vitest run -t "pattern"
# DANGEROUS from inside Claudeman — will kill your tmux session: # DANGEROUS from inside Codeman — will kill your tmux session:
# npx vitest run ← DON'T DO THIS # npx vitest run ← DON'T DO THIS
``` ```
@@ -306,10 +306,10 @@ npx vitest run -t "pattern"
## Screenshots ("sc") ## Screenshots ("sc")
When the user says "check the sc", "screenshot", or "sc", they mean uploaded screenshots from their mobile device. Screenshots are saved to `~/.claudeman/screenshots/` and uploaded via `/upload.html` on the Claudeman web UI. To view them, use the Read tool on the image files: When the user says "check the sc", "screenshot", or "sc", they mean uploaded screenshots from their mobile device. Screenshots are saved to `~/.codeman/screenshots/` and uploaded via `/upload.html` on the Codeman web UI. To view them, use the Read tool on the image files:
```bash ```bash
ls ~/.claudeman/screenshots/ # List uploaded screenshots ls ~/.codeman/screenshots/ # List uploaded screenshots
# Then use Read tool on individual files — Claude Code can view images natively # Then use Read tool on individual files — Claude Code can view images natively
``` ```
@@ -322,7 +322,7 @@ tmux list-sessions # List tmux sessions
tmux attach-session -t <name> # Attach (Ctrl+B D to detach) tmux attach-session -t <name> # Attach (Ctrl+B D to detach)
curl localhost:3000/api/sessions # Check sessions curl localhost:3000/api/sessions # Check sessions
curl localhost:3000/api/status | jq # Full app state curl localhost:3000/api/status | jq # Full app state
cat ~/.claudeman/state.json | jq # View persisted state cat ~/.codeman/state.json | jq # View persisted state
curl localhost:3000/api/subagents # List background agents curl localhost:3000/api/subagents # List background agents
curl localhost:3000/api/sessions/:id/run-summary | jq # Session timeline curl localhost:3000/api/sessions/:id/run-summary | jq # Session timeline
``` ```
@@ -337,7 +337,7 @@ curl localhost:3000/api/sessions/:id/run-summary | jq # Session timeline
| Respawn not triggering | Session settings → Respawn enabled? | Enable respawn, check idle timeout config | | Respawn not triggering | Session settings → Respawn enabled? | Enable respawn, check idle timeout config |
| Terminal blank on tab switch | Network tab for `/api/sessions/:id/buffer` | Check session exists, restart server | | Terminal blank on tab switch | Network tab for `/api/sessions/:id/buffer` | Check session exists, restart server |
| Tests failing on session limits | `tmux list-sessions \| wc -l` | Clean up: `tmux list-sessions \| grep test \| awk -F: '{print $1}' \| xargs -I{} tmux kill-session -t {}` | | Tests failing on session limits | `tmux list-sessions \| wc -l` | Clean up: `tmux list-sessions \| grep test \| awk -F: '{print $1}' \| xargs -I{} tmux kill-session -t {}` |
| State not persisting | `cat ~/.claudeman/state.json` | Check file permissions, disk space | | State not persisting | `cat ~/.codeman/state.json` | Check file permissions, disk space |
## Performance Constraints ## Performance Constraints
@@ -352,7 +352,7 @@ The app must stay fast with 20 sessions and 50 agent windows:
## Terminal Anti-Flicker System ## Terminal Anti-Flicker System
Claude Code uses Ink (React for terminals), which redraws the screen on every state change. Claudeman implements a 6-layer anti-flicker pipeline for smooth 60fps output: Claude Code uses Ink (React for terminals), which redraws the screen on every state change. Codeman implements a 6-layer anti-flicker pipeline for smooth 60fps output:
``` ```
PTY Output → Server Batching (16-50ms) → DEC 2026 Wrap → SSE → Client rAF → xterm.js PTY Output → Server Batching (16-50ms) → DEC 2026 Wrap → SSE → Client rAF → xterm.js
@@ -395,14 +395,14 @@ Use `LRUMap` for bounded caches with eviction, `StaleExpirationMap` for TTL-base
| **Terminal anti-flicker** | `docs/terminal-anti-flicker.md` | | **Terminal anti-flicker** | `docs/terminal-anti-flicker.md` |
| **API routes** | `src/web/server.ts:buildServer()` or README.md (full endpoint tables) | | **API routes** | `src/web/server.ts:buildServer()` or README.md (full endpoint tables) |
| **SSE events** | Search `broadcast(` in `server.ts` | | **SSE events** | Search `broadcast(` in `server.ts` |
| **CLI commands** | `claudeman --help` | | **CLI commands** | `codeman --help` |
| **Frontend patterns** | `src/web/public/app.js` (subagent windows, notifications) | | **Frontend patterns** | `src/web/public/app.js` (subagent windows, notifications) |
| **Session statuses** | `SessionStatus` type in `src/types.ts` | | **Session statuses** | `SessionStatus` type in `src/types.ts` |
| **Error codes** | `createErrorResponse()` in `src/types.ts` | | **Error codes** | `createErrorResponse()` in `src/types.ts` |
| **Test utilities** | `test/respawn-test-utils.ts` | | **Test utilities** | `test/respawn-test-utils.ts` |
| **Memory leak patterns** | `test/memory-leak-prevention.test.ts` | | **Memory leak patterns** | `test/memory-leak-prevention.test.ts` |
| **Keyboard shortcuts** | README.md or App Settings in web UI | | **Keyboard shortcuts** | README.md or App Settings in web UI |
| **Mobile/SSH access** | README.md (Claudeman Sessions / `sc` command) | | **Mobile/SSH access** | README.md (Codeman Sessions / `sc` command) |
| **Plan orchestrator** | `src/plan-orchestrator.ts` file header | | **Plan orchestrator** | `src/plan-orchestrator.ts` file header |
| **Agent prompts** | `src/prompts/` directory | | **Agent prompts** | `src/prompts/` directory |
| **Agent Teams (experimental)** | `agent-teams/README.md`, `agent-teams/design.md` | | **Agent Teams (experimental)** | `agent-teams/README.md`, `agent-teams/design.md` |
@@ -444,7 +444,7 @@ Use `LRUMap` for bounded caches with eviction, `StaleExpirationMap` for TTL-base
| `scripts/test-patterns.mjs` | Test file path link detection regex patterns | | `scripts/test-patterns.mjs` | Test file path link detection regex patterns |
| `scripts/watch-subagents.ts` | Real-time subagent transcript watcher (list, follow by session/agent ID) | | `scripts/watch-subagents.ts` | Real-time subagent transcript watcher (list, follow by session/agent ID) |
| `scripts/capture-readme-screenshots.mjs` | Capture screenshots for README | | `scripts/capture-readme-screenshots.mjs` | Capture screenshots for README |
| `scripts/claudeman-web.service` | systemd service file for production deployment | | `scripts/codeman-web.service` | systemd service file for production deployment |
## Memory Leak Prevention ## Memory Leak Prevention
@@ -464,7 +464,7 @@ Run `npx vitest run test/memory-leak-prevention.test.ts` to verify patterns.
## Common Workflows ## Common Workflows
**Investigating a bug**: Start dev server (`npx tsx src/index.ts web`), reproduce in browser, check terminal output and `~/.claudeman/state.json` for clues. **Investigating a bug**: Start dev server (`npx tsx src/index.ts web`), reproduce in browser, check terminal output and `~/.codeman/state.json` for clues.
**Adding a new API endpoint**: Define types in `types.ts`, add route in `server.ts:buildServer()`, broadcast SSE events if needed, handle in `app.js:handleSSEEvent()`. **Adding a new API endpoint**: Define types in `types.ts`, add route in `server.ts:buildServer()`, broadcast SSE events if needed, handle in `app.js:handleSSEEvent()`.
+16 -16
View File
@@ -1,5 +1,5 @@
<p align="center"> <p align="center">
<img src="docs/images/claudeman-title.svg" alt="Claudeman" height="60"> <img src="docs/images/codeman-title.svg" alt="Codeman" height="60">
</p> </p>
<h2 align="center">The missing control plane for Claude Code</h2> <h2 align="center">The missing control plane for Claude Code</h2>
@@ -17,7 +17,7 @@
</p> </p>
<p align="center"> <p align="center">
<img src="docs/images/subagent-demo.gif" alt="Claudeman — parallel subagent visualization" width="900"> <img src="docs/images/subagent-demo.gif" alt="Codeman — parallel subagent visualization" width="900">
</p> </p>
--- ---
@@ -25,19 +25,19 @@
## Quick Start - Installation ## Quick Start - Installation
```bash ```bash
curl -fsSL https://raw.githubusercontent.com/Ark0N/claudeman/master/install.sh | bash curl -fsSL https://raw.githubusercontent.com/Ark0N/Codeman/master/install.sh | bash
``` ```
This installs Node.js, tmux, and Claude CLI if missing, clones Claudeman to `~/.claudeman/app`, builds it. After install: This installs Node.js, tmux, and Claude CLI if missing, clones Codeman to `~/.codeman/app`, builds it. After install:
```bash ```bash
claudeman web codeman web
# Open http://localhost:3000 — press Ctrl+Enter to start your first session # Open http://localhost:3000 — press Ctrl+Enter to start your first session
``` ```
**Update to latest version:** **Update to latest version:**
```bash ```bash
curl -fsSL https://raw.githubusercontent.com/Ark0N/claudeman/master/install.sh | bash -s update curl -fsSL https://raw.githubusercontent.com/Ark0N/Codeman/master/install.sh | bash -s update
``` ```
<details> <details>
@@ -45,12 +45,12 @@ curl -fsSL https://raw.githubusercontent.com/Ark0N/claudeman/master/install.sh |
**Linux (systemd):** **Linux (systemd):**
```bash ```bash
mkdir -p ~/.config/systemd/user && printf '[Unit]\nDescription=Claudeman Web Server\nAfter=network.target\n\n[Service]\nType=simple\nExecStart=%s %s/dist/index.js web\nRestart=always\nRestartSec=10\n\n[Install]\nWantedBy=default.target\n' "$(which node)" "$HOME/.claudeman/app" > ~/.config/systemd/user/claudeman-web.service && systemctl --user daemon-reload && systemctl --user enable --now claudeman-web && loginctl enable-linger $USER mkdir -p ~/.config/systemd/user && printf '[Unit]\nDescription=Codeman Web Server\nAfter=network.target\n\n[Service]\nType=simple\nExecStart=%s %s/dist/index.js web\nRestart=always\nRestartSec=10\n\n[Install]\nWantedBy=default.target\n' "$(which node)" "$HOME/.codeman/app" > ~/.config/systemd/user/codeman-web.service && systemctl --user daemon-reload && systemctl --user enable --now codeman-web && loginctl enable-linger $USER
``` ```
**macOS (launchd):** **macOS (launchd):**
```bash ```bash
mkdir -p ~/Library/LaunchAgents && printf '<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n<plist version="1.0"><dict><key>Label</key><string>com.claudeman.web</string><key>ProgramArguments</key><array><string>%s</string><string>%s/dist/index.js</string><string>web</string></array><key>RunAtLoad</key><true/><key>KeepAlive</key><true/><key>StandardOutPath</key><string>/tmp/claudeman.log</string><key>StandardErrorPath</key><string>/tmp/claudeman.log</string></dict></plist>\n' "$(which node)" "$HOME/.claudeman/app" > ~/Library/LaunchAgents/com.claudeman.web.plist && launchctl load ~/Library/LaunchAgents/com.claudeman.web.plist mkdir -p ~/Library/LaunchAgents && printf '<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n<plist version="1.0"><dict><key>Label</key><string>com.codeman.web</string><key>ProgramArguments</key><array><string>%s</string><string>%s/dist/index.js</string><string>web</string></array><key>RunAtLoad</key><true/><key>KeepAlive</key><true/><key>StandardOutPath</key><string>/tmp/codeman.log</string><key>StandardErrorPath</key><string>/tmp/codeman.log</string></dict></plist>\n' "$(which node)" "$HOME/.codeman/app" > ~/Library/LaunchAgents/com.codeman.web.plist && launchctl load ~/Library/LaunchAgents/com.codeman.web.plist
``` ```
</details> </details>
@@ -58,10 +58,10 @@ mkdir -p ~/Library/LaunchAgents && printf '<?xml version="1.0" encoding="UTF-8"?
<summary><strong>Windows (WSL)</strong></summary> <summary><strong>Windows (WSL)</strong></summary>
```powershell ```powershell
wsl bash -c "curl -fsSL https://raw.githubusercontent.com/Ark0N/claudeman/master/install.sh | bash" wsl bash -c "curl -fsSL https://raw.githubusercontent.com/Ark0N/Codeman/master/install.sh | bash"
``` ```
Claudeman 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 Claude CLI inside WSL: `curl -fsSL https://claude.ai/install.sh | bash`. After installing, `http://localhost:3000` is accessible from your Windows browser. 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 Claude CLI inside WSL: `curl -fsSL https://claude.ai/install.sh | bash`. After installing, `http://localhost:3000` is accessible from your Windows browser.
</details> </details>
--- ---
@@ -74,7 +74,7 @@ The most responsive Claude Code experience on any phone. Full xterm.js terminal
<tr> <tr>
<td rowspan="8" width="320"><img src="docs/screenshots/mobile-keyboard-open.png" alt="Mobile — keyboard open" width="300"></td> <td rowspan="8" width="320"><img src="docs/screenshots/mobile-keyboard-open.png" alt="Mobile — keyboard open" width="300"></td>
<th>Terminal Apps</th> <th>Terminal Apps</th>
<th>Claudeman Mobile</th> <th>Codeman Mobile</th>
</tr> </tr>
<tr><td>200-300ms input lag over remote</td><td><b>Local echo — instant feedback</b></td></tr> <tr><td>200-300ms input lag over remote</td><td><b>Local echo — instant feedback</b></td></tr>
<tr><td>Tiny text, no context</td><td>Full xterm.js terminal</td></tr> <tr><td>Tiny text, no context</td><td>Full xterm.js terminal</td></tr>
@@ -94,7 +94,7 @@ The most responsive Claude Code experience on any phone. Full xterm.js terminal
- **Native momentum scrolling** — `-webkit-overflow-scrolling: touch` for buttery scroll - **Native momentum scrolling** — `-webkit-overflow-scrolling: touch` for buttery scroll
```bash ```bash
claudeman web --https codeman web --https
# Open on your phone: https://<your-ip>:3000 # Open on your phone: https://<your-ip>:3000
``` ```
@@ -104,7 +104,7 @@ claudeman web --https
## Live Agent Visualization ## Live Agent Visualization
Watch Claude's background agents work in real-time. Claudeman monitors every `Task` tool invocation and displays each agent in a draggable floating window with animated Matrix-style connection lines back to the parent session. Watch Claude's background agents work in real-time. Codeman monitors every `Task` tool invocation and displays each agent in a draggable floating window with animated Matrix-style connection lines back to the parent session.
<p align="center"> <p align="center">
<img src="docs/images/subagent-spawn.png" alt="Subagent Visualization" width="900"> <img src="docs/images/subagent-spawn.png" alt="Subagent Visualization" width="900">
@@ -120,7 +120,7 @@ Watch Claude's background agents work in real-time. Claudeman monitors every `Ta
## Zero-Lag Input Overlay ## Zero-Lag Input Overlay
When accessing Claude Code remotely (VPN, Tailscale, SSH tunnel), every keystroke normally takes 200-300ms to round-trip. Claudeman implements a **Mosh-inspired local echo system** that makes typing feel instant regardless of latency. When accessing Claude Code remotely (VPN, Tailscale, SSH tunnel), every keystroke normally takes 200-300ms to round-trip. Codeman implements a **Mosh-inspired local echo system** that makes typing feel instant regardless of latency.
A pixel-perfect DOM overlay inside xterm.js renders keystrokes at 0ms. Background forwarding silently sends every character to the PTY in 50ms debounced batches, so Tab completion, `Ctrl+R` history search, and all shell features work normally. When the server echo arrives 200-300ms later, the overlay seamlessly disappears and the real terminal text takes over — the transition is invisible. A pixel-perfect DOM overlay inside xterm.js renders keystrokes at 0ms. Background forwarding silently sends every character to the PTY in 50ms debounced batches, so Tab completion, `Ctrl+R` history search, and all shell features work normally. When the server echo arrives 200-300ms later, the overlay seamlessly disappears and the real terminal text takes over — the transition is invisible.
@@ -186,7 +186,7 @@ Click the chart icon on any session tab to see a timeline of everything that hap
### Zero-Flicker Terminal ### Zero-Flicker Terminal
Claude Code uses Ink (React for terminals) which redraws the entire screen on every state change. Claudeman implements a 6-layer anti-flicker pipeline for smooth 60fps output across all sessions: Claude Code uses Ink (React for terminals) which redraws the entire screen on every state change. Codeman implements a 6-layer anti-flicker pipeline for smooth 60fps output across all sessions:
``` ```
PTY Output → 16ms Server Batch → DEC 2026 Wrap → SSE → Client rAF → xterm.js (60fps) PTY Output → 16ms Server Batch → DEC 2026 Wrap → SSE → Client rAF → xterm.js (60fps)
@@ -268,7 +268,7 @@ Single-digit selection (1-9), color-coded status, token counts, auto-refresh. De
```mermaid ```mermaid
flowchart TB flowchart TB
subgraph Claudeman["CLAUDEMAN"] subgraph Codeman["CODEMAN"]
subgraph Frontend["Frontend Layer"] subgraph Frontend["Frontend Layer"]
UI["Web UI<br/><small>xterm.js + Agent Windows</small>"] UI["Web UI<br/><small>xterm.js + Agent Windows</small>"]
API["REST API<br/><small>Fastify</small>"] API["REST API<br/><small>Fastify</small>"]
+5 -5
View File
@@ -68,7 +68,7 @@ Note: Teammate transcripts appear in the **standard subagent directory** under t
} }
``` ```
Key fields: `agentId` format is `{name}@{teamName}`, `leadSessionId` links to Claudeman session, `backendType` indicates display mode, `color` for UI theming. Key fields: `agentId` format is `{name}@{teamName}`, `leadSessionId` links to Codeman session, `backendType` indicates display mode, `color` for UI theming.
### Task file format (verified) ### Task file format (verified)
@@ -122,7 +122,7 @@ Message types observed:
**Teammates are IN-PROCESS THREADS, not separate OS processes.** **Teammates are IN-PROCESS THREADS, not separate OS processes.**
In `in-process` mode (the default), all teammates run as threads within the single `claude` process. Only 1 claude process exists per Claudeman session, regardless of team size. In `in-process` mode (the default), all teammates run as threads within the single `claude` process. Only 1 claude process exists per Codeman session, regardless of team size.
This means: This means:
- No separate PIDs to track per teammate - No separate PIDs to track per teammate
@@ -138,7 +138,7 @@ This means:
| **tmux** | `--teammate-mode tmux` | Split panes | tmux installed | | **tmux** | `--teammate-mode tmux` | Split panes | tmux installed |
| **iTerm2** | Auto-detected | Native split panes | iTerm2 + `it2` CLI | | **iTerm2** | Auto-detected | Native split panes | iTerm2 + `it2` CLI |
**For Claudeman: use `in-process` only.** Claudeman manages its own tmux sessions externally. **For Codeman: use `in-process` only.** Codeman manages its own tmux sessions externally.
**In-process UI elements:** **In-process UI elements:**
- Status bar: `@main @teammate1 @teammate2 ...` with `shift+↑ to expand` - Status bar: `@main @teammate1 @teammate2 ...` with `shift+↑ to expand`
@@ -159,7 +159,7 @@ Fires when a task is being marked complete.
- Exit code 0: Allow completion - Exit code 0: Allow completion
- Exit code 2: Prevent completion, send feedback - Exit code 2: Prevent completion, send feedback
These are configured in `.claude/settings.local.json` alongside existing Claudeman hooks. These are configured in `.claude/settings.local.json` alongside existing Codeman hooks.
## Subagent-Watcher Compatibility (Verified) ## Subagent-Watcher Compatibility (Verified)
@@ -168,7 +168,7 @@ These are configured in `.claude/settings.local.json` alongside existing Claudem
~/.claude/projects/{hash}/{leadSessionId}/subagents/agent-{id}.jsonl ~/.claude/projects/{hash}/{leadSessionId}/subagents/agent-{id}.jsonl
``` ```
Claudeman's existing `subagent-watcher.ts` discovers them automatically. They appear in `/api/subagents` with status "active". Codeman's existing `subagent-watcher.ts` discovers them automatically. They appear in `/api/subagents` with status "active".
**Distinguishing teammates from regular subagents:** **Distinguishing teammates from regular subagents:**
- Description field starts with `<teammate-message teammate_id= team` - Description field starts with `<teammate-message teammate_id= team`
+6 -6
View File
@@ -1,4 +1,4 @@
# Claudeman Agent Teams Integration — Design (Approach C: Hybrid) # Codeman Agent Teams Integration — Design (Approach C: Hybrid)
> Updated 2026-02-12 with experiment findings. See `experiment-log.md` for raw data. > Updated 2026-02-12 with experiment findings. See `experiment-log.md` for raw data.
@@ -15,7 +15,7 @@ Monitors `~/.claude/teams/` for team creation/removal and tracks active teams.
**Discovery mechanism:** **Discovery mechanism:**
- Poll `~/.claude/teams/` for directories (team names) every 3-5 seconds - Poll `~/.claude/teams/` for directories (team names) every 3-5 seconds
- When found: parse `config.json` to get: - When found: parse `config.json` to get:
- `leadSessionId` → map to Claudeman session - `leadSessionId` → map to Codeman session
- `members` array → teammate names, agentIds, colors, models - `members` array → teammate names, agentIds, colors, models
- Watch for directory deletion (cleanup signal) - Watch for directory deletion (cleanup signal)
@@ -25,7 +25,7 @@ Monitors `~/.claude/teams/` for team creation/removal and tracks active teams.
- Teammate transcripts are at `subagents/agent-{id}.jsonl` (standard subagent path), NOT separate session transcripts - Teammate transcripts are at `subagents/agent-{id}.jsonl` (standard subagent path), NOT separate session transcripts
**Association:** **Association:**
- `config.json.leadSessionId` → Claudeman session ID (direct match!) - `config.json.leadSessionId` → Codeman session ID (direct match!)
- Each member's `agentId` (e.g., `fs-researcher@research-watchers`) → links to subagent files - Each member's `agentId` (e.g., `fs-researcher@research-watchers`) → links to subagent files
- `agentType: "team-lead"` vs `"general-purpose"` distinguishes lead from teammates - `agentType: "team-lead"` vs `"general-purpose"` distinguishes lead from teammates
@@ -37,7 +37,7 @@ Monitors `~/.claude/teams/` for team creation/removal and tracks active teams.
### 2. Team-Aware Idle Detection (HIGHEST PRIORITY) ### 2. Team-Aware Idle Detection (HIGHEST PRIORITY)
**Problem (confirmed by experiment):** Lead session shows status "idle" in Claudeman while teammates are actively working. Token count continues climbing but Claudeman thinks the session is inactive. **Problem (confirmed by experiment):** Lead session shows status "idle" in Codeman while teammates are actively working. Token count continues climbing but Codeman thinks the session is inactive.
**Solution:** **Solution:**
- Before declaring a session idle, check if it's a team lead - Before declaring a session idle, check if it's a team lead
@@ -125,7 +125,7 @@ Communication uses filesystem inbox files at `~/.claude/teams/{name}/inboxes/{te
**Potential for interaction (not tested, future work):** **Potential for interaction (not tested, future work):**
- Write to teammate inbox files to inject messages - Write to teammate inbox files to inject messages
- Must respect `.json.lock` directory locking protocol - Must respect `.json.lock` directory locking protocol
- Could enable "nudge" or "redirect" functionality from Claudeman UI - Could enable "nudge" or "redirect" functionality from Codeman UI
## Answered Questions (from experiment) ## Answered Questions (from experiment)
@@ -161,7 +161,7 @@ Communication uses filesystem inbox files at `~/.claude/teams/{name}/inboxes/{te
3. **Teammate badge in subagent windows** — mark teammate subagents with name/color 3. **Teammate badge in subagent windows** — mark teammate subagents with name/color
4. **Team tasks API + UI** — `GET /api/sessions/:id/team-tasks` + task list panel 4. **Team tasks API + UI** — `GET /api/sessions/:id/team-tasks` + task list panel
5. **Inbox monitoring** — watch inbox files, display message timeline 5. **Inbox monitoring** — watch inbox files, display message timeline
6. **TeammateIdle/TaskCompleted hooks** — add to Claudeman's hooks config generator 6. **TeammateIdle/TaskCompleted hooks** — add to Codeman's hooks config generator
## What We DON'T Need to Build ## What We DON'T Need to Build
+21 -21
View File
@@ -1,7 +1,7 @@
# Agent Teams Experiment Log # Agent Teams Experiment Log
> Experiment date: 2026-02-12 > Experiment date: 2026-02-12
> Test case: `~/claudeman-cases/agent-teams-test/` > Test case: `~/codeman-cases/agent-teams-test/`
> Team name: `research-watchers` > Team name: `research-watchers`
> Teammates: 3 (fs-researcher, perf-researcher, api-researcher) > Teammates: 3 (fs-researcher, perf-researcher, api-researcher)
> Lead session: `461daa80-94ec-4e5e-a1bb-0518f78311bc` > Lead session: `461daa80-94ec-4e5e-a1bb-0518f78311bc`
@@ -56,7 +56,7 @@ Name the team research-watchers.
Subagent transcripts also appear in the standard subagent directory: Subagent transcripts also appear in the standard subagent directory:
``` ```
~/.claude/projects/-home-arkon-claudeman-cases-agent-teams-test/ ~/.claude/projects/-home-arkon-codeman-cases-agent-teams-test/
└── 461daa80.../ └── 461daa80.../
├── 461daa80...jsonl # Lead session transcript ├── 461daa80...jsonl # Lead session transcript
└── subagents/ └── subagents/
@@ -102,7 +102,7 @@ Key observations:
- **File locking** via `.json.lock` directories (mkdir-based atomic lock, created then deleted) - **File locking** via `.json.lock` directories (mkdir-based atomic lock, created then deleted)
- Lead also has an inbox (`team-lead.json`) for receiving messages FROM teammates - Lead also has an inbox (`team-lead.json`) for receiving messages FROM teammates
**Implication for Claudeman:** We CAN intercept messages by watching inbox JSON files! We can also potentially inject messages by writing to inbox files. **Implication for Codeman:** We CAN intercept messages by watching inbox JSON files! We can also potentially inject messages by writing to inbox files.
--- ---
@@ -119,7 +119,7 @@ Teammates create transcript files at:
This is the **exact same path pattern** that regular Task tool subagents use. The existing `subagent-watcher.ts` successfully discovers them. This is the **exact same path pattern** that regular Task tool subagents use. The existing `subagent-watcher.ts` successfully discovers them.
Claudeman's `/api/subagents` endpoint returned them with status "active": Codeman's `/api/subagents` endpoint returned them with status "active":
``` ```
Agent: ae50544 Status: active Tools: 8 Model: claude-opus-4-6 Agent: ae50544 Status: active Tools: 8 Model: claude-opus-4-6
Desc: <teammate-message teammate_id= team Desc: <teammate-message teammate_id= team
@@ -156,7 +156,7 @@ Agent: a29de32 Status: active Tools: 7 Model: claude-opus-4-6
"model": "claude-opus-4-6", "model": "claude-opus-4-6",
"joinedAt": 1770875105373, "joinedAt": 1770875105373,
"tmuxPaneId": "", "tmuxPaneId": "",
"cwd": "/home/arkon/claudeman-cases/agent-teams-test", "cwd": "/home/arkon/codeman-cases/agent-teams-test",
"subscriptions": [] "subscriptions": []
}, },
{ {
@@ -169,7 +169,7 @@ Agent: a29de32 Status: active Tools: 7 Model: claude-opus-4-6
"planModeRequired": false, "planModeRequired": false,
"joinedAt": 1770875126680, "joinedAt": 1770875126680,
"tmuxPaneId": "in-process", "tmuxPaneId": "in-process",
"cwd": "/home/arkon/claudeman-cases/agent-teams-test", "cwd": "/home/arkon/codeman-cases/agent-teams-test",
"subscriptions": [], "subscriptions": [],
"backendType": "in-process" "backendType": "in-process"
}, },
@@ -183,7 +183,7 @@ Agent: a29de32 Status: active Tools: 7 Model: claude-opus-4-6
"planModeRequired": false, "planModeRequired": false,
"joinedAt": 1770875130344, "joinedAt": 1770875130344,
"tmuxPaneId": "in-process", "tmuxPaneId": "in-process",
"cwd": "/home/arkon/claudeman-cases/agent-teams-test", "cwd": "/home/arkon/codeman-cases/agent-teams-test",
"subscriptions": [], "subscriptions": [],
"backendType": "in-process" "backendType": "in-process"
}, },
@@ -197,7 +197,7 @@ Agent: a29de32 Status: active Tools: 7 Model: claude-opus-4-6
"planModeRequired": false, "planModeRequired": false,
"joinedAt": 1770875134997, "joinedAt": 1770875134997,
"tmuxPaneId": "in-process", "tmuxPaneId": "in-process",
"cwd": "/home/arkon/claudeman-cases/agent-teams-test", "cwd": "/home/arkon/codeman-cases/agent-teams-test",
"subscriptions": [], "subscriptions": [],
"backendType": "in-process" "backendType": "in-process"
} }
@@ -280,21 +280,21 @@ Evidence supporting external writes:
--- ---
## Question 7: What happens to Claudeman's idle detection with active teammates? ## Question 7: What happens to Codeman's idle detection with active teammates?
**Expected:** Lead may appear idle while teammates work **Expected:** Lead may appear idle while teammates work
**Actual: Lead stays "idle" in Claudeman's view, but terminal shows active status** **Actual: Lead stays "idle" in Codeman's view, but terminal shows active status**
Observations: Observations:
- Claudeman session status showed `"idle"` throughout the experiment - Codeman session status showed `"idle"` throughout the experiment
- The terminal output continued updating (task list checkboxes, teammate progress messages) - The terminal output continued updating (task list checkboxes, teammate progress messages)
- Lead displayed "Befuddling..." spinner while waiting for teammates - Lead displayed "Befuddling..." spinner while waiting for teammates
- Token count climbed from 27k → 33k during the experiment - Token count climbed from 27k → 33k during the experiment
- The `stop` hook DID fire at the end when the team was cleaned up - The `stop` hook DID fire at the end when the team was cleaned up
**Implication:** Current idle detection may trigger prematurely if: **Implication:** Current idle detection may trigger prematurely if:
- It only checks Claudeman's session status (which stays "idle") - It only checks Codeman's session status (which stays "idle")
- It doesn't account for active teammates - It doesn't account for active teammates
**What we need:** Check `~/.claude/teams/*/config.json` for active members before declaring idle. **What we need:** Check `~/.claude/teams/*/config.json` for active members before declaring idle.
@@ -308,7 +308,7 @@ Observations:
**Actual: ZERO separate processes! Teammates are in-process threads.** **Actual: ZERO separate processes! Teammates are in-process threads.**
``` ```
# Only 2 claude processes (both Claudeman sessions, none for teammates): # Only 2 claude processes (both Codeman sessions, none for teammates):
25405 claude --dangerously-skip-permissions --session-id 236f004f... (our main session) 25405 claude --dangerously-skip-permissions --session-id 236f004f... (our main session)
383633 claude --dangerously-skip-permissions --session-id 461daa80... (test session + 3 teammates) 383633 claude --dangerously-skip-permissions --session-id 461daa80... (test session + 3 teammates)
@@ -327,7 +327,7 @@ claude(383633)─┬─{claude}(383635)
--- ---
## Question 9: Do teammates inherit Claudeman env vars (hook events)? ## Question 9: Do teammates inherit Codeman env vars (hook events)?
**Expected:** Yes, if child processes **Expected:** Yes, if child processes
@@ -335,10 +335,10 @@ claude(383633)─┬─{claude}(383635)
Since teammates are threads in the lead's process (PID 383633), they share the exact same environment: Since teammates are threads in the lead's process (PID 383633), they share the exact same environment:
``` ```
CLAUDEMAN_SCREEN=1 CODEMAN_SCREEN=1
CLAUDEMAN_SESSION_ID=461daa80-94ec-4e5e-a1bb-0518f78311bc CODEMAN_SESSION_ID=461daa80-94ec-4e5e-a1bb-0518f78311bc
CLAUDEMAN_SCREEN_NAME=claudeman-461daa80 CODEMAN_SCREEN_NAME=codeman-461daa80
CLAUDEMAN_API_URL=http://localhost:3000 CODEMAN_API_URL=http://localhost:3000
``` ```
The `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1` env var was set via `settings.local.json`'s `env` key, which Claude Code reads at startup and sets on its process. The `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1` env var was set via `settings.local.json`'s `env` key, which Claude Code reads at startup and sets on its process.
@@ -358,7 +358,7 @@ Teammates create transcript files in the standard subagent path:
~/.claude/projects/{hash}/{leadSessionId}/subagents/agent-{id}.jsonl ~/.claude/projects/{hash}/{leadSessionId}/subagents/agent-{id}.jsonl
``` ```
Claudeman's `/api/subagents` endpoint returned all 3 teammates as active subagents. They're indistinguishable from regular Task tool subagents except: Codeman's `/api/subagents` endpoint returned all 3 teammates as active subagents. They're indistinguishable from regular Task tool subagents except:
1. Their `description` field starts with `<teammate-message teammate_id= team` 1. Their `description` field starts with `<teammate-message teammate_id= team`
2. They can be cross-referenced with `~/.claude/teams/{name}/config.json` 2. They can be cross-referenced with `~/.claude/teams/{name}/config.json`
3. They tend to be longer-lived than regular subagents 3. They tend to be longer-lived than regular subagents
@@ -419,7 +419,7 @@ Claudeman's `/api/subagents` endpoint returned all 3 teammates as active subagen
- Standard bypass permissions and token count still visible - Standard bypass permissions and token count still visible
**Subagent floating windows:** **Subagent floating windows:**
- Teammates DID appear as subagent floating windows in Claudeman's web UI - Teammates DID appear as subagent floating windows in Codeman's web UI
- They show the standard subagent info (model, tool calls, description) - They show the standard subagent info (model, tool calls, description)
- Sub-subagents (teammates' own Task tool usage) also appear - Sub-subagents (teammates' own Task tool usage) also appear
@@ -443,7 +443,7 @@ Claudeman's `/api/subagents` endpoint returned all 3 teammates as active subagen
7. **Sub-subagents** — teammates can spawn their own Task tool subagents (3-level hierarchy). 7. **Sub-subagents** — teammates can spawn their own Task tool subagents (3-level hierarchy).
8. **`teammateMode` is NOT a valid settings key** — display mode defaults to `in-process`. 8. **`teammateMode` is NOT a valid settings key** — display mode defaults to `in-process`.
### Design implications for Claudeman ### Design implications for Codeman
1. **TeamWatcher can be simple** — just poll `~/.claude/teams/` for directories + parse config.json 1. **TeamWatcher can be simple** — just poll `~/.claude/teams/` for directories + parse config.json
2. **Subagent-watcher already works** — no new infrastructure needed for teammate transcript tailing 2. **Subagent-watcher already works** — no new infrastructure needed for teammate transcript tailing
@@ -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: 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. 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. 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 ## 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: 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. 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. 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 ## 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 ## Quick Start
@@ -14,7 +14,7 @@ npm test -- test/browser-e2e.test.ts
## Framework Comparison Results ## 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 | | Framework | Avg Duration | Best For |
|-----------|--------------|----------| |-----------|--------------|----------|
@@ -114,7 +114,7 @@ await page.waitForSelector('.session-tab', { state: 'visible' });
// Assertions with expect // Assertions with expect
await expect(page.locator('.header')).toBeVisible(); await expect(page.locator('.header')).toBeVisible();
await expect(page).toHaveTitle('Claudeman'); await expect(page).toHaveTitle('Codeman');
await browser.close(); await browser.close();
``` ```
+1 -1
View File
@@ -1,6 +1,6 @@
# Codebase Cleanup Findings # 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 ## P0 — Bug Fix
+1 -1
View File
@@ -2,7 +2,7 @@
**Date**: 2026-02-18 **Date**: 2026-02-18
**Audit by**: 4-agent team (css-analyst, js-analyst, server-analyst, deps-analyst) **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"> <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> </svg>

Before

Width:  |  Height:  |  Size: 249 B

After

Width:  |  Height:  |  Size: 247 B

+1 -1
View File
@@ -2,7 +2,7 @@
## Context ## 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 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 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. 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 > **Author**: Claude Opus 4.6 | **Date**: 2026-02-26
> **Status**: Draft — Re-reviewed, MVP scope finalized. NOT pushed to GitHub > **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) 23. [Appendix A: OpenCode CLI Reference](#appendix-a-opencode-cli-reference)
24. [Appendix B: OpenCode Plugin Events](#appendix-b-opencode-plugin-events) 24. [Appendix B: OpenCode Plugin Events](#appendix-b-opencode-plugin-events)
25. [Appendix C: OpenCode Permission Config](#appendix-c-opencode-permission-config) 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 ## 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 ### Core Approach
@@ -53,7 +53,7 @@ Extend the existing `SessionMode` type from `'claude' | 'shell'` to `'claude' |
| Strategy | Approach | Complexity | Value | | Strategy | Approach | Complexity | Value |
|----------|----------|------------|-------| |----------|----------|------------|-------|
| **A: TUI-in-tmux** | Spawn `opencode` CLI in tmux, render in xterm.js | Medium | Full visual parity with Claude Code | | **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. **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 | > | **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 | > | **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? ### 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 - **Privacy-first**: Can run fully local via Ollama — no code ever leaves the machine
- **Open source**: MIT licensed, active community (700+ contributors) - **Open source**: MIT licensed, active community (700+ contributors)
- **Client/server**: Built-in `opencode serve` mode enables richer programmatic integration - **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 ## 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}) POST /api/sessions → new Session({mode: 'claude', mux: TmuxManager})
@@ -230,10 +230,10 @@ POST /api/sessions/:id/interactive → session.startInteractive()
↓ ↓
TmuxManager.createSession() 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>" 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 ptyProcess.onData() → emit('terminal') → SSE broadcast → xterm.js
``` ```
@@ -246,10 +246,10 @@ POST /api/sessions/:id/interactive → session.startInteractive()
↓ ↓
TmuxManager.createSession() TmuxManager.createSession()
↓ ↓
tmux new-session -ds "claudeman-<id>" tmux new-session -ds "codeman-<id>"
tmux respawn-pane -k -t ... "opencode --model <model>" 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 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) 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 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()` - Terminal data streaming via `ptyProcess.onData()`
- SSE event broadcasting via `broadcast()` - SSE event broadcasting via `broadcast()`
- xterm.js terminal rendering in the browser - 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) - Session CRUD API routes (create, get, delete)
- Tab management in frontend - Tab management in frontend
- Session kill/cleanup logic (`TmuxManager.killSession()`) - 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 | | Subagent detection | `BashToolParser` + `SubagentWatcher` | Different tool output format | DEFERRED |
| Ralph completion | `<promise>PHRASE</promise>` tags | Not applicable (needs alternative) | 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 | | 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 # 3. Test interactive TUI
opencode 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 new-session -ds "test-opencode" -c /tmp -x 120 -y 40
tmux set-option -t "test-opencode" remain-on-exit on tmux set-option -t "test-opencode" remain-on-exit on
tmux respawn-pane -k -t "test-opencode" 'opencode --model anthropic/claude-sonnet-4-5' 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 LANG=en_US.UTF-8`,
`export LC_ALL=en_US.UTF-8`, `export LC_ALL=en_US.UTF-8`,
`unset COLORTERM`, // Prevent color issues in tmux `unset COLORTERM`, // Prevent color issues in tmux
`export CLAUDEMAN_MUX=1`, `export CODEMAN_MUX=1`,
`export CLAUDEMAN_SESSION_ID=${sessionId}`, `export CODEMAN_SESSION_ID=${sessionId}`,
`export CLAUDEMAN_MUX_NAME=${muxName}`, `export CODEMAN_MUX_NAME=${muxName}`,
`export CLAUDEMAN_API_URL=${process.env.CLAUDEMAN_API_URL || 'http://localhost:3000'}`, `export CODEMAN_API_URL=${process.env.CODEMAN_API_URL || 'http://localhost:3000'}`,
]; ];
if (mode === 'opencode') { if (mode === 'opencode') {
@@ -905,7 +905,7 @@ const mode = cmd.includes('opencode') ? 'opencode' : 'claude';
### Known Limitations (from review) ### 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 > - `_claudeSessionId = this.id` correlation (session.ts:405) won't work for OpenCode
> - Subagent-session correlation via Session ID matching won't work > - Subagent-session correlation via Session ID matching won't work
> - Transcript watching via Claude session ID path 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 ### 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. > **[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.** > **[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 ### 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 ### 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 | | Trigger | Hook name matches event type | Event name subscription |
| Key events | `stop`, `idle_prompt`, `permission_prompt`, `elicitation_dialog` | `session.idle`, `permission.asked`, `session.status` | | Key events | `stop`, `idle_prompt`, `permission_prompt`, `elicitation_dialog` | `session.idle`, `permission.asked`, `session.status` |
| Communication | Exit codes + environment variables | Function context + return values | | 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 ```javascript
// This plugin bridges OpenCode events to Claudeman's API // This plugin bridges OpenCode events to Codeman's API
export const claudemanBridge = async ({ project, $ }) => { export const codemanBridge = async ({ project, $ }) => {
const apiUrl = process.env.CLAUDEMAN_API_URL || 'http://localhost:3000'; const apiUrl = process.env.CODEMAN_API_URL || 'http://localhost:3000';
const sessionId = process.env.CLAUDEMAN_SESSION_ID; const sessionId = process.env.CODEMAN_SESSION_ID;
if (!sessionId) return {}; if (!sessionId) return {};
@@ -1440,7 +1440,7 @@ export const claudemanBridge = async ({ project, $ }) => {
### Plugin Installation ### 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 ```typescript
// In session.ts or a new opencode-hooks.ts: // 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'); const pluginDir = join(workingDir, '.opencode', 'plugins');
await mkdirp(pluginDir); await mkdirp(pluginDir);
const pluginContent = generateClaudemanBridgePlugin(sessionId); const pluginContent = generateCodemanBridgePlugin(sessionId);
await writeFile(join(pluginDir, 'claudeman-bridge.js'), pluginContent); 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). > **[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 ```javascript
// Add to claudeman-bridge.js return object: // Add to codeman-bridge.js return object:
'session.compacted': async (info) => { 'session.compacted': async (info) => {
await notifyClademan('session_compacted', { info }); await notifyClademan('session_compacted', { info });
}, },
@@ -1472,12 +1472,12 @@ async function installOpenCodePlugin(workingDir: string, sessionId: string): Pro
### Benefits of the Plugin Bridge ### 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) - **`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) - **`tool.execute.*`** → Tool call tracking (similar to BashToolParser for Claude)
- **`todo.updated`** → OpenCode's built-in todo system → Claudeman can display it - **`todo.updated`** → OpenCode's built-in todo system → Codeman can display it
- **`session.error`** → Error surfacing in Claudeman UI - **`session.error`** → Error surfacing in Codeman UI
- **`session.compacted`** → Track auto-compaction events - **`session.compacted`** → Track auto-compaction events
--- ---
@@ -1664,7 +1664,7 @@ tmux session ─── opencode TUI (interactive, port N/A)
│ │
├── xterm.js (terminal rendering, Strategy 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 ├── GET /session/* → structured session data
├── POST /session/message → send prompt programmatically ├── 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. > **[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**: > **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 > - (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 > - (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/utils/ansi-content-filter.ts` | Strip cosmetic ANSI sequences for idle detection | 4 | DEFERRED |
| `src/completion-detector.ts` | `CompletionDetector` interface + implementations | 7 | 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 | | `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/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 | | `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 ### 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 - **Use MockSession** from `test/respawn-test-utils.ts` for respawn testing
- **Mock the opencode binary** for unit tests (`jest.mock` or stub) - **Mock the opencode binary** for unit tests (`jest.mock` or stub)
- **Use unique test ports** (3155+) — never port 3000 - **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?** 5. **Should we auto-generate `opencode.json` in the working directory?**
- Option A: Let OpenCode use existing project config (respect user settings) - Option A: Let OpenCode use existing project config (respect user settings)
- Option B: Generate a temporary one with Claudeman's settings - Option B: Generate a temporary one with Codeman's settings
- Recommendation: Option A (use `OPENCODE_CONFIG_CONTENT` env var for Claudeman-specific overrides, don't modify project files) - 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) - 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 - 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 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) 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 | | 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** | | **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 | | **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 | | **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 ## 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 ### Review Team
@@ -2182,7 +2182,7 @@ Environment Variables:
Full list of subscribable events in OpenCode's plugin system: 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 | | **Command** | `command.executed` | Slash command run | Low |
| **Files** | `file.edited` | File modified | Medium (track changes) | | **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.updated` | Complete message | High (completion detection) |
| | `message.removed` | Message deleted | Low | | | `message.removed` | Message deleted | Low |
| | `message.part.removed` | Part 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) | | | `permission.replied` | User responded | High (track approvals) |
| **Server** | `server.connected` | Server started | Medium | | **Server** | `server.connected` | Server started | Medium |
| **Sessions** | `session.idle` | Agent finished working | **Critical** (idle detection!) | | **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 - `"ask"` — Prompt user for approval
- `"deny"` — Block the action - `"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: 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) ### 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', ...) → If none: this._mux.createSession(id, workingDir, 'claude', ...)
4. TmuxManager.createSession() (tmux-manager.ts:225) 4. TmuxManager.createSession() (tmux-manager.ts:225)
→ tmux new-session -ds "claudeman-<shortId>" -c <workingDir> -x 120 -y 40 → tmux new-session -ds "codeman-<shortId>" -c <workingDir> -x 120 -y 40
→ tmux set-option -t "claudeman-<shortId>" remain-on-exit on → tmux set-option -t "codeman-<shortId>" remain-on-exit on
→ Build command: "export PATH=... && export LANG=... && claude --dangerously-skip-permissions --session-id <id>" → 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 → Wait 100ms, configure tmux, get PID
→ Return MuxSession { muxName, pid, mode } → Return MuxSession { muxName, pid, mode }
5. Back in startInteractive() (session.ts:~950) 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', name: 'xterm-256color',
cols: 120, rows: 40, cols: 120, rows: 40,
env: { LANG, LC_ALL, TERM } env: { LANG, LC_ALL, TERM }
+2 -2
View File
@@ -1,7 +1,7 @@
# Performance Audit: First Page Load # Performance Audit: First Page Load
**Date**: 2026-02-18 **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) **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] │ [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) │ ├── initTerminal() ................... (SYNC: new Terminal() + terminal.open() → canvas creation)
│ ├── connectSSE() → /api/events ....... (SSE → fires 'init' with getLightState()) │ ├── connectSSE() → /api/events ....... (SSE → fires 'init' with getLightState())
│ ├── loadState() → /api/status ........ (DUPLICATE #1: same data as SSE init!) │ ├── 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 **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 **Method**: 4-agent parallel analysis of server, PTY pipeline, frontend, and background systems
--- ---
## Executive Summary ## 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. 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 ## 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 ## Key Concepts to Implement
@@ -181,7 +181,7 @@ Three states: CLOSED → HALF_OPEN → OPEN
### 3.1 Template Library ### 3.1 Template Library
- Bug Fix, Feature, Refactoring, Test Coverage, Documentation templates - Bug Fix, Feature, Refactoring, Test Coverage, Documentation templates
- Template selector in wizard - Template selector in wizard
- Custom templates in `~/.claudeman/templates/` - Custom templates in `~/.codeman/templates/`
### 3.2 Tool Permissions ### 3.2 Tool Permissions
- Configure allowed Claude tools per loop - Configure allowed Claude tools per loop
+8 -8
View File
@@ -19,7 +19,7 @@
8. [Prompt Templates](#prompt-templates) 8. [Prompt Templates](#prompt-templates)
9. [When to Use (and Not Use)](#when-to-use-and-not-use) 9. [When to Use (and Not Use)](#when-to-use-and-not-use)
10. [Real-World Examples](#real-world-examples) 10. [Real-World Examples](#real-world-examples)
11. [Claudeman Implementation](#claudeman-implementation) 11. [Codeman Implementation](#codeman-implementation)
12. [Troubleshooting](#troubleshooting) 12. [Troubleshooting](#troubleshooting)
--- ---
@@ -148,18 +148,18 @@ The completion phrase pattern is the core contract between Claude and the loop s
### False Positive Prevention ### 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 - Initial prompts
- Documentation or examples - Documentation or examples
- Comments - 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) - **1st occurrence**: Store as expected phrase (likely in the prompt)
- **2nd occurrence**: Emit `completionDetected` (actual completion) - **2nd occurrence**: Emit `completionDetected` (actual completion)
- **If loop already active**: Emit immediately (explicit loop start via `/ralph-loop:ralph-loop`) - **If loop already active**: Emit immediately (explicit loop start via `/ralph-loop:ralph-loop`)
```typescript ```typescript
// From claudeman/src/ralph-tracker.ts // From codeman/src/ralph-tracker.ts
private handleCompletionPhrase(phrase: string): void { private handleCompletionPhrase(phrase: string): void {
const count = (this._completionPhraseCount.get(phrase) || 0) + 1; const count = (this._completionPhraseCount.get(phrase) || 0) + 1;
this._completionPhraseCount.set(phrase, count); 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 ### 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) - [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) - [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-tracker.ts` - Core detection engine
- `src/ralph-loop.ts` - Task orchestration - `src/ralph-loop.ts` - Task orchestration
- `src/respawn-controller.ts` - Session cycling - `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 ## 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) - Backend event pipeline (server.ts, hooks-config.ts, team-watcher.ts, subagent-watcher.ts)
- Frontend notification manager (app.js NotificationManager class, 4-layer architecture) - Frontend notification manager (app.js NotificationManager class, 4-layer architecture)
- Settings UI and persistence (localStorage + server backup) - 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 Date: 2026-02-17
## Executive Summary ## 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. 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) ### 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 ```typescript
const curlCmd = (event: HookEventType) => const curlCmd = (event: HookEventType) =>
`HOOK_DATA=$(cat 2>/dev/null || echo '{}'); ` + `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' ` + `-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`; `2>/dev/null || true`;
``` ```
@@ -143,8 +143,8 @@ The `sanitizeHookData()` function limits what gets broadcast:
### 2.4 Environment Variables (Lines 70-101) ### 2.4 Environment Variables (Lines 70-101)
Two env vars are set per case directory via `updateCaseEnvVars()`: Two env vars are set per case directory via `updateCaseEnvVars()`:
- `CLAUDEMAN_API_URL` -- server URL (e.g., `http://localhost:3000`) - `CODEMAN_API_URL` -- server URL (e.g., `http://localhost:3000`)
- `CLAUDEMAN_SESSION_ID` -- session identifier - `CODEMAN_SESSION_ID` -- session identifier
These are resolved at runtime by the shell, so the hook config is static per case. 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 ## 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 1. **In-app notification drawer** (Layer 1) - badge + list UI
2. **Document title flashing** (Layer 2) - tab title blinks when hidden 2. **Document title flashing** (Layer 2) - tab title blinks when hidden
@@ -32,19 +32,19 @@ updateTabTitle() {
this.titleFlashInterval = setInterval(() => { this.titleFlashInterval = setInterval(() => {
this.titleFlashState = !this.titleFlashState; this.titleFlashState = !this.titleFlashState;
document.title = this.titleFlashState document.title = this.titleFlashState
? `\u26A0\uFE0F (${this.unreadCount}) Claudeman` ? `\u26A0\uFE0F (${this.unreadCount}) Codeman`
: this.originalTitle; : this.originalTitle;
}, TITLE_FLASH_INTERVAL_MS); }, TITLE_FLASH_INTERVAL_MS);
// Set immediately // Set immediately
document.title = `\u26A0\uFE0F (${this.unreadCount}) Claudeman`; document.title = `\u26A0\uFE0F (${this.unreadCount}) Codeman`;
} }
} }
} }
``` ```
The title alternates between: The title alternates between:
- Warning emoji + unread count: `"(3) Claudeman"` - Warning emoji + unread count: `"(3) Codeman"`
- Original title: `"Claudeman"` - Original title: `"Codeman"`
### What Triggers It ### 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: 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): Browser notifications reference `/favicon.ico` as their icon (line 1130):
```js ```js
const notif = new Notification(`Claudeman: ${title}`, { const notif = new Notification(`Codeman: ${title}`, {
body, body,
tag, tag,
icon: '/favicon.ico', icon: '/favicon.ico',
@@ -306,7 +306,7 @@ Key detail: Title flash always stops when tab becomes visible, but **unread coun
## 5. Focus/Blur Handling ## 5. Focus/Blur Handling
### No window focus/blur listeners ### 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. 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 > Generated: 2026-02-17
> Source files analyzed: > Source files analyzed:
> - `/home/arkon/default/claudeman/src/web/public/app.js` (main frontend, ~15k lines) > - `/home/arkon/default/codeman/src/web/public/app.js` (main frontend, ~15k lines)
> - `/home/arkon/default/claudeman/src/web/public/index.html` > - `/home/arkon/default/codeman/src/web/public/index.html`
> - `/home/arkon/default/claudeman/src/web/public/styles.css` > - `/home/arkon/default/codeman/src/web/public/styles.css`
> - `/home/arkon/default/claudeman/src/web/public/mobile.css` > - `/home/arkon/default/codeman/src/web/public/mobile.css`
--- ---
@@ -87,8 +87,8 @@ const defaults = {
#### Storage Keys (lines 952-956) #### Storage Keys (lines 952-956)
Device-specific localStorage keys prevent mobile settings from overriding desktop settings: Device-specific localStorage keys prevent mobile settings from overriding desktop settings:
- Desktop: `claudeman-notification-prefs` - Desktop: `codeman-notification-prefs`
- Mobile: `claudeman-notification-prefs-mobile` - Mobile: `codeman-notification-prefs-mobile`
#### Version Migrations (lines 928-940) #### 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: When the tab is not visible and there are unread notifications:
1. `setInterval` at 1500ms toggles between: 1. `setInterval` at 1500ms toggles between:
- Warning emoji + unread count: `"(3) Claudeman"` - Warning emoji + unread count: `"(3) Codeman"`
- Original title: `"Claudeman"` - Original title: `"Codeman"`
2. Set immediately on first notification (no wait for first interval tick) 2. Set immediately on first notification (no wait for first interval tick)
### Stopping ### Stopping
@@ -311,7 +311,7 @@ sendBrowserNotif() called
### Notification Object (lines 1127-1143) ### Notification Object (lines 1127-1143)
```js ```js
new Notification(`Claudeman: ${title}`, { new Notification(`Codeman: ${title}`, {
body, body,
tag, // Groups same-tag notifications (replaces previous with same tag) tag, // Groups same-tag notifications (replaces previous with same tag)
icon: '/favicon.ico', 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): 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 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) ### 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. 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 #### Alerts Section
| Setting | Element ID | Type | Default | | Setting | Element ID | Type | Default |
@@ -54,16 +54,16 @@ Notification preferences are stored in **two places simultaneously**:
#### Layer 1: localStorage (primary, device-specific) #### 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()` - 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 - Device type is based on `window.innerWidth`: `<430` = mobile, `430-768` = tablet, `>=768` = desktop
- Read in `loadPreferences()` (line 896), written in `savePreferences()` (line 958) - 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) - On save, notification prefs are bundled with app settings: `{ ...settings, notificationPreferences: notifPrefsToSave }` (line 9475)
- Sent via `PUT /api/settings` to the Fastify server - 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 - The `notificationPreferences` key sits at the top level of the settings JSON alongside app settings
#### Load priority #### Load priority
@@ -109,8 +109,8 @@ On startup, `loadAppSettingsFromServer()` (line 9787) fetches from server and:
### App settings (separate from notification prefs) ### App settings (separate from notification prefs)
App settings use a different device-specific localStorage key: App settings use a different device-specific localStorage key:
- Desktop: `claudeman-app-settings` - Desktop: `codeman-app-settings`
- Mobile: `claudeman-app-settings-mobile` - Mobile: `codeman-app-settings-mobile`
- Determined by `getSettingsStorageKey()` at line 9562 - Determined by `getSettingsStorageKey()` at line 9562
@@ -213,8 +213,8 @@ const AUTO_CLOSE_NOTIFICATION_MS = 8000; // Auto-close browser notifications
### Separate storage keys - YES ### Separate storage keys - YES
Desktop and mobile use completely separate localStorage keys: Desktop and mobile use completely separate localStorage keys:
- **Notification prefs**: `claudeman-notification-prefs` vs `claudeman-notification-prefs-mobile` - **Notification prefs**: `codeman-notification-prefs` vs `codeman-notification-prefs-mobile`
- **App settings**: `claudeman-app-settings` vs `claudeman-app-settings-mobile` - **App settings**: `codeman-app-settings` vs `codeman-app-settings-mobile`
### Different defaults - YES ### Different defaults - YES
@@ -303,7 +303,7 @@ The settings UI shows the current permission state via a status badge:
### HTTPS requirement ### 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 ## 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. 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) - Preferences are loaded once from localStorage (line 878)
- All sessions share the same notification rules - All sessions share the same notification rules
+1 -1
View File
@@ -1,6 +1,6 @@
# Terminal Anti-Flicker System # 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 ## Pipeline Overview
+8 -8
View File
@@ -1,4 +1,4 @@
# Claudeman TypeScript Improvement Suggestions # Codeman TypeScript Improvement Suggestions
**Generated**: February 2026 **Generated**: February 2026
**Based on**: Research into TypeScript best practices (2024-2025) and codebase analysis **Based on**: Research into TypeScript best practices (2024-2025) and codebase analysis
@@ -229,33 +229,33 @@ Replace string-based errors with typed errors:
```typescript ```typescript
// src/errors.ts // src/errors.ts
export class ClaudemanError extends Error { export class CodemanError extends Error {
constructor( constructor(
message: string, message: string,
public code: string, public code: string,
public context?: Record<string, unknown> public context?: Record<string, unknown>
) { ) {
super(message); super(message);
Object.setPrototypeOf(this, ClaudemanError.prototype); Object.setPrototypeOf(this, CodemanError.prototype);
this.name = 'ClaudemanError'; this.name = 'CodemanError';
} }
} }
export class SessionError extends ClaudemanError { export class SessionError extends CodemanError {
constructor(message: string, code: string, public sessionId: string) { constructor(message: string, code: string, public sessionId: string) {
super(message, code, { sessionId }); super(message, code, { sessionId });
this.name = 'SessionError'; this.name = 'SessionError';
} }
} }
export class ValidationError extends ClaudemanError { export class ValidationError extends CodemanError {
constructor(message: string, public field: string, public value: unknown) { constructor(message: string, public field: string, public value: unknown) {
super(message, 'VALIDATION_ERROR', { field, value }); super(message, 'VALIDATION_ERROR', { field, value });
this.name = 'ValidationError'; this.name = 'ValidationError';
} }
} }
export class ScreenError extends ClaudemanError { export class ScreenError extends CodemanError {
constructor(message: string, public screenName: string, public operation: string) { constructor(message: string, public screenName: string, public operation: string) {
super(message, 'SCREEN_ERROR', { screenName, operation }); super(message, 'SCREEN_ERROR', { screenName, operation });
this.name = 'ScreenError'; this.name = 'ScreenError';
@@ -324,7 +324,7 @@ Enforce ID formats at compile time:
```typescript ```typescript
type CycleIdFormat = `${string}:cycle-${number}`; type CycleIdFormat = `${string}:cycle-${number}`;
type ScreenSessionName = `claudeman-${string}`; type ScreenSessionName = `codeman-${string}`;
interface RespawnCycleMetrics { interface RespawnCycleMetrics {
cycleId: CycleIdFormat; // Enforces format at compile time 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. Confirmed by research: Web Speech API is the right choice.
- **Free, fast (150-300ms interim), trivial complexity** - **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 - Works on localhost without HTTPS
- Accuracy is adequate for English command dictation - Accuracy is adequate for English command dictation
- Deepgram streaming (Phase 2 optional) only if accuracy complaints arise - Deepgram streaming (Phase 2 optional) only if accuracy complaints arise
+40 -40
View File
@@ -1,14 +1,14 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Claudeman Universal Installer # Codeman Universal Installer
# https://github.com/Ark0N/claudeman # https://github.com/Ark0N/Codeman
# #
# Usage: curl -fsSL https://raw.githubusercontent.com/Ark0N/claudeman/master/install.sh | bash # Usage: curl -fsSL https://raw.githubusercontent.com/Ark0N/Codeman/master/install.sh | bash
# #
# Environment variables: # Environment variables:
# CLAUDEMAN_NONINTERACTIVE=1 - Skip all prompts (for CI/automation) # CODEMAN_NONINTERACTIVE=1 - Skip all prompts (for CI/automation)
# CLAUDEMAN_INSTALL_DIR - Custom install directory (default: ~/.claudeman/app) # CODEMAN_INSTALL_DIR - Custom install directory (default: ~/.codeman/app)
# CLAUDEMAN_SKIP_SYSTEMD=1 - Skip systemd service setup prompt # CODEMAN_SKIP_SYSTEMD=1 - Skip systemd service setup prompt
# CLAUDEMAN_NODE_VERSION - Node.js major version to install (default: 22) # CODEMAN_NODE_VERSION - Node.js major version to install (default: 22)
set -euo pipefail set -euo pipefail
@@ -16,12 +16,12 @@ set -euo pipefail
# Configuration # Configuration
# ============================================================================ # ============================================================================
INSTALL_DIR="${CLAUDEMAN_INSTALL_DIR:-$HOME/.claudeman/app}" INSTALL_DIR="${CODEMAN_INSTALL_DIR:-$HOME/.codeman/app}"
REPO_URL="https://github.com/Ark0N/claudeman.git" REPO_URL="https://github.com/Ark0N/Codeman.git"
MIN_NODE_VERSION=18 MIN_NODE_VERSION=18
TARGET_NODE_VERSION="${CLAUDEMAN_NODE_VERSION:-22}" TARGET_NODE_VERSION="${CODEMAN_NODE_VERSION:-22}"
NONINTERACTIVE="${CLAUDEMAN_NONINTERACTIVE:-0}" NONINTERACTIVE="${CODEMAN_NONINTERACTIVE:-0}"
SKIP_SYSTEMD="${CLAUDEMAN_SKIP_SYSTEMD:-0}" SKIP_SYSTEMD="${CODEMAN_SKIP_SYSTEMD:-0}"
# Claude CLI search paths (from src/session.ts) # Claude CLI search paths (from src/session.ts)
CLAUDE_SEARCH_PATHS=( CLAUDE_SEARCH_PATHS=(
@@ -611,15 +611,15 @@ add_to_path() {
if [[ "$shell_name" == "fish" ]]; then if [[ "$shell_name" == "fish" ]]; then
echo "" >> "$profile" echo "" >> "$profile"
echo "# Added by Claudeman installer" >> "$profile" echo "# Added by Codeman installer" >> "$profile"
echo "fish_add_path $bin_dir" >> "$profile" echo "fish_add_path $bin_dir" >> "$profile"
else else
echo "" >> "$profile" echo "" >> "$profile"
echo "# Added by Claudeman installer" >> "$profile" echo "# Added by Codeman installer" >> "$profile"
echo "export PATH=\"$bin_dir:\$PATH\"" >> "$profile" echo "export PATH=\"$bin_dir:\$PATH\"" >> "$profile"
fi fi
# Also export for the current process so claudeman works immediately # Also export for the current process so codeman works immediately
export PATH="$bin_dir:$PATH" export PATH="$bin_dir:$PATH"
success "Added to $profile" success "Added to $profile"
@@ -636,7 +636,7 @@ setup_sc_alias() {
fi fi
echo "" >> "$profile" echo "" >> "$profile"
echo "# Claudeman tmux session shortcut" >> "$profile" echo "# Codeman tmux session shortcut" >> "$profile"
echo "alias sc='tmux-chooser'" >> "$profile" echo "alias sc='tmux-chooser'" >> "$profile"
info "Added 'sc' alias for tmux-chooser" info "Added 'sc' alias for tmux-chooser"
@@ -648,7 +648,7 @@ setup_sc_alias() {
setup_systemd_service() { setup_systemd_service() {
local service_dir="$HOME/.config/systemd/user" local service_dir="$HOME/.config/systemd/user"
local service_file="$service_dir/claudeman-web.service" local service_file="$service_dir/codeman-web.service"
info "Setting up systemd user service..." info "Setting up systemd user service..."
@@ -661,7 +661,7 @@ setup_systemd_service() {
# Create service file # Create service file
cat > "$service_file" << EOF cat > "$service_file" << EOF
[Unit] [Unit]
Description=Claudeman Web Server Description=Codeman Web Server
After=network.target After=network.target
[Service] [Service]
@@ -681,7 +681,7 @@ EOF
systemctl --user daemon-reload systemctl --user daemon-reload
# Enable service # Enable service
systemctl --user enable claudeman-web.service 2>/dev/null || true systemctl --user enable codeman-web.service 2>/dev/null || true
# Enable lingering (allows service to run after logout) # Enable lingering (allows service to run after logout)
if command -v loginctl &>/dev/null; then if command -v loginctl &>/dev/null; then
@@ -689,7 +689,7 @@ EOF
fi fi
success "Systemd service installed at $service_file" success "Systemd service installed at $service_file"
info "Start with: systemctl --user start claudeman-web" info "Start with: systemctl --user start codeman-web"
} }
# ============================================================================ # ============================================================================
@@ -760,7 +760,7 @@ main() {
if prompt_yes_no "Git is not installed. Install it now?"; then if prompt_yes_no "Git is not installed. Install it now?"; then
install_dependency "git" "$os" "$distro" install_dependency "git" "$os" "$distro"
else else
die "Git is required to install Claudeman." die "Git is required to install Codeman."
fi fi
else else
success "Git is installed" success "Git is installed"
@@ -781,7 +781,7 @@ main() {
# Rehash to pick up new node # Rehash to pick up new node
hash -r 2>/dev/null || true hash -r 2>/dev/null || true
else else
die "Node.js $MIN_NODE_VERSION+ is required to run Claudeman." die "Node.js $MIN_NODE_VERSION+ is required to run Codeman."
fi fi
else else
local node_ver local node_ver
@@ -832,7 +832,7 @@ main() {
# Clone/Update Repository # Clone/Update Repository
# ======================================================================== # ========================================================================
info "Installing Claudeman to $INSTALL_DIR..." info "Installing Codeman to $INSTALL_DIR..."
if [[ -d "$INSTALL_DIR/.git" ]]; then if [[ -d "$INSTALL_DIR/.git" ]]; then
info "Existing installation found, updating..." info "Existing installation found, updating..."
@@ -882,8 +882,8 @@ main() {
local symlink_dir="$HOME/.local/bin" local symlink_dir="$HOME/.local/bin"
mkdir -p "$symlink_dir" 2>/dev/null || true mkdir -p "$symlink_dir" 2>/dev/null || true
if [[ -d "$symlink_dir" ]]; then if [[ -d "$symlink_dir" ]]; then
ln -sf "$INSTALL_DIR/dist/index.js" "$symlink_dir/claudeman" ln -sf "$INSTALL_DIR/dist/index.js" "$symlink_dir/codeman"
info "Created symlink: $symlink_dir/claudeman" info "Created symlink: $symlink_dir/codeman"
# Install tmux-chooser as 'tmux-chooser' command # Install tmux-chooser as 'tmux-chooser' command
if [[ -f "$INSTALL_DIR/scripts/tmux-chooser.sh" ]]; then if [[ -f "$INSTALL_DIR/scripts/tmux-chooser.sh" ]]; then
@@ -916,16 +916,16 @@ main() {
echo "" echo ""
echo -e "${GREEN}${BOLD}============================================================${NC}" echo -e "${GREEN}${BOLD}============================================================${NC}"
echo -e "${GREEN}${BOLD} Claudeman installed successfully!${NC}" echo -e "${GREEN}${BOLD} Codeman installed successfully!${NC}"
echo -e "${GREEN}${BOLD}============================================================${NC}" echo -e "${GREEN}${BOLD}============================================================${NC}"
echo "" echo ""
echo -e " ${BOLD}Quick Start:${NC}" echo -e " ${BOLD}Quick Start:${NC}"
echo "" echo ""
echo -e " ${CYAN}# Start the web server${NC}" echo -e " ${CYAN}# Start the web server${NC}"
echo -e " claudeman web" echo -e " codeman web"
echo "" echo ""
echo -e " ${CYAN}# Start with HTTPS (only needed for remote access)${NC}" echo -e " ${CYAN}# Start with HTTPS (only needed for remote access)${NC}"
echo -e " claudeman web --https" echo -e " codeman web --https"
echo "" echo ""
echo -e " ${CYAN}# Open in browser${NC}" echo -e " ${CYAN}# Open in browser${NC}"
echo -e " http://localhost:3000" echo -e " http://localhost:3000"
@@ -937,21 +937,21 @@ main() {
echo -e " ${CYAN}sc -h${NC} # Help" echo -e " ${CYAN}sc -h${NC} # Help"
echo "" echo ""
if [[ "$os" == "linux" ]] && [[ -f "$HOME/.config/systemd/user/claudeman-web.service" ]]; then if [[ "$os" == "linux" ]] && [[ -f "$HOME/.config/systemd/user/codeman-web.service" ]]; then
echo -e " ${BOLD}Systemd Service:${NC}" echo -e " ${BOLD}Systemd Service:${NC}"
echo "" echo ""
echo -e " ${CYAN}systemctl --user start claudeman-web${NC} # Start" echo -e " ${CYAN}systemctl --user start codeman-web${NC} # Start"
echo -e " ${CYAN}systemctl --user status claudeman-web${NC} # Check status" echo -e " ${CYAN}systemctl --user status codeman-web${NC} # Check status"
echo -e " ${CYAN}journalctl --user -u claudeman-web -f${NC} # View logs" echo -e " ${CYAN}journalctl --user -u codeman-web -f${NC} # View logs"
echo "" echo ""
fi fi
echo -e " ${BOLD}Documentation:${NC}" echo -e " ${BOLD}Documentation:${NC}"
echo -e " https://github.com/Ark0N/claudeman" echo -e " https://github.com/Ark0N/Codeman"
echo "" echo ""
if ! check_claude; then if ! check_claude; then
echo -e " ${YELLOW}${BOLD}Reminder:${NC} Install Claude CLI to start using Claudeman:" echo -e " ${YELLOW}${BOLD}Reminder:${NC} Install Claude CLI to start using Codeman:"
echo -e " ${CYAN}curl -fsSL https://claude.ai/install.sh | bash${NC}" echo -e " ${CYAN}curl -fsSL https://claude.ai/install.sh | bash${NC}"
echo "" echo ""
fi fi
@@ -959,20 +959,20 @@ main() {
# Check if PATH needs reload in user's shell # Check if PATH needs reload in user's shell
local profile local profile
profile=$(detect_shell_profile) profile=$(detect_shell_profile)
if ! command -v claudeman &>/dev/null 2>&1; then if ! command -v codeman &>/dev/null 2>&1; then
echo -e " ${YELLOW}Run this to start using claudeman now:${NC}" echo -e " ${YELLOW}Run this to start using codeman now:${NC}"
echo "" echo ""
echo -e " ${CYAN}source $profile && claudeman web${NC}" echo -e " ${CYAN}source $profile && codeman web${NC}"
echo "" echo ""
fi fi
} }
update() { update() {
if [[ ! -d "$INSTALL_DIR/.git" ]]; then if [[ ! -d "$INSTALL_DIR/.git" ]]; then
die "Claudeman is not installed at $INSTALL_DIR. Run the installer first." die "Codeman is not installed at $INSTALL_DIR. Run the installer first."
fi fi
info "Updating Claudeman..." info "Updating Codeman..."
cd "$INSTALL_DIR" cd "$INSTALL_DIR"
git fetch --quiet origin git fetch --quiet origin
git reset --hard origin/master --quiet git reset --hard origin/master --quiet
@@ -980,7 +980,7 @@ update() {
npm run build --quiet 2>/dev/null || npm run build npm run build --quiet 2>/dev/null || npm run build
success "Updated to $(node -e "console.log(require('./package.json').version)")" success "Updated to $(node -e "console.log(require('./package.json').version)")"
echo "" echo ""
echo -e " ${DIM}Restart claudeman web to use the new version.${NC}" echo -e " ${DIM}Restart codeman web to use the new version.${NC}"
echo "" echo ""
} }
+3 -3
View File
@@ -1,12 +1,12 @@
# Claudeman Mobile Test Suite # Codeman Mobile Test Suite
Comprehensive mobile UI testing for Claudeman's web interface using Playwright with dual-engine support (Chromium + WebKit). Comprehensive mobile UI testing for Codeman's web interface using Playwright with dual-engine support (Chromium + WebKit).
**325 tests across 135 devices — all passing.** **325 tests across 135 devices — all passing.**
## Purpose ## Purpose
Validates Claudeman's mobile UI across 135 devices, covering: Validates Codeman's mobile UI across 135 devices, covering:
- **Keyboard simulation** — 3-layer approach to emulate virtual keyboards in headless browsers - **Keyboard simulation** — 3-layer approach to emulate virtual keyboards in headless browsers
- **Touch/swipe interactions** — CDP trusted events (Chromium) + synthetic fallback (WebKit) - **Touch/swipe interactions** — CDP trusted events (Chromium) + synthetic fallback (WebKit)
+3 -3
View File
@@ -126,7 +126,7 @@ export const BODY_CLASSES = {
// Settings localStorage keys // Settings localStorage keys
export const STORAGE_KEYS = { export const STORAGE_KEYS = {
SETTINGS_MOBILE: 'claudeman-app-settings-mobile', SETTINGS_MOBILE: 'codeman-app-settings-mobile',
SETTINGS_DESKTOP: 'claudeman-app-settings', SETTINGS_DESKTOP: 'codeman-app-settings',
NOTIFICATION_PREFS_MOBILE: 'claudeman-notification-prefs-mobile', NOTIFICATION_PREFS_MOBILE: 'codeman-notification-prefs-mobile',
} as const; } as const;
+3 -3
View File
@@ -43,7 +43,7 @@ describe('Settings Modal', () => {
await context.close(); await context.close();
}); });
it('uses claudeman-app-settings-mobile localStorage key', async () => { it('uses codeman-app-settings-mobile localStorage key', async () => {
const key = await page.evaluate(() => { const key = await page.evaluate(() => {
return (window as any).app?.getSettingsStorageKey?.() ?? null; return (window as any).app?.getSettingsStorageKey?.() ?? null;
}); });
@@ -53,10 +53,10 @@ describe('Settings Modal', () => {
} else { } else {
// Verify by checking what key is used when saving settings // Verify by checking what key is used when saving settings
await page.evaluate(() => { await page.evaluate(() => {
localStorage.setItem('claudeman-app-settings-mobile', JSON.stringify({ testFlag: true })); localStorage.setItem('codeman-app-settings-mobile', JSON.stringify({ testFlag: true }));
}); });
const stored = await page.evaluate(() => { const stored = await page.evaluate(() => {
return localStorage.getItem('claudeman-app-settings-mobile'); return localStorage.getItem('codeman-app-settings-mobile');
}); });
expect(stored).toContain('testFlag'); expect(stored).toContain('testFlag');
} }
+3 -3
View File
@@ -1,11 +1,11 @@
{ {
"name": "claudeman", "name": "codeman",
"version": "0.1619", "version": "0.1619",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "claudeman", "name": "codeman",
"version": "0.1619", "version": "0.1619",
"hasInstallScript": true, "hasInstallScript": true,
"license": "MIT", "license": "MIT",
@@ -25,7 +25,7 @@
"zod": "^4.3.6" "zod": "^4.3.6"
}, },
"bin": { "bin": {
"claudeman": "dist/index.js" "codeman": "dist/index.js"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^20.19.33", "@types/node": "^20.19.33",
+5 -5
View File
@@ -1,12 +1,12 @@
{ {
"name": "claudeman", "name": "codeman",
"version": "0.1650", "version": "0.1650",
"description": "The missing control plane for Claude Code - run 20 autonomous agents with real-time monitoring and session persistence", "description": "The missing control plane for Claude Code - run 20 autonomous agents with real-time monitoring and session persistence",
"type": "module", "type": "module",
"main": "dist/index.js", "main": "dist/index.js",
"types": "dist/index.d.ts", "types": "dist/index.d.ts",
"bin": { "bin": {
"claudeman": "./dist/index.js" "codeman": "./dist/index.js"
}, },
"scripts": { "scripts": {
"postinstall": "node scripts/postinstall.js", "postinstall": "node scripts/postinstall.js",
@@ -71,12 +71,12 @@
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/Ark0N/claudeman.git" "url": "git+https://github.com/Ark0N/Codeman.git"
}, },
"bugs": { "bugs": {
"url": "https://github.com/Ark0N/claudeman/issues" "url": "https://github.com/Ark0N/Codeman/issues"
}, },
"homepage": "https://github.com/Ark0N/claudeman#readme", "homepage": "https://github.com/Ark0N/Codeman#readme",
"files": [ "files": [
"dist", "dist",
"scripts/postinstall.js", "scripts/postinstall.js",
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2026 Claudeman Contributors Copyright (c) 2026 Codeman Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
+2 -2
View File
@@ -39,7 +39,7 @@ Server echoes 'h' ←───────────────────
## Origin ## Origin
This library was extracted from [Claudeman](https://github.com/Ark0N/Claudeman), a Claude Code session manager that runs 20+ autonomous AI agents over 24-hour sessions. The local echo system was built to make mobile and remote access feel instant, then battle-tested across thousands of hours of real usage. After 3 deep code audits, it was extracted into this standalone library with 78 tests covering every state transition. This library was extracted from [Codeman](https://github.com/Ark0N/Codeman), a Claude Code session manager that runs 20+ autonomous AI agents over 24-hour sessions. The local echo system was built to make mobile and remote access feel instant, then battle-tested across thousands of hours of real usage. After 3 deep code audits, it was extracted into this standalone library with 78 tests covering every state transition.
## Install ## Install
@@ -375,4 +375,4 @@ Overlay hides when scrolled up (`viewportY !== baseY`). Debounced re-render when
## License ## License
MIT — [Claudeman](https://github.com/Ark0N/Claudeman) Contributors MIT — [Codeman](https://github.com/Ark0N/Codeman) Contributors
+2 -2
View File
@@ -32,10 +32,10 @@
"addon" "addon"
], ],
"license": "MIT", "license": "MIT",
"homepage": "https://github.com/Ark0N/Claudeman/tree/master/packages/xterm-zerolag-input#readme", "homepage": "https://github.com/Ark0N/Codeman/tree/master/packages/xterm-zerolag-input#readme",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/Ark0N/Claudeman.git", "url": "git+https://github.com/Ark0N/Codeman.git",
"directory": "packages/xterm-zerolag-input" "directory": "packages/xterm-zerolag-input"
}, },
"devDependencies": { "devDependencies": {
+8 -8
View File
@@ -2,7 +2,7 @@
/** /**
* Browser Framework Comparison Script * Browser Framework Comparison Script
* *
* Compares Playwright, Puppeteer, and Agent-Browser for testing Claudeman web UI. * Compares Playwright, Puppeteer, and Agent-Browser for testing Codeman web UI.
* Run: node scripts/browser-comparison.mjs * Run: node scripts/browser-comparison.mjs
*/ */
@@ -88,7 +88,7 @@ async function main() {
const page = await playwrightBrowser.newPage(); const page = await playwrightBrowser.newPage();
await page.goto(`http://localhost:${PORTS.playwright}`); await page.goto(`http://localhost:${PORTS.playwright}`);
const title = await page.title(); const title = await page.title();
if (title !== 'Claudeman') throw new Error(`Expected Claudeman, got ${title}`); if (title !== 'Codeman') throw new Error(`Expected Codeman, got ${title}`);
await page.close(); await page.close();
}); });
@@ -98,7 +98,7 @@ async function main() {
await page.goto(`http://localhost:${PORTS.playwright}`); await page.goto(`http://localhost:${PORTS.playwright}`);
const logo = await page.locator('.header-brand .logo').textContent(); const logo = await page.locator('.header-brand .logo').textContent();
const version = await page.locator('#versionDisplay').textContent(); const version = await page.locator('#versionDisplay').textContent();
if (logo !== 'Claudeman') throw new Error('Logo mismatch'); if (logo !== 'Codeman') throw new Error('Logo mismatch');
if (!version.match(/v\d+/)) throw new Error('Version mismatch'); if (!version.match(/v\d+/)) throw new Error('Version mismatch');
await page.close(); await page.close();
}); });
@@ -149,7 +149,7 @@ async function main() {
const page = await puppeteerBrowser.newPage(); const page = await puppeteerBrowser.newPage();
await page.goto(`http://localhost:${PORTS.puppeteer}`); await page.goto(`http://localhost:${PORTS.puppeteer}`);
const title = await page.title(); const title = await page.title();
if (title !== 'Claudeman') throw new Error(`Expected Claudeman, got ${title}`); if (title !== 'Codeman') throw new Error(`Expected Codeman, got ${title}`);
await page.close(); await page.close();
}); });
@@ -159,7 +159,7 @@ async function main() {
await page.goto(`http://localhost:${PORTS.puppeteer}`); await page.goto(`http://localhost:${PORTS.puppeteer}`);
const logo = await page.$eval('.header-brand .logo', el => el.textContent); const logo = await page.$eval('.header-brand .logo', el => el.textContent);
const version = await page.$eval('#versionDisplay', el => el.textContent); const version = await page.$eval('#versionDisplay', el => el.textContent);
if (logo !== 'Claudeman') throw new Error('Logo mismatch'); if (logo !== 'Codeman') throw new Error('Logo mismatch');
if (!version.match(/v\d+/)) throw new Error('Version mismatch'); if (!version.match(/v\d+/)) throw new Error('Version mismatch');
await page.close(); await page.close();
}); });
@@ -202,7 +202,7 @@ async function main() {
agentBrowser(`open http://localhost:${PORTS.agentBrowser}`); agentBrowser(`open http://localhost:${PORTS.agentBrowser}`);
await new Promise(r => setTimeout(r, 2000)); await new Promise(r => setTimeout(r, 2000));
const title = agentBrowserJson('get title'); const title = agentBrowserJson('get title');
agentBrowserAvailable = title.title === 'Claudeman'; agentBrowserAvailable = title.title === 'Codeman';
console.log(' Browser launched'); console.log(' Browser launched');
// Test 1: Page load // Test 1: Page load
@@ -210,14 +210,14 @@ async function main() {
agentBrowser(`open http://localhost:${PORTS.agentBrowser}`); agentBrowser(`open http://localhost:${PORTS.agentBrowser}`);
await new Promise(r => setTimeout(r, 1000)); await new Promise(r => setTimeout(r, 1000));
const title = agentBrowserJson('get title'); const title = agentBrowserJson('get title');
if (title.title !== 'Claudeman') throw new Error(`Expected Claudeman, got ${title.title}`); if (title.title !== 'Codeman') throw new Error(`Expected Codeman, got ${title.title}`);
}); });
// Test 2: Element selection // Test 2: Element selection
await runBenchmark('element-selection', 'agent-browser', async () => { await runBenchmark('element-selection', 'agent-browser', async () => {
const logo = agentBrowserJson('get text ".header-brand .logo"'); const logo = agentBrowserJson('get text ".header-brand .logo"');
const version = agentBrowserJson('get text "#versionDisplay"'); const version = agentBrowserJson('get text "#versionDisplay"');
if (logo.text !== 'Claudeman') throw new Error('Logo mismatch'); if (logo.text !== 'Codeman') throw new Error('Logo mismatch');
if (!version.text.match(/v\d+/)) throw new Error('Version mismatch'); if (!version.text.match(/v\d+/)) throw new Error('Version mismatch');
}); });
+10 -10
View File
@@ -98,7 +98,7 @@ function makeSession(id, name, mode, status, extra = {}) {
id, id,
pid: status === 'idle' ? null : 12345 + Math.floor(Math.random() * 10000), pid: status === 'idle' ? null : 12345 + Math.floor(Math.random() * 10000),
status, status,
workingDir: '/home/arkon/claudeman-cases/testcase', workingDir: '/home/arkon/codeman-cases/testcase',
currentTaskId: null, currentTaskId: null,
createdAt: Date.now() - 3600000, createdAt: Date.now() - 3600000,
lastActivityAt: Date.now() - (status === 'idle' ? 60000 : 5000), lastActivityAt: Date.now() - (status === 'idle' ? 60000 : 5000),
@@ -336,7 +336,7 @@ const TERMINAL_INIT = [
'', '',
` ${RED}████${RST} ${BOLD}Claude Code${RST} v2.1.47`, ` ${RED}████${RST} ${BOLD}Claude Code${RST} v2.1.47`,
` ${RED}████${RST} ${GRY}Opus 4.6${RST} ${DIM}·${RST} ${GRY}Claude Max${RST}`, ` ${RED}████${RST} ${GRY}Opus 4.6${RST} ${DIM}·${RST} ${GRY}Claude Max${RST}`,
` ${RED}██${RST} ${RED}██${RST} ${GRY}~/claudeman-cases/testcase${RST}`, ` ${RED}██${RST} ${RED}██${RST} ${GRY}~/codeman-cases/testcase${RST}`,
'', '',
'', '',
`${GRY}❯ Try "how do I log an error?"${RST}`, `${GRY}❯ Try "how do I log an error?"${RST}`,
@@ -353,7 +353,7 @@ const TERMINAL_WORKING = [
`${GRN}●${RST} The codebase is essentially a template/scaffold with just a CLAUDE.md file and empty ${BOLD}src/${RST} directory. Since there's already a comprehensive`, `${GRN}●${RST} The codebase is essentially a template/scaffold with just a CLAUDE.md file and empty ${BOLD}src/${RST} directory. Since there's already a comprehensive`,
`CLAUDE.md file present, let me read it fully and suggest improvements based on the /init command requirements:`, `CLAUDE.md file present, let me read it fully and suggest improvements based on the /init command requirements:`,
'', '',
`${GRN}●${RST} ${BOLD}Read${RST}(/home/arkon/claudeman-cases/testcase/CLAUDE.md)`, `${GRN}●${RST} ${BOLD}Read${RST}(/home/arkon/codeman-cases/testcase/CLAUDE.md)`,
` ${GRY}░${RST} Read ${BOLD}407${RST} lines`, ` ${GRY}░${RST} Read ${BOLD}407${RST} lines`,
'', '',
`${GRN}●${RST} Now I understand the situation. This is a ${BOLD}template CLAUDE.md file${RST} designed for the Claude Code framework with the "Ralph Loop" autonomous work`, `${GRN}●${RST} Now I understand the situation. This is a ${BOLD}template CLAUDE.md file${RST} designed for the Claude Code framework with the "Ralph Loop" autonomous work`,
@@ -420,15 +420,15 @@ const TERMINAL_RALPH = [
const SUBAGENT_ACTIVITY = { const SUBAGENT_ACTIVITY = {
'agent-001': [ 'agent-001': [
{ type: 'tool', tool: 'Glob', input: { pattern: 'src/**/*.ts' }, timestamp: new Date().toISOString(), agentId: 'agent-001' }, { type: 'tool', tool: 'Glob', input: { pattern: 'src/**/*.ts' }, timestamp: new Date().toISOString(), agentId: 'agent-001' },
{ type: 'tool', tool: 'Read', input: { file_path: '/home/arkon/claudeman/src/web/server.ts' }, timestamp: new Date().toISOString(), agentId: 'agent-001' }, { type: 'tool', tool: 'Read', input: { file_path: '/home/arkon/codeman/src/web/server.ts' }, timestamp: new Date().toISOString(), agentId: 'agent-001' },
{ type: 'tool', tool: 'Grep', input: { pattern: 'app\\.get|app\\.post|app\\.put|app\\.delete', path: 'src/' }, timestamp: new Date().toISOString(), agentId: 'agent-001' }, { type: 'tool', tool: 'Grep', input: { pattern: 'app\\.get|app\\.post|app\\.put|app\\.delete', path: 'src/' }, timestamp: new Date().toISOString(), agentId: 'agent-001' },
{ type: 'tool', tool: 'Read', input: { file_path: '/home/arkon/claudeman/src/web/schemas.ts' }, timestamp: new Date().toISOString(), agentId: 'agent-001' }, { type: 'tool', tool: 'Read', input: { file_path: '/home/arkon/codeman/src/web/schemas.ts' }, timestamp: new Date().toISOString(), agentId: 'agent-001' },
{ type: 'message', role: 'assistant', text: 'Found 47 API endpoints across server.ts. Documenting REST paths...', timestamp: new Date().toISOString(), agentId: 'agent-001' }, { type: 'message', role: 'assistant', text: 'Found 47 API endpoints across server.ts. Documenting REST paths...', timestamp: new Date().toISOString(), agentId: 'agent-001' },
], ],
'agent-002': [ 'agent-002': [
{ type: 'tool', tool: 'Glob', input: { pattern: 'test/**/*.test.ts' }, timestamp: new Date().toISOString(), agentId: 'agent-002' }, { type: 'tool', tool: 'Glob', input: { pattern: 'test/**/*.test.ts' }, timestamp: new Date().toISOString(), agentId: 'agent-002' },
{ type: 'tool', tool: 'Read', input: { file_path: '/home/arkon/claudeman/test/respawn-test-utils.ts' }, timestamp: new Date().toISOString(), agentId: 'agent-002' }, { type: 'tool', tool: 'Read', input: { file_path: '/home/arkon/codeman/test/respawn-test-utils.ts' }, timestamp: new Date().toISOString(), agentId: 'agent-002' },
{ type: 'tool', tool: 'Read', input: { file_path: '/home/arkon/claudeman/vitest.config.ts' }, timestamp: new Date().toISOString(), agentId: 'agent-002' }, { type: 'tool', tool: 'Read', input: { file_path: '/home/arkon/codeman/vitest.config.ts' }, timestamp: new Date().toISOString(), agentId: 'agent-002' },
{ type: 'message', role: 'assistant', text: 'Analyzing test patterns: MockSession, unique ports, fileParallelism: false...', timestamp: new Date().toISOString(), agentId: 'agent-002' }, { type: 'message', role: 'assistant', text: 'Analyzing test patterns: MockSession, unique ports, fileParallelism: false...', timestamp: new Date().toISOString(), agentId: 'agent-002' },
], ],
}; };
@@ -436,9 +436,9 @@ const SUBAGENT_ACTIVITY = {
// Subagent spawn terminal content // Subagent spawn terminal content
const TERMINAL_SUBAGENT = [ const TERMINAL_SUBAGENT = [
'', '',
`${GRN}●${RST} Working on ${CYN}/home/arkon/claudeman-cases/testcase${RST} - I'll use the ${BOLD}Task tool${RST} to spawn parallel agents.`, `${GRN}●${RST} Working on ${CYN}/home/arkon/codeman-cases/testcase${RST} - I'll use the ${BOLD}Task tool${RST} to spawn parallel agents.`,
'', '',
`${GRN}●${RST} ${BOLD}Read${RST}(/home/arkon/claudeman-cases/testcase/CLAUDE.md)`, `${GRN}●${RST} ${BOLD}Read${RST}(/home/arkon/codeman-cases/testcase/CLAUDE.md)`,
` ${GRY}░${RST} Read ${BOLD}127${RST} lines ${GRY}│${RST} ${CYN}1.2KB${RST}`, ` ${GRY}░${RST} Read ${BOLD}127${RST} lines ${GRY}│${RST} ${CYN}1.2KB${RST}`,
'', '',
`${GRN}●${RST} ${BOLD}Bash${RST}(find . -name "*.ts" -not -path "*/node_modules/*" | head -20)`, `${GRN}●${RST} ${BOLD}Bash${RST}(find . -name "*.ts" -not -path "*/node_modules/*" | head -20)`,
@@ -864,7 +864,7 @@ async function captureMultiSessionMonitor(page) {
async function main() { async function main() {
console.log('='.repeat(60)); console.log('='.repeat(60));
console.log('Claudeman README Screenshot Capture'); console.log('Codeman README Screenshot Capture');
console.log('='.repeat(60)); console.log('='.repeat(60));
console.log(`Port: ${PORT} | Viewport: ${VIEWPORT.width}x${VIEWPORT.height}`); console.log(`Port: ${PORT} | Viewport: ${VIEWPORT.width}x${VIEWPORT.height}`);
console.log(''); console.log('');
+6 -6
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env node #!/usr/bin/env node
/** /**
* Capture a GIF of subagent spawning on the LIVE Claudeman server. * Capture a GIF of subagent spawning on the LIVE Codeman server.
* *
* Connects to the running server on port 3000, creates a test session, * Connects to the running server on port 3000, creates a test session,
* sends a prompt that spawns 3 parallel agents, and captures a tight * sends a prompt that spawns 3 parallel agents, and captures a tight
@@ -69,7 +69,7 @@ async function capture() {
try { try {
// Create case and seed with real source files // Create case and seed with real source files
console.log(`Creating case: ${CASE_NAME}`); console.log(`Creating case: ${CASE_NAME}`);
const caseDir = join(process.env.HOME, 'claudeman-cases', CASE_NAME); const caseDir = join(process.env.HOME, 'codeman-cases', CASE_NAME);
const caseSrc = join(caseDir, 'src'); const caseSrc = join(caseDir, 'src');
const caseTest = join(caseDir, 'test'); const caseTest = join(caseDir, 'test');
mkdirSync(caseSrc, { recursive: true }); mkdirSync(caseSrc, { recursive: true });
@@ -123,7 +123,7 @@ async function capture() {
// Configure settings: enable subagents, disable Files pane // Configure settings: enable subagents, disable Files pane
await setupPage.evaluate(() => { await setupPage.evaluate(() => {
const existing = JSON.parse(localStorage.getItem('claudeman-app-settings') || '{}'); const existing = JSON.parse(localStorage.getItem('codeman-app-settings') || '{}');
Object.assign(existing, { Object.assign(existing, {
showSubagents: true, showSubagents: true,
subagentTrackingEnabled: true, subagentTrackingEnabled: true,
@@ -132,7 +132,7 @@ async function capture() {
showProjectInsights: false, showProjectInsights: false,
showFileBrowser: false, showFileBrowser: false,
}); });
localStorage.setItem('claudeman-app-settings', JSON.stringify(existing)); localStorage.setItem('codeman-app-settings', JSON.stringify(existing));
}); });
await setupPage.reload({ waitUntil: 'domcontentloaded' }); await setupPage.reload({ waitUntil: 'domcontentloaded' });
await sleep(1000); await sleep(1000);
@@ -227,7 +227,7 @@ async function capture() {
// Re-apply settings (new context = fresh localStorage) // Re-apply settings (new context = fresh localStorage)
await recordPage.evaluate(() => { await recordPage.evaluate(() => {
const existing = JSON.parse(localStorage.getItem('claudeman-app-settings') || '{}'); const existing = JSON.parse(localStorage.getItem('codeman-app-settings') || '{}');
Object.assign(existing, { Object.assign(existing, {
showSubagents: true, showSubagents: true,
subagentTrackingEnabled: true, subagentTrackingEnabled: true,
@@ -236,7 +236,7 @@ async function capture() {
showProjectInsights: false, showProjectInsights: false,
showFileBrowser: false, showFileBrowser: false,
}); });
localStorage.setItem('claudeman-app-settings', JSON.stringify(existing)); localStorage.setItem('codeman-app-settings', JSON.stringify(existing));
}); });
await recordPage.reload({ waitUntil: 'domcontentloaded' }); await recordPage.reload({ waitUntil: 'domcontentloaded' });
await sleep(1000); await sleep(1000);
+8 -8
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env node #!/usr/bin/env node
/** /**
* Capture screenshots and GIFs of Claudeman's subagent visualization * Capture screenshots and GIFs of Codeman's subagent visualization
* *
* Uses real Claude sessions that spawn actual subagents via the Task tool. * Uses real Claude sessions that spawn actual subagents via the Task tool.
* *
@@ -23,7 +23,7 @@ import { mkdirSync, existsSync, unlinkSync, rmSync } from 'fs';
const __dirname = dirname(fileURLToPath(import.meta.url)); const __dirname = dirname(fileURLToPath(import.meta.url));
const PROJECT_ROOT = join(__dirname, '..'); const PROJECT_ROOT = join(__dirname, '..');
const OUTPUT_DIR = join(PROJECT_ROOT, 'docs', 'images'); const OUTPUT_DIR = join(PROJECT_ROOT, 'docs', 'images');
const CASES_DIR = join(process.env.HOME, 'claudeman-cases'); const CASES_DIR = join(process.env.HOME, 'codeman-cases');
// Configuration // Configuration
const PORT = 3198; // Unique port for capture script (avoid 3199 conflicts) const PORT = 3198; // Unique port for capture script (avoid 3199 conflicts)
@@ -58,10 +58,10 @@ let serverProcess = null;
let browser = null; let browser = null;
/** /**
* Start the Claudeman web server * Start the Codeman web server
*/ */
async function startServer() { async function startServer() {
console.log(`Starting Claudeman server on port ${PORT}...`); console.log(`Starting Codeman server on port ${PORT}...`);
serverProcess = spawn('npx', ['tsx', 'src/index.ts', 'web', '--port', String(PORT)], { serverProcess = spawn('npx', ['tsx', 'src/index.ts', 'web', '--port', String(PORT)], {
cwd: PROJECT_ROOT, cwd: PROJECT_ROOT,
@@ -222,7 +222,7 @@ async function configureSettings(page) {
subagentActiveTabOnly: false, // Show all subagents regardless of active tab subagentActiveTabOnly: false, // Show all subagents regardless of active tab
showMonitor: true, showMonitor: true,
}; };
localStorage.setItem('claudeman-app-settings', JSON.stringify(settings)); localStorage.setItem('codeman-app-settings', JSON.stringify(settings));
}); });
await page.reload({ waitUntil: 'domcontentloaded' }); await page.reload({ waitUntil: 'domcontentloaded' });
await sleep(500); await sleep(500);
@@ -292,7 +292,7 @@ function convertToGif(inputPath, outputPath) {
*/ */
async function capture() { async function capture() {
console.log('='.repeat(60)); console.log('='.repeat(60));
console.log('Claudeman Subagent Screenshot Capture'); console.log('Codeman Subagent Screenshot Capture');
console.log('='.repeat(60)); console.log('='.repeat(60));
console.log(`Output directory: ${OUTPUT_DIR}`); console.log(`Output directory: ${OUTPUT_DIR}`);
console.log(`Case name: ${CASE_NAME}`); console.log(`Case name: ${CASE_NAME}`);
@@ -323,8 +323,8 @@ async function capture() {
const page = await context.newPage(); const page = await context.newPage();
page.setDefaultTimeout(30000); page.setDefaultTimeout(30000);
// Navigate to Claudeman // Navigate to Codeman
console.log('Loading Claudeman...'); console.log('Loading Codeman...');
await page.goto(BASE_URL, { waitUntil: 'domcontentloaded' }); await page.goto(BASE_URL, { waitUntil: 'domcontentloaded' });
await sleep(1000); await sleep(1000);
@@ -1,10 +1,10 @@
[Unit] [Unit]
Description=Claudeman Web Server Description=Codeman Web Server
After=network.target After=network.target
[Service] [Service]
Type=simple Type=simple
WorkingDirectory=/home/arkon/default/claudeman WorkingDirectory=/home/arkon/default/codeman
ExecStart=/usr/bin/node dist/index.js web --https ExecStart=/usr/bin/node dist/index.js web --https
Restart=always Restart=always
RestartSec=5 RestartSec=5
@@ -15,7 +15,7 @@ Environment=HOME=/home/arkon
# Logging # Logging
StandardOutput=journal StandardOutput=journal
StandardError=journal StandardError=journal
SyslogIdentifier=claudeman SyslogIdentifier=codeman
# Resource limits # Resource limits
LimitNOFILE=65536 LimitNOFILE=65536
+1 -1
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env node #!/usr/bin/env node
/** /**
* Take mobile screenshots of Claudeman using Playwright * Take mobile screenshots of Codeman using Playwright
* Emulates iPhone 15 Pro (closest to iPhone 17 Pro) * Emulates iPhone 15 Pro (closest to iPhone 17 Pro)
* Includes keyboard simulation * Includes keyboard simulation
*/ */
+3 -3
View File
@@ -4,8 +4,8 @@
SESSION_ID="${1:-5ecdf859-3605-42d8-b0a2-f8f0a1b5ff98}" SESSION_ID="${1:-5ecdf859-3605-42d8-b0a2-f8f0a1b5ff98}"
DURATION_HOURS="${2:-12}" DURATION_HOURS="${2:-12}"
ISSUES_FILE="/home/arkon/default/claudeman/respawn-monitor-issues.md" ISSUES_FILE="/home/arkon/default/codeman/respawn-monitor-issues.md"
FIXES_FILE="/home/arkon/default/claudeman/respawn-monitor-fixes.md" FIXES_FILE="/home/arkon/default/codeman/respawn-monitor-fixes.md"
API_URL="https://localhost:3000" API_URL="https://localhost:3000"
POLL_INTERVAL=60 # seconds POLL_INTERVAL=60 # seconds
@@ -54,7 +54,7 @@ while [ $(date +%s) -lt $END_TIME ]; do
if [ -z "$RESP" ]; then if [ -z "$RESP" ]; then
log_issue "ERROR" "API not responding - server may be down" log_issue "ERROR" "API not responding - server may be down"
add_fix "API not responding" "Check if claudeman-web service is running:\n\`\`\`bash\nsystemctl --user status claudeman-web\nsystemctl --user restart claudeman-web\n\`\`\`" add_fix "API not responding" "Check if codeman-web service is running:\n\`\`\`bash\nsystemctl --user status codeman-web\nsystemctl --user restart codeman-web\n\`\`\`"
sleep $POLL_INTERVAL sleep $POLL_INTERVAL
continue continue
fi fi
+4 -4
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env node #!/usr/bin/env node
/** /**
* Claudeman postinstall verification script * Codeman postinstall verification script
* Runs after `npm install` to check environment readiness * Runs after `npm install` to check environment readiness
*/ */
@@ -94,7 +94,7 @@ function getTmuxInstallInstructions() {
// Main Checks // Main Checks
// ============================================================================ // ============================================================================
console.log(colors.bold('Claudeman postinstall check...')); console.log(colors.bold('Codeman postinstall check...'));
console.log(''); console.log('');
let hasWarnings = false; let hasWarnings = false;
@@ -259,11 +259,11 @@ if (hasErrors) {
console.log(colors.bold('Next steps:')); console.log(colors.bold('Next steps:'));
if (isGlobalInstall) { if (isGlobalInstall) {
console.log(colors.dim(' 1. Start: ') + colors.cyan('claudeman web')); console.log(colors.dim(' 1. Start: ') + colors.cyan('codeman web'));
console.log(colors.dim(' 2. Open: ') + colors.cyan('http://localhost:3000')); console.log(colors.dim(' 2. Open: ') + colors.cyan('http://localhost:3000'));
} else { } else {
console.log(colors.dim(' 1. Build: ') + colors.cyan('npm run build')); console.log(colors.dim(' 1. Build: ') + colors.cyan('npm run build'));
console.log(colors.dim(' 2. Start: ') + colors.cyan('claudeman web')); console.log(colors.dim(' 2. Start: ') + colors.cyan('codeman web'));
console.log(colors.dim(' 3. Open: ') + colors.cyan('http://localhost:3000')); console.log(colors.dim(' 3. Open: ') + colors.cyan('http://localhost:3000'));
} }
+4 -4
View File
@@ -8,7 +8,7 @@ import { join } from 'node:path';
import { homedir } from 'node:os'; import { homedir } from 'node:os';
const BASE_URL = 'https://localhost:3000'; const BASE_URL = 'https://localhost:3000';
const CASE_NAME = 'claudeman-ios'; const CASE_NAME = 'codeman-ios';
async function main() { async function main() {
console.log('🎬 Starting Ralph Loop Wizard demo...\n'); console.log('🎬 Starting Ralph Loop Wizard demo...\n');
@@ -25,7 +25,7 @@ async function main() {
}); });
const page = await context.newPage(); const page = await context.newPage();
console.log('📱 Opening Claudeman web UI...'); console.log('📱 Opening Codeman web UI...');
await page.goto(BASE_URL); await page.goto(BASE_URL);
await page.waitForLoadState('networkidle'); await page.waitForLoadState('networkidle');
@@ -41,8 +41,8 @@ async function main() {
await page.selectOption('#ralphCaseSelect', CASE_NAME); await page.selectOption('#ralphCaseSelect', CASE_NAME);
// Load initprompt.md if it exists // Load initprompt.md if it exists
const initPromptPath = join(homedir(), 'claudeman-cases', CASE_NAME, 'initprompt.md'); const initPromptPath = join(homedir(), 'codeman-cases', CASE_NAME, 'initprompt.md');
let taskDescription = 'Build an awesome iOS app for Claudeman!'; let taskDescription = 'Build an awesome iOS app for Codeman!';
if (existsSync(initPromptPath)) { if (existsSync(initPromptPath)) {
taskDescription = readFileSync(initPromptPath, 'utf-8').trim(); taskDescription = readFileSync(initPromptPath, 'utf-8').trim();
console.log(`📝 Loaded initprompt.md (${taskDescription.length} chars)`); console.log(`📝 Loaded initprompt.md (${taskDescription.length} chars)`);
+5 -5
View File
@@ -10,8 +10,8 @@ import { join } from 'node:path';
import { homedir } from 'node:os'; import { homedir } from 'node:os';
const BASE_URL = 'https://localhost:3000'; const BASE_URL = 'https://localhost:3000';
const CASE_NAME = 'claudeman-ios'; const CASE_NAME = 'codeman-ios';
const CASE_PATH = join(homedir(), 'claudeman-cases', CASE_NAME); const CASE_PATH = join(homedir(), 'codeman-cases', CASE_NAME);
async function main() { async function main() {
// Read initprompt.md // Read initprompt.md
@@ -49,7 +49,7 @@ async function main() {
page.on('pageerror', err => console.log(`[Browser Error] ${err.message}`)); page.on('pageerror', err => console.log(`[Browser Error] ${err.message}`));
try { try {
console.log('[1/10] Navigating to Claudeman...'); console.log('[1/10] Navigating to Codeman...');
await page.goto(BASE_URL, { timeout: 10000 }); await page.goto(BASE_URL, { timeout: 10000 });
console.log('[2/10] Clicking Ralph Loop button (.btn-ralph)...'); console.log('[2/10] Clicking Ralph Loop button (.btn-ralph)...');
@@ -122,7 +122,7 @@ async function main() {
}); });
session = sessions session = sessions
.filter(s => s.workingDir?.includes('claudeman-ios')) .filter(s => s.workingDir?.includes('codeman-ios'))
.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt))[0]; .sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt))[0];
if (session) { if (session) {
@@ -151,7 +151,7 @@ async function main() {
console.log('SUCCESS! Ralph Loop started.'); console.log('SUCCESS! Ralph Loop started.');
console.log('========================================'); console.log('========================================');
console.log(`Session ID: ${session?.id}`); console.log(`Session ID: ${session?.id}`);
console.log(`Screen: claudeman-${session?.id?.slice(0, 8)}`); console.log(`Screen: codeman-${session?.id?.slice(0, 8)}`);
console.log(`Status: ${session?.status}`); console.log(`Status: ${session?.status}`);
console.log(''); console.log('');
console.log('Session is now running. View at: https://localhost:3000'); console.log('Session is now running. View at: https://localhost:3000');
+6 -6
View File
@@ -8,7 +8,7 @@ import { join } from 'node:path';
import { homedir } from 'node:os'; import { homedir } from 'node:os';
const BASE_URL = 'https://localhost:3000'; const BASE_URL = 'https://localhost:3000';
const CASE_NAME = 'claudeman-ios'; const CASE_NAME = 'codeman-ios';
async function main() { async function main() {
console.log('Starting Ralph Loop via wizard on production server...\n'); console.log('Starting Ralph Loop via wizard on production server...\n');
@@ -21,7 +21,7 @@ async function main() {
const context = await browser.newContext({ ignoreHTTPSErrors: true }); const context = await browser.newContext({ ignoreHTTPSErrors: true });
const page = await context.newPage(); const page = await context.newPage();
console.log('1. Opening Claudeman web UI...'); console.log('1. Opening Codeman web UI...');
await page.goto(BASE_URL); await page.goto(BASE_URL);
await page.waitForLoadState('networkidle'); await page.waitForLoadState('networkidle');
@@ -36,8 +36,8 @@ async function main() {
await page.selectOption('#ralphCaseSelect', CASE_NAME); await page.selectOption('#ralphCaseSelect', CASE_NAME);
// Load initprompt.md // Load initprompt.md
const initPromptPath = join(homedir(), 'claudeman-cases', CASE_NAME, 'initprompt.md'); const initPromptPath = join(homedir(), 'codeman-cases', CASE_NAME, 'initprompt.md');
let taskDescription = 'Build an awesome iOS app for Claudeman!'; let taskDescription = 'Build an awesome iOS app for Codeman!';
if (existsSync(initPromptPath)) { if (existsSync(initPromptPath)) {
taskDescription = readFileSync(initPromptPath, 'utf-8').trim(); taskDescription = readFileSync(initPromptPath, 'utf-8').trim();
console.log(`5. Loaded initprompt.md (${taskDescription.length} chars)`); console.log(`5. Loaded initprompt.md (${taskDescription.length} chars)`);
@@ -87,13 +87,13 @@ async function main() {
return res.json(); return res.json();
}); });
const iosSession = sessions.find(s => s.workingDir?.includes('claudeman-ios')); const iosSession = sessions.find(s => s.workingDir?.includes('codeman-ios'));
console.log('\n========================================'); console.log('\n========================================');
console.log('SUCCESS! Ralph Loop started.'); console.log('SUCCESS! Ralph Loop started.');
console.log('========================================'); console.log('========================================');
console.log(`Session ID: ${iosSession?.id}`); console.log(`Session ID: ${iosSession?.id}`);
console.log(`Screen: claudeman-${iosSession?.id?.slice(0, 8)}`); console.log(`Screen: codeman-${iosSession?.id?.slice(0, 8)}`);
console.log(`Working Dir: ${iosSession?.workingDir}`); console.log(`Working Dir: ${iosSession?.workingDir}`);
console.log('\nThe session is now running on your production server.'); console.log('\nThe session is now running on your production server.');
console.log('View it at: https://localhost:3000'); console.log('View it at: https://localhost:3000');
+12 -12
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env node #!/usr/bin/env node
/** /**
* Browser test for clickable file links in Claudeman terminal * Browser test for clickable file links in Codeman terminal
* Uses Playwright directly to ignore HTTPS errors * Uses Playwright directly to ignore HTTPS errors
*/ */
@@ -33,8 +33,8 @@ async function main() {
const page = await context.newPage(); const page = await context.newPage();
try { try {
// Navigate to Claudeman // Navigate to Codeman
console.log('Navigating to Claudeman...'); console.log('Navigating to Codeman...');
await page.goto(BASE_URL, { waitUntil: 'networkidle', timeout: 30000 }); await page.goto(BASE_URL, { waitUntil: 'networkidle', timeout: 30000 });
console.log('Page loaded: ' + await page.title()); console.log('Page loaded: ' + await page.title());
@@ -63,9 +63,9 @@ async function main() {
await page.waitForTimeout(2000); await page.waitForTimeout(2000);
} }
// Get active session (use claudeman session if exists) // Get active session (use codeman session if exists)
const claudemanSession = sessions.find(s => s.workingDir?.includes('claudeman')); const codemanSession = sessions.find(s => s.workingDir?.includes('codeman'));
const sessionId = claudemanSession?.id || sessions[0]?.id; const sessionId = codemanSession?.id || sessions[0]?.id;
console.log(`Using session: ${sessionId}\n`); console.log(`Using session: ${sessionId}\n`);
// Send a command that outputs file paths // Send a command that outputs file paths
@@ -81,8 +81,8 @@ async function main() {
await page.waitForTimeout(2000); await page.waitForTimeout(2000);
// Take screenshot // Take screenshot
await page.screenshot({ path: '/tmp/claudeman-link-test-1.png' }); await page.screenshot({ path: '/tmp/codeman-link-test-1.png' });
console.log('Screenshot saved: /tmp/claudeman-link-test-1.png\n'); console.log('Screenshot saved: /tmp/codeman-link-test-1.png\n');
// Check if terminal contains our file path // Check if terminal contains our file path
const terminalContent = await page.evaluate(() => { const terminalContent = await page.evaluate(() => {
@@ -132,8 +132,8 @@ async function main() {
console.log('Log viewer windows:', logViewerCount); console.log('Log viewer windows:', logViewerCount);
// Take final screenshot // Take final screenshot
await page.screenshot({ path: '/tmp/claudeman-link-test-2.png' }); await page.screenshot({ path: '/tmp/codeman-link-test-2.png' });
console.log('\nFinal screenshot: /tmp/claudeman-link-test-2.png'); console.log('\nFinal screenshot: /tmp/codeman-link-test-2.png');
// Test the link provider registration // Test the link provider registration
console.log('\n--- Link Provider Debug ---'); console.log('\n--- Link Provider Debug ---');
@@ -155,8 +155,8 @@ async function main() {
} catch (error) { } catch (error) {
console.error('Error:', error.message); console.error('Error:', error.message);
await page.screenshot({ path: '/tmp/claudeman-link-test-error.png' }); await page.screenshot({ path: '/tmp/codeman-link-test-error.png' });
console.log('Error screenshot saved to /tmp/claudeman-link-test-error.png'); console.log('Error screenshot saved to /tmp/codeman-link-test-error.png');
} finally { } finally {
await browser.close(); await browser.close();
console.log('\nBrowser closed'); console.log('\nBrowser closed');
+2 -2
View File
@@ -1,8 +1,8 @@
#!/bin/bash #!/bin/bash
# Test script for clickable file links in Claudeman terminal # Test script for clickable file links in Codeman terminal
# Creates a log file and continuously writes timestamped data # Creates a log file and continuously writes timestamped data
LOGFILE="/tmp/claudeman-test-$(date +%s).log" LOGFILE="/tmp/codeman-test-$(date +%s).log"
echo "Created log file: $LOGFILE" echo "Created log file: $LOGFILE"
echo "" echo ""
+29 -29
View File
@@ -1,6 +1,6 @@
#!/bin/bash #!/bin/bash
# ============================================================================ # ============================================================================
# Claudeman Sessions - Mobile-friendly Tmux Session Chooser # Codeman Sessions - Mobile-friendly Tmux Session Chooser
# Optimized for iPhone/Termius (portrait ~45 chars, landscape ~95 chars) # Optimized for iPhone/Termius (portrait ~45 chars, landscape ~95 chars)
# ============================================================================ # ============================================================================
# #
@@ -8,7 +8,7 @@
# - Single-digit selection (1-9) for fast thumb typing # - Single-digit selection (1-9) for fast thumb typing
# - Compact display, no wasted space # - Compact display, no wasted space
# - Color-coded status for quick scanning # - Color-coded status for quick scanning
# - Names pulled from Claudeman state.json # - Names pulled from Codeman state.json
# - Minimal keystrokes to attach # - Minimal keystrokes to attach
# #
# Usage: # Usage:
@@ -29,8 +29,8 @@ set -e
# Configuration # Configuration
# ============================================================================ # ============================================================================
CLAUDEMAN_STATE="$HOME/.claudeman/state.json" CODEMAN_STATE="$HOME/.codeman/state.json"
CLAUDEMAN_SESSIONS="$HOME/.claudeman/mux-sessions.json" CODEMAN_SESSIONS="$HOME/.codeman/mux-sessions.json"
# iPhone 17 Pro portrait width (conservative) # iPhone 17 Pro portrait width (conservative)
@@ -106,22 +106,22 @@ truncate() {
find_full_session_id() { find_full_session_id() {
local short_id="$1" local short_id="$1"
if [ -f "$CLAUDEMAN_STATE" ]; then if [ -f "$CODEMAN_STATE" ]; then
local full_id local full_id
full_id=$(jq -r --arg short "$short_id" ' full_id=$(jq -r --arg short "$short_id" '
.sessions | keys[] | select(startswith($short)) .sessions | keys[] | select(startswith($short))
' "$CLAUDEMAN_STATE" 2>/dev/null | head -1) ' "$CODEMAN_STATE" 2>/dev/null | head -1)
if [ -n "$full_id" ]; then if [ -n "$full_id" ]; then
echo "$full_id" echo "$full_id"
return return
fi fi
fi fi
if [ -f "$CLAUDEMAN_SESSIONS" ]; then if [ -f "$CODEMAN_SESSIONS" ]; then
local full_id local full_id
full_id=$(jq -r --arg short "$short_id" ' full_id=$(jq -r --arg short "$short_id" '
.[] | select(.sessionId | startswith($short)) | .sessionId .[] | select(.sessionId | startswith($short)) | .sessionId
' "$CLAUDEMAN_SESSIONS" 2>/dev/null | head -1) ' "$CODEMAN_SESSIONS" 2>/dev/null | head -1)
if [ -n "$full_id" ]; then if [ -n "$full_id" ]; then
echo "$full_id" echo "$full_id"
return return
@@ -136,22 +136,22 @@ get_session_name() {
local name="" local name=""
local workdir="" local workdir=""
if [ -f "$CLAUDEMAN_SESSIONS" ]; then if [ -f "$CODEMAN_SESSIONS" ]; then
local result local result
result=$(jq -r --arg id "$session_id" ' result=$(jq -r --arg id "$session_id" '
.[] | select(.sessionId | startswith($id)) | "\(.name // "")\t\(.workingDir // "")" .[] | select(.sessionId | startswith($id)) | "\(.name // "")\t\(.workingDir // "")"
' "$CLAUDEMAN_SESSIONS" 2>/dev/null | head -1) ' "$CODEMAN_SESSIONS" 2>/dev/null | head -1)
if [ -n "$result" ]; then if [ -n "$result" ]; then
name="${result%% *}" name="${result%% *}"
workdir="${result#* }" workdir="${result#* }"
fi fi
fi fi
if [ -z "$name" ] && [ -f "$CLAUDEMAN_STATE" ]; then if [ -z "$name" ] && [ -f "$CODEMAN_STATE" ]; then
local result local result
result=$(jq -r --arg id "$session_id" ' result=$(jq -r --arg id "$session_id" '
.sessions | to_entries[] | select(.key | startswith($id)) | "\(.value.name // "")\t\(.value.workingDir // "")" .sessions | to_entries[] | select(.key | startswith($id)) | "\(.value.name // "")\t\(.value.workingDir // "")"
' "$CLAUDEMAN_STATE" 2>/dev/null | head -1) ' "$CODEMAN_STATE" 2>/dev/null | head -1)
if [ -n "$result" ]; then if [ -n "$result" ]; then
name="${result%% *}" name="${result%% *}"
[ -z "$workdir" ] && workdir="${result#* }" [ -z "$workdir" ] && workdir="${result#* }"
@@ -174,22 +174,22 @@ get_session_name() {
get_working_dir() { get_working_dir() {
local session_id="$1" local session_id="$1"
if [ -f "$CLAUDEMAN_SESSIONS" ]; then if [ -f "$CODEMAN_SESSIONS" ]; then
local dir local dir
dir=$(jq -r --arg id "$session_id" ' dir=$(jq -r --arg id "$session_id" '
.[] | select(.sessionId | startswith($id)) | .workingDir // empty .[] | select(.sessionId | startswith($id)) | .workingDir // empty
' "$CLAUDEMAN_SESSIONS" 2>/dev/null | head -1) ' "$CODEMAN_SESSIONS" 2>/dev/null | head -1)
if [ -n "$dir" ] && [ "$dir" != "null" ]; then if [ -n "$dir" ] && [ "$dir" != "null" ]; then
echo "${dir/#$HOME/~}" echo "${dir/#$HOME/~}"
return return
fi fi
fi fi
if [ -f "$CLAUDEMAN_STATE" ]; then if [ -f "$CODEMAN_STATE" ]; then
local dir local dir
dir=$(jq -r --arg id "$session_id" ' dir=$(jq -r --arg id "$session_id" '
.sessions | to_entries[] | select(.key | startswith($id)) | .value.workingDir // empty .sessions | to_entries[] | select(.key | startswith($id)) | .value.workingDir // empty
' "$CLAUDEMAN_STATE" 2>/dev/null | head -1) ' "$CODEMAN_STATE" 2>/dev/null | head -1)
if [ -n "$dir" ] && [ "$dir" != "null" ]; then if [ -n "$dir" ] && [ "$dir" != "null" ]; then
echo "${dir/#$HOME/~}" echo "${dir/#$HOME/~}"
return return
@@ -201,12 +201,12 @@ get_working_dir() {
get_tokens() { get_tokens() {
local session_id="$1" local session_id="$1"
if [ -f "$CLAUDEMAN_STATE" ]; then if [ -f "$CODEMAN_STATE" ]; then
local tokens local tokens
tokens=$(jq -r --arg id "$session_id" ' tokens=$(jq -r --arg id "$session_id" '
.sessions | to_entries[] | select(.key | startswith($id)) | .sessions | to_entries[] | select(.key | startswith($id)) |
((.value.inputTokens // 0) + (.value.outputTokens // 0)) ((.value.inputTokens // 0) + (.value.outputTokens // 0))
' "$CLAUDEMAN_STATE" 2>/dev/null | head -1) ' "$CODEMAN_STATE" 2>/dev/null | head -1)
if [ -n "$tokens" ] && [ "$tokens" != "null" ] && [ "$tokens" -gt 0 ] 2>/dev/null; then if [ -n "$tokens" ] && [ "$tokens" != "null" ] && [ "$tokens" -gt 0 ] 2>/dev/null; then
if [ "$tokens" -gt 1000 ]; then if [ "$tokens" -gt 1000 ]; then
@@ -223,11 +223,11 @@ get_tokens() {
get_respawn_status() { get_respawn_status() {
local session_id="$1" local session_id="$1"
if [ -f "$CLAUDEMAN_SESSIONS" ]; then if [ -f "$CODEMAN_SESSIONS" ]; then
local respawn_enabled local respawn_enabled
respawn_enabled=$(jq -r --arg id "$session_id" ' respawn_enabled=$(jq -r --arg id "$session_id" '
.[] | select(.sessionId | startswith($id)) | .respawnConfig.enabled // false .[] | select(.sessionId | startswith($id)) | .respawnConfig.enabled // false
' "$CLAUDEMAN_SESSIONS" 2>/dev/null | head -1) ' "$CODEMAN_SESSIONS" 2>/dev/null | head -1)
if [ "$respawn_enabled" = "true" ]; then if [ "$respawn_enabled" = "true" ]; then
echo "R" echo "R"
@@ -273,8 +273,8 @@ parse_sessions() {
while IFS= read -r line; do while IFS= read -r line; do
local session_name="${line%%:*}" local session_name="${line%%:*}"
# Only show claudeman sessions # Only show codeman sessions
if [[ "$session_name" != claudeman-* ]]; then if [[ "$session_name" != codeman-* ]]; then
continue continue
fi fi
@@ -292,9 +292,9 @@ parse_sessions() {
MUX_NAMES+=("$session_name") MUX_NAMES+=("$session_name")
SESSION_STATES+=("$state") SESSION_STATES+=("$state")
# Extract session ID from claudeman session name # Extract session ID from codeman session name
local session_id="" local session_id=""
local cm_regex='^claudeman-(.+)$' local cm_regex='^codeman-(.+)$'
if [[ "$session_name" =~ $cm_regex ]]; then if [[ "$session_name" =~ $cm_regex ]]; then
session_id="${BASH_REMATCH[1]}" session_id="${BASH_REMATCH[1]}"
fi fi
@@ -327,7 +327,7 @@ clear_screen() {
print_header() { print_header() {
local count=${#SESSION_PIDS[@]} local count=${#SESSION_PIDS[@]}
echo -e "${B}${CYAN}Claudeman Sessions${R} ${D}($count)${R}" echo -e "${B}${CYAN}Codeman Sessions${R} ${D}($count)${R}"
echo -e "${D}$(printf '%.0s─' {1..32})${R}" echo -e "${D}$(printf '%.0s─' {1..32})${R}"
} }
@@ -395,13 +395,13 @@ print_footer() {
print_no_sessions() { print_no_sessions() {
clear_screen clear_screen
echo -e "${B}${CYAN}Claudeman Sessions${R}" echo -e "${B}${CYAN}Codeman Sessions${R}"
echo -e "${D}$(printf '%.0s─' {1..32})${R}" echo -e "${D}$(printf '%.0s─' {1..32})${R}"
echo "" echo ""
echo -e " ${YELLOW}No tmux sessions found${R}" echo -e " ${YELLOW}No tmux sessions found${R}"
echo "" echo ""
echo -e " ${D}Start one with:${R}" echo -e " ${D}Start one with:${R}"
echo -e " ${WHITE}claudeman web${R}" echo -e " ${WHITE}codeman web${R}"
echo "" echo ""
echo -e "${D}$(printf '%.0s─' {1..32})${R}" echo -e "${D}$(printf '%.0s─' {1..32})${R}"
echo -e " ${GRAY}[${WHITE}r${GRAY}]efresh [${WHITE}q${GRAY}]uit${R}" echo -e " ${GRAY}[${WHITE}r${GRAY}]efresh [${WHITE}q${GRAY}]uit${R}"
@@ -584,7 +584,7 @@ quick_attach() {
show_help() { show_help() {
cat << 'EOF' cat << 'EOF'
Claudeman Sessions - Mobile-friendly Tmux Session Chooser Codeman Sessions - Mobile-friendly Tmux Session Chooser
USAGE: USAGE:
sc Interactive chooser sc Interactive chooser
@@ -607,7 +607,7 @@ INDICATORS:
TIPS: TIPS:
- Detach from tmux: Ctrl+B D - Detach from tmux: Ctrl+B D
- Session names from Claudeman state - Session names from Codeman state
- Optimized for Termius/iPhone - Optimized for Termius/iPhone
EOF EOF
+8 -8
View File
@@ -1,8 +1,8 @@
#!/bin/bash #!/bin/bash
# #
# Claudeman Tmux Session Manager # Codeman Tmux Session Manager
# Interactive tool with arrow key navigation # Interactive tool with arrow key navigation
# Reads from ~/.claudeman/mux-sessions.json # Reads from ~/.codeman/mux-sessions.json
# #
# Colors # Colors
@@ -17,8 +17,8 @@ DIM='\033[2m'
NC='\033[0m' NC='\033[0m'
REVERSE='\033[7m' REVERSE='\033[7m'
# Use the same path as claudeman (src/tmux-manager.ts) # Use the same path as codeman (src/tmux-manager.ts)
SESSIONS_FILE="${HOME}/.claudeman/mux-sessions.json" SESSIONS_FILE="${HOME}/.codeman/mux-sessions.json"
# Cached data # Cached data
@@ -133,7 +133,7 @@ kill_session() {
# Draw header (only once) # Draw header (only once)
draw_header() { draw_header() {
echo -e "${BOLD}${CYAN}╔══════════════════════════════════════════════════════════╗${NC}" echo -e "${BOLD}${CYAN}╔══════════════════════════════════════════════════════════╗${NC}"
echo -e "${BOLD}${CYAN}║ Claudeman Tmux Session Manager ║${NC}" echo -e "${BOLD}${CYAN}║ Codeman Tmux Session Manager ║${NC}"
echo -e "${BOLD}${CYAN}║ ${DIM}Press q or Esc to exit${CYAN} ║${NC}" echo -e "${BOLD}${CYAN}║ ${DIM}Press q or Esc to exit${CYAN} ║${NC}"
echo -e "${BOLD}${CYAN}╚══════════════════════════════════════════════════════════╝${NC}" echo -e "${BOLD}${CYAN}╚══════════════════════════════════════════════════════════╝${NC}"
echo "" echo ""
@@ -401,11 +401,11 @@ quick_list() {
local now=$(date +%s) local now=$(date +%s)
if [[ $CACHED_COUNT -eq 0 ]]; then if [[ $CACHED_COUNT -eq 0 ]]; then
echo "No claudeman sessions found." echo "No codeman sessions found."
exit 0 exit 0
fi fi
echo "Claudeman Sessions ($SESSIONS_FILE):" echo "Codeman Sessions ($SESSIONS_FILE):"
echo "" echo ""
printf "%-4s %-22s %-14s %-10s %-8s\n" "#" "NAME" "RUNNING" "STATUS" "MODE" printf "%-4s %-22s %-14s %-10s %-8s\n" "#" "NAME" "RUNNING" "STATUS" "MODE"
echo "─────────────────────────────────────────────────────────────────" echo "─────────────────────────────────────────────────────────────────"
@@ -432,7 +432,7 @@ quick_list() {
# Usage # Usage
usage() { usage() {
cat << EOF cat << EOF
Claudeman Tmux Session Manager Codeman Tmux Session Manager
Reads from: $SESSIONS_FILE Reads from: $SESSIONS_FILE
+4 -4
View File
@@ -11,9 +11,9 @@
* - Event emission for state changes * - Event emission for state changes
* *
* Subclasses implement: * Subclasses implement:
* - `muxNamePrefix`: Prefix for mux session names (e.g., 'claudeman-aicheck-') * - `muxNamePrefix`: Prefix for mux session names (e.g., 'codeman-aicheck-')
* - `doneMarker`: Completion marker in output file (e.g., '__AICHECK_DONE__') * - `doneMarker`: Completion marker in output file (e.g., '__AICHECK_DONE__')
* - `tempFilePrefix`: Prefix for temp files (e.g., 'claudeman-aicheck') * - `tempFilePrefix`: Prefix for temp files (e.g., 'codeman-aicheck')
* - `logPrefix`: Prefix for log messages (e.g., '[AiIdleChecker]') * - `logPrefix`: Prefix for log messages (e.g., '[AiIdleChecker]')
* - `buildPrompt()`: Build the prompt from terminal buffer * - `buildPrompt()`: Build the prompt from terminal buffer
* - `parseVerdict()`: Parse the verdict from AI output * - `parseVerdict()`: Parse the verdict from AI output
@@ -143,13 +143,13 @@ export abstract class AiCheckerBase<
// ========== Abstract Properties ========== // ========== Abstract Properties ==========
/** Prefix for mux session names (e.g., 'claudeman-aicheck-') */ /** Prefix for mux session names (e.g., 'codeman-aicheck-') */
protected abstract readonly muxNamePrefix: string; protected abstract readonly muxNamePrefix: string;
/** Marker written to temp file when check is complete */ /** Marker written to temp file when check is complete */
protected abstract readonly doneMarker: string; protected abstract readonly doneMarker: string;
/** Prefix for temp files (e.g., 'claudeman-aicheck') */ /** Prefix for temp files (e.g., 'codeman-aicheck') */
protected abstract readonly tempFilePrefix: string; protected abstract readonly tempFilePrefix: string;
/** Prefix for log messages (e.g., '[AiIdleChecker]') */ /** Prefix for log messages (e.g., '[AiIdleChecker]') */
+3 -3
View File
@@ -8,7 +8,7 @@
* ## How It Works * ## How It Works
* *
* 1. Generate temp file path for output capture * 1. Generate temp file path for output capture
* 2. Spawn tmux: `tmux new-session -d -s claudeman-aicheck-<short> bash -c 'claude -p ...'` * 2. Spawn tmux: `tmux new-session -d -s codeman-aicheck-<short> bash -c 'claude -p ...'`
* 3. Poll the temp file every 500ms for `__AICHECK_DONE__` marker * 3. Poll the temp file every 500ms for `__AICHECK_DONE__` marker
* 4. Parse the file content for IDLE/WORKING on the first word * 4. Parse the file content for IDLE/WORKING on the first word
* 5. Kill tmux session and delete temp file * 5. Kill tmux session and delete temp file
@@ -129,9 +129,9 @@ export class AiIdleChecker extends AiCheckerBase<
AiCheckResult, AiCheckResult,
AiCheckState AiCheckState
> { > {
protected readonly muxNamePrefix = 'claudeman-aicheck-'; protected readonly muxNamePrefix = 'codeman-aicheck-';
protected readonly doneMarker = '__AICHECK_DONE__'; protected readonly doneMarker = '__AICHECK_DONE__';
protected readonly tempFilePrefix = 'claudeman-aicheck'; protected readonly tempFilePrefix = 'codeman-aicheck';
protected readonly logPrefix = '[AiIdleChecker]'; protected readonly logPrefix = '[AiIdleChecker]';
protected readonly checkDescription = 'AI idle check'; protected readonly checkDescription = 'AI idle check';
+3 -3
View File
@@ -8,7 +8,7 @@
* ## How It Works * ## How It Works
* *
* 1. Generate temp file path for output capture * 1. Generate temp file path for output capture
* 2. Spawn tmux: `tmux new-session -d -s claudeman-plancheck-<short> bash -c 'claude -p ...'` * 2. Spawn tmux: `tmux new-session -d -s codeman-plancheck-<short> bash -c 'claude -p ...'`
* 3. Poll the temp file every 500ms for `__PLANCHECK_DONE__` marker * 3. Poll the temp file every 500ms for `__PLANCHECK_DONE__` marker
* 4. Parse the file content for PLAN_MODE/NOT_PLAN_MODE on the first line * 4. Parse the file content for PLAN_MODE/NOT_PLAN_MODE on the first line
* 5. Kill tmux session and delete temp file * 5. Kill tmux session and delete temp file
@@ -91,9 +91,9 @@ export class AiPlanChecker extends AiCheckerBase<
AiPlanCheckResult, AiPlanCheckResult,
AiPlanCheckState AiPlanCheckState
> { > {
protected readonly muxNamePrefix = 'claudeman-plancheck-'; protected readonly muxNamePrefix = 'codeman-plancheck-';
protected readonly doneMarker = '__PLANCHECK_DONE__'; protected readonly doneMarker = '__PLANCHECK_DONE__';
protected readonly tempFilePrefix = 'claudeman-plancheck'; protected readonly tempFilePrefix = 'codeman-plancheck';
protected readonly logPrefix = '[AiPlanChecker]'; protected readonly logPrefix = '[AiPlanChecker]';
protected readonly checkDescription = 'AI plan check'; protected readonly checkDescription = 'AI plan check';
+4 -4
View File
@@ -1,5 +1,5 @@
/** /**
* @fileoverview Claudeman CLI command definitions * @fileoverview Codeman CLI command definitions
* *
* Defines all CLI commands and subcommands for managing Claude sessions, * Defines all CLI commands and subcommands for managing Claude sessions,
* tasks, Ralph loops, and the web server. * tasks, Ralph loops, and the web server.
@@ -22,7 +22,7 @@ const pkg = require('../package.json') as { version: string };
const program = new Command(); const program = new Command();
program program
.name('claudeman') .name('codeman')
.description('Claude Code session manager with autonomous Ralph Loop') .description('Claude Code session manager with autonomous Ralph Loop')
.version(pkg.version); .version(pkg.version);
@@ -407,7 +407,7 @@ program
? sessions.filter((s) => s.isBusy()).length ? sessions.filter((s) => s.isBusy()).length
: storedValues.filter((s) => s.status === 'busy').length; : storedValues.filter((s) => s.status === 'busy').length;
console.log(chalk.bold('\nClaudeman Status')); console.log(chalk.bold('\nCodeman Status'));
console.log('─'.repeat(40)); console.log('─'.repeat(40));
console.log(chalk.bold('\nSessions:')); console.log(chalk.bold('\nSessions:'));
@@ -504,7 +504,7 @@ program
const https = !!options.https; const https = !!options.https;
const protocol = https ? 'https' : 'http'; const protocol = https ? 'https' : 'http';
console.log(chalk.cyan(`Starting Claudeman web interface on port ${port}${https ? ' (HTTPS)' : ''}...`)); console.log(chalk.cyan(`Starting Codeman web interface on port ${port}${https ? ' (HTTPS)' : ''}...`));
try { try {
const server = await startWebServer(port, https); const server = await startWebServer(port, https);
+1 -1
View File
@@ -2,7 +2,7 @@
* @fileoverview Centralized buffer size limits for memory management. * @fileoverview Centralized buffer size limits for memory management.
* *
* These constants define the maximum sizes for various buffers throughout * These constants define the maximum sizes for various buffers throughout
* Claudeman. Consolidating them here ensures consistent limits and makes * Codeman. Consolidating them here ensures consistent limits and makes
* it easy to tune memory usage. * it easy to tune memory usage.
* *
* Memory Budget Rationale (for 20 concurrent sessions): * Memory Budget Rationale (for 20 concurrent sessions):
+6 -6
View File
@@ -2,9 +2,9 @@
* @fileoverview Claude Code hooks configuration generator * @fileoverview Claude Code hooks configuration generator
* *
* Generates .claude/settings.local.json with hook definitions that POST * Generates .claude/settings.local.json with hook definitions that POST
* to Claudeman's /api/hook-event endpoint when Claude Code fires * to Codeman's /api/hook-event endpoint when Claude Code fires
* notification or stop hooks. Uses $CLAUDEMAN_API_URL and * notification or stop hooks. Uses $CODEMAN_API_URL and
* $CLAUDEMAN_SESSION_ID env vars (set on every managed session) so the * $CODEMAN_SESSION_ID env vars (set on every managed session) so the
* config is static per case directory. * config is static per case directory.
*/ */
@@ -18,7 +18,7 @@ import type { HookEventType } from './types.js';
* Generates the hooks section for .claude/settings.local.json * Generates the hooks section for .claude/settings.local.json
* *
* The hook commands read stdin JSON from Claude Code (contains tool_name, * The hook commands read stdin JSON from Claude Code (contains tool_name,
* tool_input, etc.) and forward it as the `data` field to Claudeman's API. * tool_input, etc.) and forward it as the `data` field to Codeman's API.
* Env vars are resolved at runtime by the shell, so the config is static * Env vars are resolved at runtime by the shell, so the config is static
* per case directory. * per case directory.
*/ */
@@ -27,9 +27,9 @@ export function generateHooksConfig(): { hooks: Record<string, unknown[]> } {
// Falls back to empty object if stdin is unavailable or malformed. // Falls back to empty object if stdin is unavailable or malformed.
const curlCmd = (event: HookEventType) => const curlCmd = (event: HookEventType) =>
`HOOK_DATA=$(cat 2>/dev/null || echo '{}'); ` + `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' ` + `-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`; `2>/dev/null || true`;
return { return {
+2 -2
View File
@@ -137,7 +137,7 @@ export class ImageWatcher extends EventEmitter {
/** /**
* Start watching a session's working directory for new images. * Start watching a session's working directory for new images.
* *
* @param sessionId - Claudeman session ID * @param sessionId - Codeman session ID
* @param workingDir - Path to the session's working directory * @param workingDir - Path to the session's working directory
*/ */
watchSession(sessionId: string, workingDir: string): void { watchSession(sessionId: string, workingDir: string): void {
@@ -200,7 +200,7 @@ export class ImageWatcher extends EventEmitter {
/** /**
* Stop watching a session's working directory. * Stop watching a session's working directory.
* *
* @param sessionId - Claudeman session ID to stop watching * @param sessionId - Codeman session ID to stop watching
*/ */
unwatchSession(sessionId: string): void { unwatchSession(sessionId: string): void {
const watcher = this.sessionWatchers.get(sessionId); const watcher = this.sessionWatchers.get(sessionId);
+2 -2
View File
@@ -1,8 +1,8 @@
#!/usr/bin/env node #!/usr/bin/env node
/** /**
* @fileoverview Claudeman CLI entry point * @fileoverview Codeman CLI entry point
* *
* This is the main executable entry point for the Claudeman CLI. * This is the main executable entry point for the Codeman CLI.
* It sets up global error handlers and invokes the CLI parser. * It sets up global error handlers and invokes the CLI parser.
* *
* @module index * @module index
+3 -3
View File
@@ -13,9 +13,9 @@ import type { ProcessStats, PersistedRespawnConfig, NiceConfig, ClaudeMode, Sess
* Multiplexer session metadata. * Multiplexer session metadata.
*/ */
export interface MuxSession { export interface MuxSession {
/** Claudeman session ID */ /** Codeman session ID */
sessionId: string; sessionId: string;
/** Multiplexer session name (e.g., "claudeman-abc12345") */ /** Multiplexer session name (e.g., "codeman-abc12345") */
muxName: string; muxName: string;
/** Process PID */ /** Process PID */
pid: number; pid: number;
@@ -105,7 +105,7 @@ export interface TerminalMultiplexer extends EventEmitter {
/** Get all tracked sessions */ /** Get all tracked sessions */
getSessions(): MuxSession[]; getSessions(): MuxSession[];
/** Get a session by Claudeman session ID */ /** Get a session by Codeman session ID */
getSession(sessionId: string): MuxSession | undefined; getSession(sessionId: string): MuxSession | undefined;
/** Get all sessions with process resource statistics */ /** Get all sessions with process resource statistics */
+1 -1
View File
@@ -1,5 +1,5 @@
/** /**
* @fileoverview Run Summary Tracker for Claudeman sessions * @fileoverview Run Summary Tracker for Codeman sessions
* *
* Tracks significant events during a session's lifetime to provide users * Tracks significant events during a session's lifetime to provide users
* with a consolidated "what happened while I was away" view. * with a consolidated "what happened while I was away" view.
+2 -2
View File
@@ -2,7 +2,7 @@
* @fileoverview Append-only JSONL audit log for session lifecycle events. * @fileoverview Append-only JSONL audit log for session lifecycle events.
* *
* Records every session creation, start, exit, deletion, recovery, and server * Records every session creation, start, exit, deletion, recovery, and server
* start/stop to ~/.claudeman/session-lifecycle.jsonl. Survives server restarts * start/stop to ~/.codeman/session-lifecycle.jsonl. Survives server restarts
* (unlike RunSummary which is in-memory only). * (unlike RunSummary which is in-memory only).
* *
* @module session-lifecycle-log * @module session-lifecycle-log
@@ -22,7 +22,7 @@ export class SessionLifecycleLog {
private writeQueue: Promise<void> = Promise.resolve(); private writeQueue: Promise<void> = Promise.resolve();
constructor(filePath?: string) { constructor(filePath?: string) {
this.filePath = filePath || join(homedir(), '.claudeman', 'session-lifecycle.jsonl'); this.filePath = filePath || join(homedir(), '.codeman', 'session-lifecycle.jsonl');
const dir = dirname(this.filePath); const dir = dirname(this.filePath);
if (!existsSync(dir)) { if (!existsSync(dir)) {
mkdirSync(dir, { recursive: true, mode: 0o700 }); mkdirSync(dir, { recursive: true, mode: 0o700 });
+15 -15
View File
@@ -400,8 +400,8 @@ export class Session extends EventEmitter {
this.mode = config.mode || 'claude'; this.mode = config.mode || 'claude';
this._name = config.name || ''; this._name = config.name || '';
this._lastActivityAt = this.createdAt; this._lastActivityAt = this.createdAt;
// Set claudeSessionId immediately — Claudeman always passes --session-id ${this.id} // Set claudeSessionId immediately — Codeman always passes --session-id ${this.id}
// to Claude CLI, so the Claude session ID always matches the Claudeman session ID. // to Claude CLI, so the Claude session ID always matches the Codeman session ID.
// This ensures subagent matching works even for recovered sessions (where // This ensures subagent matching works even for recovered sessions (where
// startInteractive() hasn't been called yet). // startInteractive() hasn't been called yet).
this._claudeSessionId = this.id; this._claudeSessionId = this.id;
@@ -1041,7 +1041,7 @@ export class Session extends EventEmitter {
throw new Error('OpenCode sessions require tmux. Direct PTY fallback is not supported.'); throw new Error('OpenCode sessions require tmux. Direct PTY fallback is not supported.');
} }
try { try {
// Pass --session-id to use the SAME ID as the Claudeman session // Pass --session-id to use the SAME ID as the Codeman session
// This ensures subagents can be directly matched to the correct tab // This ensures subagents can be directly matched to the correct tab
const args = [...this._buildPermissionArgs(), '--session-id', this.id]; const args = [...this._buildPermissionArgs(), '--session-id', this.id];
if (this._model) args.push('--model', this._model); if (this._model) args.push('--model', this._model);
@@ -1058,10 +1058,10 @@ export class Session extends EventEmitter {
TERM: 'xterm-256color', TERM: 'xterm-256color',
COLORTERM: undefined, COLORTERM: undefined,
CLAUDECODE: undefined, CLAUDECODE: undefined,
// Inform Claude it's running within Claudeman (helps prevent self-termination) // Inform Claude it's running within Codeman (helps prevent self-termination)
CLAUDEMAN_MUX: '1', CODEMAN_MUX: '1',
CLAUDEMAN_SESSION_ID: this.id, CODEMAN_SESSION_ID: this.id,
CLAUDEMAN_API_URL: process.env.CLAUDEMAN_API_URL || 'http://localhost:3000', CODEMAN_API_URL: process.env.CODEMAN_API_URL || 'http://localhost:3000',
}, },
}); });
} catch (spawnErr) { } catch (spawnErr) {
@@ -1376,9 +1376,9 @@ export class Session extends EventEmitter {
LANG: 'en_US.UTF-8', LANG: 'en_US.UTF-8',
LC_ALL: 'en_US.UTF-8', LC_ALL: 'en_US.UTF-8',
TERM: 'xterm-256color', TERM: 'xterm-256color',
CLAUDEMAN_MUX: '1', CODEMAN_MUX: '1',
CLAUDEMAN_SESSION_ID: this.id, CODEMAN_SESSION_ID: this.id,
CLAUDEMAN_API_URL: process.env.CLAUDEMAN_API_URL || 'http://localhost:3000', CODEMAN_API_URL: process.env.CODEMAN_API_URL || 'http://localhost:3000',
}, },
}); });
} catch (spawnErr) { } catch (spawnErr) {
@@ -1509,10 +1509,10 @@ export class Session extends EventEmitter {
TERM: 'xterm-256color', TERM: 'xterm-256color',
COLORTERM: undefined, COLORTERM: undefined,
CLAUDECODE: undefined, CLAUDECODE: undefined,
// Inform Claude it's running within Claudeman // Inform Claude it's running within Codeman
CLAUDEMAN_MUX: '1', CODEMAN_MUX: '1',
CLAUDEMAN_SESSION_ID: this.id, CODEMAN_SESSION_ID: this.id,
CLAUDEMAN_API_URL: process.env.CLAUDEMAN_API_URL || 'http://localhost:3000', CODEMAN_API_URL: process.env.CODEMAN_API_URL || 'http://localhost:3000',
}, },
}); });
} catch (spawnErr) { } catch (spawnErr) {
@@ -1867,7 +1867,7 @@ export class Session extends EventEmitter {
} }
// Parse Claude Code CLI info from terminal startup output // Parse Claude Code CLI info from terminal startup output
// Extracts version, model, and account type for display in Claudeman UI // Extracts version, model, and account type for display in Codeman UI
// Note: Expects cleanData with ANSI codes already stripped by caller // Note: Expects cleanData with ANSI codes already stripped by caller
private parseClaudeCodeInfo(cleanData: string): void { private parseClaudeCodeInfo(cleanData: string): void {
// Only parse once per session (during startup) // Only parse once per session (during startup)
+19 -3
View File
@@ -1,8 +1,8 @@
/** /**
* @fileoverview Persistent JSON state storage for Claudeman. * @fileoverview Persistent JSON state storage for Codeman.
* *
* This module provides the StateStore class which persists application state * This module provides the StateStore class which persists application state
* to `~/.claudeman/state.json` with debounced writes to prevent excessive disk I/O. * to `~/.codeman/state.json` with debounced writes to prevent excessive disk I/O.
* *
* State is split into two files: * State is split into two files:
* - `state.json`: Main app state (sessions, tasks, config) * - `state.json`: Main app state (sessions, tasks, config)
@@ -68,7 +68,23 @@ export class StateStore {
private _saveInFlight: Promise<void> | null = null; private _saveInFlight: Promise<void> | null = null;
constructor(filePath?: string) { constructor(filePath?: string) {
this.filePath = filePath || join(homedir(), '.claudeman', 'state.json'); // Migrate legacy data directory (~/.claudeman → ~/.codeman)
if (!filePath) {
const legacyDir = join(homedir(), '.claudeman');
const newDir = join(homedir(), '.codeman');
if (existsSync(legacyDir) && !existsSync(newDir)) {
console.log(`[state-store] Migrating data directory: ${legacyDir} → ${newDir}`);
renameSync(legacyDir, newDir);
}
const legacyCasesDir = join(homedir(), 'claudeman-cases');
const newCasesDir = join(homedir(), 'codeman-cases');
if (existsSync(legacyCasesDir) && !existsSync(newCasesDir)) {
console.log(`[state-store] Migrating cases directory: ${legacyCasesDir} → ${newCasesDir}`);
renameSync(legacyCasesDir, newCasesDir);
}
}
this.filePath = filePath || join(homedir(), '.codeman', 'state.json');
this.ralphStatePath = this.filePath.replace('.json', '-inner.json'); this.ralphStatePath = this.filePath.replace('.json', '-inner.json');
this.state = this.load(); this.state = this.load();
this.state.config.stateFilePath = this.filePath; this.state.config.stateFilePath = this.filePath;
+10 -10
View File
@@ -310,7 +310,7 @@ export class SubagentWatcher extends EventEmitter {
/** /**
* Run pgrep once and read /proc info for all Claude PIDs in parallel. * Run pgrep once and read /proc info for all Claude PIDs in parallel.
* Returns a Map of pid -> { environ, cmdline } for subagent processes only. * Returns a Map of pid -> { environ, cmdline } for subagent processes only.
* Excludes main Claudeman-managed Claude processes (CLAUDEMAN_MUX=1). * Excludes main Codeman-managed Claude processes (CODEMAN_MUX=1).
* Also updates cached PIDs on tracked agents when a match is found. * Also updates cached PIDs on tracked agents when a match is found.
*/ */
private async getClaudePids(): Promise<Map<number, { environ: string; cmdline: string }>> { private async getClaudePids(): Promise<Map<number, { environ: string; cmdline: string }>> {
@@ -330,8 +330,8 @@ export class SubagentWatcher extends EventEmitter {
let cmdline = ''; let cmdline = '';
try { environ = await readFile(`/proc/${pid}/environ`, 'utf8'); } catch { /* skip */ } try { environ = await readFile(`/proc/${pid}/environ`, 'utf8'); } catch { /* skip */ }
try { cmdline = await readFile(`/proc/${pid}/cmdline`, 'utf8'); } catch { /* skip */ } try { cmdline = await readFile(`/proc/${pid}/cmdline`, 'utf8'); } catch { /* skip */ }
// Skip main Claudeman-managed Claude processes — only track subagents // Skip main Codeman-managed Claude processes — only track subagents
if (environ.includes('CLAUDEMAN_MUX=1')) return; if (environ.includes('CODEMAN_MUX=1')) return;
if (environ || cmdline) { if (environ || cmdline) {
result.set(pid, { environ, cmdline }); result.set(pid, { environ, cmdline });
} }
@@ -562,8 +562,8 @@ export class SubagentWatcher extends EventEmitter {
} }
/** /**
* Get subagents for a specific Claudeman session * Get subagents for a specific Codeman session
* Maps Claudeman working directory to Claude's project hash * Maps Codeman working directory to Claude's project hash
*/ */
getSubagentsForSession(workingDir: string): SubagentInfo[] { getSubagentsForSession(workingDir: string): SubagentInfo[] {
const projectHash = this.getProjectHash(workingDir); const projectHash = this.getProjectHash(workingDir);
@@ -677,7 +677,7 @@ export class SubagentWatcher extends EventEmitter {
} }
/** /**
* Kill all subagents for a specific Claudeman session. * Kill all subagents for a specific Codeman session.
* IMPORTANT: Must scope to sessionId to avoid cross-session kills. * IMPORTANT: Must scope to sessionId to avoid cross-session kills.
* All sessions in the same workingDir share a projectHash, so filtering * All sessions in the same workingDir share a projectHash, so filtering
* by workingDir alone would kill subagents belonging to OTHER sessions. * by workingDir alone would kill subagents belonging to OTHER sessions.
@@ -696,7 +696,7 @@ export class SubagentWatcher extends EventEmitter {
/** /**
* Find the process ID of a Claude subagent by its session ID. * Find the process ID of a Claude subagent by its session ID.
* Searches /proc for claude processes with matching session ID in environment. * Searches /proc for claude processes with matching session ID in environment.
* Skips main Claudeman-managed Claude processes (identified by CLAUDEMAN_MUX=1). * Skips main Codeman-managed Claude processes (identified by CODEMAN_MUX=1).
* Caches the discovered PID on the matching agent info for future fast checks. * Caches the discovered PID on the matching agent info for future fast checks.
*/ */
private async findSubagentProcess(sessionId: string): Promise<number | null> { private async findSubagentProcess(sessionId: string): Promise<number | null> {
@@ -721,9 +721,9 @@ export class SubagentWatcher extends EventEmitter {
// Can't read this process's environ - skip // Can't read this process's environ - skip
} }
// Defense-in-depth: never kill a main Claudeman-managed Claude process. // Defense-in-depth: never kill a main Codeman-managed Claude process.
// Main processes have CLAUDEMAN_MUX=1 in their environment; subagents don't. // Main processes have CODEMAN_MUX=1 in their environment; subagents don't.
if (environ.includes('CLAUDEMAN_MUX=1')) continue; if (environ.includes('CODEMAN_MUX=1')) continue;
if (environ.includes(sessionId)) { if (environ.includes(sessionId)) {
// Cache PID on the matching agent // Cache PID on the matching agent
+2 -2
View File
@@ -2,7 +2,7 @@
* @fileoverview Agent Teams Watcher * @fileoverview Agent Teams Watcher
* *
* Polls ~/.claude/teams/ and ~/.claude/tasks/ for agent team activity. * Polls ~/.claude/teams/ and ~/.claude/tasks/ for agent team activity.
* Matches teams to Claudeman sessions via leadSessionId and emits * Matches teams to Codeman sessions via leadSessionId and emits
* events for UI updates and team-aware idle detection. * events for UI updates and team-aware idle detection.
* *
* @module team-watcher * @module team-watcher
@@ -70,7 +70,7 @@ export class TeamWatcher extends EventEmitter {
return Array.from(this.teams.values()); return Array.from(this.teams.values());
} }
/** Get team associated with a Claudeman session (matched by leadSessionId) */ /** Get team associated with a Codeman session (matched by leadSessionId) */
getTeamForSession(sessionId: string): TeamConfig | undefined { getTeamForSession(sessionId: string): TeamConfig | undefined {
const teamName = this.sessionToTeam.get(sessionId); const teamName = this.sessionToTeam.get(sessionId);
if (teamName) { if (teamName) {
+3 -3
View File
@@ -18,11 +18,11 @@ Then update the Project Overview section below.
--- ---
## Claudeman Environment ## Codeman Environment
This session is managed by **Claudeman** and runs within a tmux session. This session is managed by **Codeman** and runs within a tmux session.
**Important**: Check for `CLAUDEMAN_MUX=1` environment variable to confirm. **Important**: Check for `CODEMAN_MUX=1` environment variable to confirm.
- Do NOT attempt to kill your own tmux session - Do NOT attempt to kill your own tmux session
- The session persists across disconnects - your work is safe - The session persists across disconnects - your work is safe
- Token usage, costs, and background tasks are tracked externally - Token usage, costs, and background tasks are tracked externally
+1 -1
View File
@@ -1,7 +1,7 @@
/** /**
* @fileoverview CLAUDE.md template generation * @fileoverview CLAUDE.md template generation
* *
* Generates CLAUDE.md configuration files for new Claudeman projects. * Generates CLAUDE.md configuration files for new Codeman projects.
* Reads the bundled case-template.md and performs placeholder substitution. * Reads the bundled case-template.md and performs placeholder substitution.
* Supports custom templates via settings.json override. * Supports custom templates via settings.json override.
* *
+23 -20
View File
@@ -10,7 +10,7 @@
* - **Reliable input**: `send-keys -l` sends literal text in a single command * - **Reliable input**: `send-keys -l` sends literal text in a single command
* - **Teammate support**: Immutable pane IDs enable targeting individual teammates * - **Teammate support**: Immutable pane IDs enable targeting individual teammates
* *
* tmux sessions are named `claudeman-{sessionId}` and stored in ~/.claudeman/mux-sessions.json. * tmux sessions are named `codeman-{sessionId}` and stored in ~/.codeman/mux-sessions.json.
* *
* Key features: * Key features:
* - `send-keys 'text' Enter` sends literal text in a single command * - `send-keys 'text' Enter` sends literal text in a single command
@@ -74,15 +74,18 @@ const DEFAULT_STATS_INTERVAL_MS = 2000;
* - Create a tmux session * - Create a tmux session
* - Send input to a tmux session * - Send input to a tmux session
* - Discover/reconcile real tmux sessions * - Discover/reconcile real tmux sessions
* - Read/write ~/.claudeman/mux-sessions.json * - Read/write ~/.codeman/mux-sessions.json
*/ */
const IS_TEST_MODE = !!process.env.VITEST; const IS_TEST_MODE = !!process.env.VITEST;
/** Path to persisted mux session metadata */ /** Path to persisted mux session metadata */
const MUX_SESSIONS_FILE = join(homedir(), '.claudeman', 'mux-sessions.json'); const MUX_SESSIONS_FILE = join(homedir(), '.codeman', 'mux-sessions.json');
/** Regex to validate tmux session names (only allow safe characters) */ /** Regex to validate tmux session names (only allow safe characters) */
const SAFE_MUX_NAME_PATTERN = /^claudeman-[a-f0-9-]+$/; const SAFE_MUX_NAME_PATTERN = /^codeman-[a-f0-9-]+$/;
/** Legacy pattern for pre-rename sessions (claudeman- prefix) */
const LEGACY_MUX_NAME_PATTERN = /^claudeman-[a-f0-9-]+$/;
/** Regex to validate tmux pane targets (e.g., "%0", "%1", "0", "1") */ /** Regex to validate tmux pane targets (e.g., "%0", "%1", "0", "1") */
const SAFE_PANE_TARGET_PATTERN = /^(%\d+|\d+)$/; const SAFE_PANE_TARGET_PATTERN = /^(%\d+|\d+)$/;
@@ -92,7 +95,7 @@ const SAFE_PANE_TARGET_PATTERN = /^(%\d+|\d+)$/;
* Prevents command injection via malformed session IDs. * Prevents command injection via malformed session IDs.
*/ */
function isValidMuxName(name: string): boolean { function isValidMuxName(name: string): boolean {
return SAFE_MUX_NAME_PATTERN.test(name); return SAFE_MUX_NAME_PATTERN.test(name) || LEGACY_MUX_NAME_PATTERN.test(name);
} }
/** /**
@@ -336,7 +339,7 @@ export class TmuxManager extends EventEmitter implements TerminalMultiplexer {
*/ */
async createSession(options: CreateSessionOptions): Promise<MuxSession> { async createSession(options: CreateSessionOptions): Promise<MuxSession> {
const { sessionId, workingDir, mode, name, niceConfig, model, claudeMode, allowedTools, openCodeConfig } = options; const { sessionId, workingDir, mode, name, niceConfig, model, claudeMode, allowedTools, openCodeConfig } = options;
const muxName = `claudeman-${sessionId.slice(0, 8)}`; const muxName = `codeman-${sessionId.slice(0, 8)}`;
if (!isValidMuxName(muxName)) { if (!isValidMuxName(muxName)) {
throw new Error('Invalid session name: contains unsafe characters'); throw new Error('Invalid session name: contains unsafe characters');
@@ -382,10 +385,10 @@ export class TmuxManager extends EventEmitter implements TerminalMultiplexer {
'export LANG=en_US.UTF-8', 'export LANG=en_US.UTF-8',
'export LC_ALL=en_US.UTF-8', 'export LC_ALL=en_US.UTF-8',
'unset COLORTERM', 'unset COLORTERM',
'export CLAUDEMAN_MUX=1', 'export CODEMAN_MUX=1',
`export CLAUDEMAN_SESSION_ID=${sessionId}`, `export CODEMAN_SESSION_ID=${sessionId}`,
`export CLAUDEMAN_MUX_NAME=${muxName}`, `export CODEMAN_MUX_NAME=${muxName}`,
`export CLAUDEMAN_API_URL=${process.env.CLAUDEMAN_API_URL || 'http://localhost:3000'}`, `export CODEMAN_API_URL=${process.env.CODEMAN_API_URL || 'http://localhost:3000'}`,
]; ];
// Only unset CLAUDECODE for Claude sessions // Only unset CLAUDECODE for Claude sessions
if (mode === 'claude') envExports.splice(2, 0, 'unset CLAUDECODE'); if (mode === 'claude') envExports.splice(2, 0, 'unset CLAUDECODE');
@@ -440,7 +443,7 @@ export class TmuxManager extends EventEmitter implements TerminalMultiplexer {
// Mouse mode is OFF by default so xterm.js handles text selection natively. // Mouse mode is OFF by default so xterm.js handles text selection natively.
// It gets enabled dynamically when panes are split (agent teams). // It gets enabled dynamically when panes are split (agent teams).
const configPromises: Promise<void>[] = [ const configPromises: Promise<void>[] = [
// Disable tmux status bar — Claudeman's web UI provides session info // Disable tmux status bar — Codeman's web UI provides session info
execAsync(`tmux set-option -t "${muxName}" status off`, { timeout: EXEC_TIMEOUT_MS }) execAsync(`tmux set-option -t "${muxName}" status off`, { timeout: EXEC_TIMEOUT_MS })
.then(() => {}).catch(() => { /* Non-critical — session still works with status bar */ }), .then(() => {}).catch(() => { /* Non-critical — session still works with status bar */ }),
// Override global remain-on-exit with session-level setting // Override global remain-on-exit with session-level setting
@@ -567,10 +570,10 @@ export class TmuxManager extends EventEmitter implements TerminalMultiplexer {
'export LANG=en_US.UTF-8', 'export LANG=en_US.UTF-8',
'export LC_ALL=en_US.UTF-8', 'export LC_ALL=en_US.UTF-8',
'unset COLORTERM', 'unset COLORTERM',
'export CLAUDEMAN_MUX=1', 'export CODEMAN_MUX=1',
`export CLAUDEMAN_SESSION_ID=${sessionId}`, `export CODEMAN_SESSION_ID=${sessionId}`,
`export CLAUDEMAN_MUX_NAME=${muxName}`, `export CODEMAN_MUX_NAME=${muxName}`,
`export CLAUDEMAN_API_URL=${process.env.CLAUDEMAN_API_URL || 'http://localhost:3000'}`, `export CODEMAN_API_URL=${process.env.CODEMAN_API_URL || 'http://localhost:3000'}`,
]; ];
if (mode === 'claude') envExports.splice(2, 0, 'unset CLAUDECODE'); if (mode === 'claude') envExports.splice(2, 0, 'unset CLAUDECODE');
const envExportsStr = envExports.join(' && '); const envExportsStr = envExports.join(' && ');
@@ -685,7 +688,7 @@ export class TmuxManager extends EventEmitter implements TerminalMultiplexer {
} }
// SAFETY: Never kill the tmux session we're running inside of // SAFETY: Never kill the tmux session we're running inside of
const currentMuxName = process.env.CLAUDEMAN_MUX_NAME; const currentMuxName = process.env.CODEMAN_MUX_NAME;
if (currentMuxName && session.muxName === currentMuxName) { if (currentMuxName && session.muxName === currentMuxName) {
console.error(`[TmuxManager] BLOCKED: Refusing to kill own tmux session: ${session.muxName}`); console.error(`[TmuxManager] BLOCKED: Refusing to kill own tmux session: ${session.muxName}`);
return false; return false;
@@ -824,7 +827,7 @@ export class TmuxManager extends EventEmitter implements TerminalMultiplexer {
} }
} }
// Discover unknown claudeman sessions // Discover unknown codeman sessions
try { try {
const output = execSync( const output = execSync(
"tmux list-sessions -F '#{session_name}' 2>/dev/null || true", "tmux list-sessions -F '#{session_name}' 2>/dev/null || true",
@@ -833,7 +836,7 @@ export class TmuxManager extends EventEmitter implements TerminalMultiplexer {
for (const line of output.split('\n')) { for (const line of output.split('\n')) {
const sessionName = line.trim(); const sessionName = line.trim();
if (!sessionName || !sessionName.startsWith('claudeman-')) continue; if (!sessionName || (!sessionName.startsWith('codeman-') && !sessionName.startsWith('claudeman-'))) continue;
// Check if this session is already known // Check if this session is already known
let isKnown = false; let isKnown = false;
@@ -846,7 +849,7 @@ export class TmuxManager extends EventEmitter implements TerminalMultiplexer {
if (!isKnown) { if (!isKnown) {
// Extract session ID fragment from name // Extract session ID fragment from name
const fragment = sessionName.replace('claudeman-', ''); const fragment = sessionName.replace(/^(?:codeman|claudeman)-/, '');
const sessionId = `restored-${fragment}`; const sessionId = `restored-${fragment}`;
const pid = this.getPanePid(sessionName); const pid = this.getPanePid(sessionName);
@@ -1310,7 +1313,7 @@ export class TmuxManager extends EventEmitter implements TerminalMultiplexer {
return false; return false;
} }
// Build target: sessionName.paneId (e.g., "claudeman-abc12345.%1") // Build target: sessionName.paneId (e.g., "codeman-abc12345.%1")
const target = paneTarget.startsWith('%') const target = paneTarget.startsWith('%')
? `${muxName}.${paneTarget}` ? `${muxName}.${paneTarget}`
: `${muxName}.%${paneTarget}`; : `${muxName}.%${paneTarget}`;
+4 -4
View File
@@ -1,8 +1,8 @@
/** /**
* @fileoverview Type definitions for Claudeman * @fileoverview Type definitions for Codeman
* *
* This module contains all TypeScript interfaces, types, and enums used * This module contains all TypeScript interfaces, types, and enums used
* throughout the Claudeman application. It provides type safety for: * throughout the Codeman application. It provides type safety for:
* - Session management * - Session management
* - Task queue operations * - Task queue operations
* - Ralph Loop configuration * - Ralph Loop configuration
@@ -859,7 +859,7 @@ export const DEFAULT_CONFIG: AppConfig = {
* Types for tracking Ralph Wiggum loops and todo lists * Types for tracking Ralph Wiggum loops and todo lists
* running inside Claude Code sessions. * running inside Claude Code sessions.
* *
* This allows Claudeman to detect and display when Claude Code * This allows Codeman to detect and display when Claude Code
* is running its own autonomous loops internally. * is running its own autonomous loops internally.
*/ */
@@ -1369,7 +1369,7 @@ export interface ActiveBashTool {
* Used to trigger automatic image popup display in the web UI. * Used to trigger automatic image popup display in the web UI.
*/ */
export interface ImageDetectedEvent { export interface ImageDetectedEvent {
/** Claudeman session ID where the image was detected */ /** Codeman session ID where the image was detected */
sessionId: string; sessionId: string;
/** Full path to the detected image file */ /** Full path to the detected image file */
filePath: string; filePath: string;
+57 -44
View File
@@ -1,4 +1,4 @@
// Claudeman App - Tab-based Terminal UI // Codeman App - Tab-based Terminal UI
// ============================================================================ // ============================================================================
// Constants // Constants
@@ -852,14 +852,14 @@ const VoiceInput = {
_getDeepgramConfig() { _getDeepgramConfig() {
try { try {
return JSON.parse(localStorage.getItem('claudeman-voice-settings') || '{}'); return JSON.parse(localStorage.getItem('codeman-voice-settings') || '{}');
} catch (_e) { } catch (_e) {
return {}; return {};
} }
}, },
_saveDeepgramConfig(config) { _saveDeepgramConfig(config) {
localStorage.setItem('claudeman-voice-settings', JSON.stringify(config)); localStorage.setItem('codeman-voice-settings', JSON.stringify(config));
}, },
_shouldUseDeepgram() { _shouldUseDeepgram() {
@@ -2294,7 +2294,7 @@ class NotificationManager {
// Get storage key for notification prefs (device-specific) // Get storage key for notification prefs (device-specific)
getStorageKey() { getStorageKey() {
const isMobile = MobileDetection.getDeviceType() === 'mobile'; const isMobile = MobileDetection.getDeviceType() === 'mobile';
return isMobile ? 'claudeman-notification-prefs-mobile' : 'claudeman-notification-prefs'; return isMobile ? 'codeman-notification-prefs-mobile' : 'codeman-notification-prefs';
} }
savePreferences() { savePreferences() {
@@ -2449,11 +2449,11 @@ class NotificationManager {
this.titleFlashInterval = setInterval(() => { this.titleFlashInterval = setInterval(() => {
this.titleFlashState = !this.titleFlashState; this.titleFlashState = !this.titleFlashState;
document.title = this.titleFlashState document.title = this.titleFlashState
? `\u26A0\uFE0F (${this.unreadCount}) Claudeman` ? `\u26A0\uFE0F (${this.unreadCount}) Codeman`
: this.originalTitle; : this.originalTitle;
}, TITLE_FLASH_INTERVAL_MS); }, TITLE_FLASH_INTERVAL_MS);
// Set immediately // Set immediately
document.title = `\u26A0\uFE0F (${this.unreadCount}) Claudeman`; document.title = `\u26A0\uFE0F (${this.unreadCount}) Codeman`;
} }
} }
} }
@@ -2488,7 +2488,7 @@ class NotificationManager {
if (now - this.lastBrowserNotifTime < 3000) return; if (now - this.lastBrowserNotifTime < 3000) return;
this.lastBrowserNotifTime = now; this.lastBrowserNotifTime = now;
const notif = new Notification(`Claudeman: ${title}`, { const notif = new Notification(`Codeman: ${title}`, {
body, body,
tag, // Groups same-tag notifications tag, // Groups same-tag notifications
icon: '/favicon.ico', icon: '/favicon.ico',
@@ -2634,7 +2634,7 @@ class NotificationManager {
} }
} }
class ClaudemanApp { class CodemanApp {
constructor() { constructor() {
this.sessions = new Map(); this.sessions = new Map();
this.sessionOrder = []; // Track tab order for drag-and-drop reordering this.sessionOrder = []; // Track tab order for drag-and-drop reordering
@@ -2919,7 +2919,7 @@ class ClaudemanApp {
initTerminal() { initTerminal() {
// Load scrollback setting from localStorage (default 5000) // Load scrollback setting from localStorage (default 5000)
const scrollback = parseInt(localStorage.getItem('claudeman-scrollback')) || DEFAULT_SCROLLBACK; const scrollback = parseInt(localStorage.getItem('codeman-scrollback')) || DEFAULT_SCROLLBACK;
this.terminal = new Terminal({ this.terminal = new Terminal({
theme: { theme: {
@@ -4023,7 +4023,7 @@ class ClaudemanApp {
this._cleanupSessionData(data.id); this._cleanupSessionData(data.id);
if (this.activeSessionId === data.id) { if (this.activeSessionId === data.id) {
this.activeSessionId = null; this.activeSessionId = null;
try { localStorage.removeItem('claudeman-active-session'); } catch {} try { localStorage.removeItem('codeman-active-session'); } catch {}
this.terminal.clear(); this.terminal.clear();
this.showWelcome(); this.showWelcome();
} }
@@ -4709,11 +4709,11 @@ class ClaudemanApp {
} }
this.renderSubagentPanel(); this.renderSubagentPanel();
// Find which Claudeman session owns this subagent (direct claudeSessionId match only) // Find which Codeman session owns this subagent (direct claudeSessionId match only)
this.findParentSessionForSubagent(data.agentId); this.findParentSessionForSubagent(data.agentId);
// Auto-open window for new active agents — but ONLY if they belong to a Claudeman session tab. // Auto-open window for new active agents — but ONLY if they belong to a Codeman session tab.
// Agents from external Claude sessions (not managed by Claudeman) should not pop up. // Agents from external Claude sessions (not managed by Codeman) should not pop up.
if (data.status === 'active') { if (data.status === 'active') {
const agentForCheck = this.subagents.get(data.agentId); const agentForCheck = this.subagents.get(data.agentId);
const hasMatchingTab = agentForCheck?.sessionId && const hasMatchingTab = agentForCheck?.sessionId &&
@@ -5106,11 +5106,11 @@ class ClaudemanApp {
const headerVersionEl = this.$('headerVersion'); const headerVersionEl = this.$('headerVersion');
if (versionEl) { if (versionEl) {
versionEl.textContent = `v${data.version}`; versionEl.textContent = `v${data.version}`;
versionEl.title = `Claudeman v${data.version}`; versionEl.title = `Codeman v${data.version}`;
} }
if (headerVersionEl) { if (headerVersionEl) {
headerVersionEl.textContent = `v${data.version}`; headerVersionEl.textContent = `v${data.version}`;
headerVersionEl.title = `Claudeman v${data.version}`; headerVersionEl.title = `Codeman v${data.version}`;
} }
} }
@@ -5299,7 +5299,7 @@ class ClaudemanApp {
// Priority: current active > localStorage > first session // Priority: current active > localStorage > first session
let restoreId = previousActiveId; let restoreId = previousActiveId;
if (!restoreId || !this.sessions.has(restoreId)) { if (!restoreId || !this.sessions.has(restoreId)) {
try { restoreId = localStorage.getItem('claudeman-active-session'); } catch {} try { restoreId = localStorage.getItem('codeman-active-session'); } catch {}
} }
if (restoreId && this.sessions.has(restoreId)) { if (restoreId && this.sessions.has(restoreId)) {
this.selectSession(restoreId); this.selectSession(restoreId);
@@ -5605,7 +5605,7 @@ class ClaudemanApp {
// Load session order from localStorage // Load session order from localStorage
loadSessionOrder() { loadSessionOrder() {
try { try {
const saved = localStorage.getItem('claudeman-session-order'); const saved = localStorage.getItem('codeman-session-order');
return saved ? JSON.parse(saved) : []; return saved ? JSON.parse(saved) : [];
} catch { } catch {
return []; return [];
@@ -5615,7 +5615,7 @@ class ClaudemanApp {
// Save session order to localStorage // Save session order to localStorage
saveSessionOrder() { saveSessionOrder() {
try { try {
localStorage.setItem('claudeman-session-order', JSON.stringify(this.sessionOrder)); localStorage.setItem('codeman-session-order', JSON.stringify(this.sessionOrder));
} catch { } catch {
// Ignore storage errors // Ignore storage errors
} }
@@ -5924,7 +5924,7 @@ class ClaudemanApp {
this._localEchoOverlay.suppressBufferDetection(); this._localEchoOverlay.suppressBufferDetection();
} }
this.activeSessionId = sessionId; this.activeSessionId = sessionId;
try { localStorage.setItem('claudeman-active-session', sessionId); } catch {} try { localStorage.setItem('codeman-active-session', sessionId); } catch {}
this.hideWelcome(); this.hideWelcome();
// Clear idle hooks on view, but keep action hooks until user interacts // Clear idle hooks on view, but keep action hooks until user interacts
this.clearPendingHooks(sessionId, 'idle_prompt'); this.clearPendingHooks(sessionId, 'idle_prompt');
@@ -6160,7 +6160,7 @@ class ClaudemanApp {
if (this.activeSessionId === sessionId) { if (this.activeSessionId === sessionId) {
this.activeSessionId = null; this.activeSessionId = null;
try { localStorage.removeItem('claudeman-active-session'); } catch {} try { localStorage.removeItem('codeman-active-session'); } catch {}
// Select another session or show welcome (use sessionOrder for consistent ordering) // Select another session or show welcome (use sessionOrder for consistent ordering)
if (this.sessionOrder.length > 0 && this.sessions.size > 0) { if (this.sessionOrder.length > 0 && this.sessions.size > 0) {
const nextSessionId = this.sessionOrder[0]; const nextSessionId = this.sessionOrder[0];
@@ -6242,7 +6242,7 @@ class ClaudemanApp {
goHome() { goHome() {
// Deselect active session and show welcome screen // Deselect active session and show welcome screen
this.activeSessionId = null; this.activeSessionId = null;
try { localStorage.removeItem('claudeman-active-session'); } catch {} try { localStorage.removeItem('codeman-active-session'); } catch {}
this.terminal.clear(); this.terminal.clear();
this.showWelcome(); this.showWelcome();
this.renderSessionTabs(); this.renderSessionTabs();
@@ -9250,7 +9250,7 @@ class ClaudemanApp {
} else { } else {
// No cases exist yet - show the default case name as directory // No cases exist yet - show the default case name as directory
select.value = 'testcase'; select.value = 'testcase';
document.getElementById('dirDisplay').textContent = '~/claudeman-cases/testcase'; document.getElementById('dirDisplay').textContent = '~/codeman-cases/testcase';
this.updateMobileCaseLabel('testcase'); this.updateMobileCaseLabel('testcase');
} }
@@ -9317,7 +9317,7 @@ class ClaudemanApp {
setRunMode(mode) { setRunMode(mode) {
this._runMode = mode; this._runMode = mode;
try { localStorage.setItem('claudeman_runMode', mode); } catch {} try { localStorage.setItem('codeman_runMode', mode); } catch {}
this._applyRunMode(); this._applyRunMode();
// Close menu // Close menu
document.getElementById('runModeMenu')?.classList.remove('active'); document.getElementById('runModeMenu')?.classList.remove('active');
@@ -9361,7 +9361,7 @@ class ClaudemanApp {
} }
_initRunMode() { _initRunMode() {
try { this._runMode = localStorage.getItem('claudeman_runMode') || 'claude'; } catch { this._runMode = 'claude'; } try { this._runMode = localStorage.getItem('codeman_runMode') || 'claude'; } catch { this._runMode = 'claude'; }
this._applyRunMode(); this._applyRunMode();
} }
@@ -10132,7 +10132,7 @@ class ClaudemanApp {
this.terminalBuffers.clear(); this.terminalBuffers.clear();
this.terminalBufferCache.clear(); this.terminalBufferCache.clear();
this.activeSessionId = null; this.activeSessionId = null;
try { localStorage.removeItem('claudeman-active-session'); } catch {} try { localStorage.removeItem('codeman-active-session'); } catch {}
this.respawnStatus = {}; this.respawnStatus = {};
this.respawnCountdownTimers = {}; this.respawnCountdownTimers = {};
this.respawnActionLogs = {}; this.respawnActionLogs = {};
@@ -10239,13 +10239,13 @@ class ClaudemanApp {
this.terminal.options.fontSize = size; this.terminal.options.fontSize = size;
document.getElementById('fontSizeDisplay').textContent = size; document.getElementById('fontSizeDisplay').textContent = size;
this.fitAddon.fit(); this.fitAddon.fit();
localStorage.setItem('claudeman-font-size', size); localStorage.setItem('codeman-font-size', size);
// Update overlay font cache and re-render at new cell dimensions // Update overlay font cache and re-render at new cell dimensions
this._localEchoOverlay?.refreshFont(); this._localEchoOverlay?.refreshFont();
} }
loadFontSize() { loadFontSize() {
const saved = localStorage.getItem('claudeman-font-size'); const saved = localStorage.getItem('codeman-font-size');
if (saved) { if (saved) {
const size = parseInt(saved, 10); const size = parseInt(saved, 10);
if (size >= 10 && size <= 24) { if (size >= 10 && size <= 24) {
@@ -10676,7 +10676,7 @@ class ClaudemanApp {
// ========== Respawn Presets ========== // ========== Respawn Presets ==========
loadRespawnPresets() { loadRespawnPresets() {
const saved = localStorage.getItem('claudeman-respawn-presets'); const saved = localStorage.getItem('codeman-respawn-presets');
const custom = saved ? JSON.parse(saved) : []; const custom = saved ? JSON.parse(saved) : [];
return [...BUILTIN_RESPAWN_PRESETS, ...custom]; return [...BUILTIN_RESPAWN_PRESETS, ...custom];
} }
@@ -10684,7 +10684,7 @@ class ClaudemanApp {
saveRespawnPresets(presets) { saveRespawnPresets(presets) {
// Only save custom presets (not built-in) // Only save custom presets (not built-in)
const custom = presets.filter(p => !p.builtIn); const custom = presets.filter(p => !p.builtIn);
localStorage.setItem('claudeman-respawn-presets', JSON.stringify(custom)); localStorage.setItem('codeman-respawn-presets', JSON.stringify(custom));
} }
renderPresetDropdown() { renderPresetDropdown() {
@@ -11842,7 +11842,7 @@ class ClaudemanApp {
// Get the settings storage key based on device type (mobile vs desktop) // Get the settings storage key based on device type (mobile vs desktop)
getSettingsStorageKey() { getSettingsStorageKey() {
const isMobile = MobileDetection.getDeviceType() === 'mobile'; const isMobile = MobileDetection.getDeviceType() === 'mobile';
return isMobile ? 'claudeman-app-settings-mobile' : 'claudeman-app-settings'; return isMobile ? 'codeman-app-settings-mobile' : 'codeman-app-settings';
} }
// Get default settings based on device type // Get default settings based on device type
@@ -12138,7 +12138,7 @@ class ClaudemanApp {
// Sync voice settings from server (seed localStorage if no local API key) // Sync voice settings from server (seed localStorage if no local API key)
if (voiceSettings) { if (voiceSettings) {
const localVoice = localStorage.getItem('claudeman-voice-settings'); const localVoice = localStorage.getItem('codeman-voice-settings');
if (!localVoice || !JSON.parse(localVoice).apiKey) { if (!localVoice || !JSON.parse(localVoice).apiKey) {
VoiceInput._saveDeepgramConfig(voiceSettings); VoiceInput._saveDeepgramConfig(voiceSettings);
} }
@@ -12179,7 +12179,7 @@ class ClaudemanApp {
const windowStates = { minimized: minimizedState, open: openWindows }; const windowStates = { minimized: minimizedState, open: openWindows };
// Save to localStorage for quick restore // Save to localStorage for quick restore
localStorage.setItem('claudeman-subagent-window-states', JSON.stringify(windowStates)); localStorage.setItem('codeman-subagent-window-states', JSON.stringify(windowStates));
// Save to server for cross-browser persistence // Save to server for cross-browser persistence
try { try {
@@ -12206,7 +12206,7 @@ class ClaudemanApp {
if (res.ok) { if (res.ok) {
states = await res.json(); states = await res.json();
// Also update localStorage // Also update localStorage
localStorage.setItem('claudeman-subagent-window-states', JSON.stringify(states)); localStorage.setItem('codeman-subagent-window-states', JSON.stringify(states));
} }
} catch (err) { } catch (err) {
console.error('Failed to load subagent window states from server:', err); console.error('Failed to load subagent window states from server:', err);
@@ -12215,7 +12215,7 @@ class ClaudemanApp {
// Fallback to localStorage // Fallback to localStorage
if (!states) { if (!states) {
try { try {
const saved = localStorage.getItem('claudeman-subagent-window-states'); const saved = localStorage.getItem('codeman-subagent-window-states');
if (saved) { if (saved) {
states = JSON.parse(saved); states = JSON.parse(saved);
} }
@@ -12321,7 +12321,7 @@ class ClaudemanApp {
// Save to localStorage for instant recovery // Save to localStorage for instant recovery
try { try {
localStorage.setItem('claudeman-subagent-parents', JSON.stringify(mapData)); localStorage.setItem('codeman-subagent-parents', JSON.stringify(mapData));
} catch (err) { } catch (err) {
console.error('Failed to save subagent parents to localStorage:', err); console.error('Failed to save subagent parents to localStorage:', err);
} }
@@ -12351,7 +12351,7 @@ class ClaudemanApp {
if (res.ok) { if (res.ok) {
mapData = await res.json(); mapData = await res.json();
// Update localStorage as cache // Update localStorage as cache
localStorage.setItem('claudeman-subagent-parents', JSON.stringify(mapData)); localStorage.setItem('codeman-subagent-parents', JSON.stringify(mapData));
} }
} catch (err) { } catch (err) {
console.error('Failed to load subagent parents from server:', err); console.error('Failed to load subagent parents from server:', err);
@@ -12360,7 +12360,7 @@ class ClaudemanApp {
// Fallback to localStorage // Fallback to localStorage
if (!mapData) { if (!mapData) {
try { try {
const saved = localStorage.getItem('claudeman-subagent-parents'); const saved = localStorage.getItem('codeman-subagent-parents');
if (saved) { if (saved) {
mapData = JSON.parse(saved); mapData = JSON.parse(saved);
} }
@@ -14670,8 +14670,8 @@ class ClaudemanApp {
const agent = this.subagents.get(agentId); const agent = this.subagents.get(agentId);
if (!agent) return; if (!agent) return;
// Only open windows for agents that belong to a Claudeman-managed session tab. // Only open windows for agents that belong to a Codeman-managed session tab.
// Agents from external Claude sessions (not tracked by Claudeman) should not pop up. // Agents from external Claude sessions (not tracked by Codeman) should not pop up.
if (agent.sessionId) { if (agent.sessionId) {
const hasMatchingTab = Array.from(this.sessions.values()).some(s => s.claudeSessionId === agent.sessionId); const hasMatchingTab = Array.from(this.sessions.values()).some(s => s.claudeSessionId === agent.sessionId);
if (!hasMatchingTab) return; if (!hasMatchingTab) return;
@@ -15610,7 +15610,7 @@ class ClaudemanApp {
/** Open a standalone terminal window for a tmux-pane teammate (no subagent entry needed) */ /** Open a standalone terminal window for a tmux-pane teammate (no subagent entry needed) */
openTeammateTerminalWindow(paneData) { openTeammateTerminalWindow(paneData) {
// Only open if the session has a tab in Claudeman // Only open if the session has a tab in Codeman
if (!this.sessions.has(paneData.sessionId)) return; if (!this.sessions.has(paneData.sessionId)) return;
// Use pane target as the unique ID for this window // Use pane target as the unique ID for this window
@@ -16831,7 +16831,7 @@ class ClaudemanApp {
this.sessions.clear(); this.sessions.clear();
this.muxSessions = []; this.muxSessions = [];
this.activeSessionId = null; this.activeSessionId = null;
try { localStorage.removeItem('claudeman-active-session'); } catch {} try { localStorage.removeItem('codeman-active-session'); } catch {}
this.renderSessionTabs(); this.renderSessionTabs();
this.renderMuxSessions(); this.renderMuxSessions();
this.terminal.clear(); this.terminal.clear();
@@ -16842,7 +16842,7 @@ class ClaudemanApp {
// Just remove tabs, keep mux sessions running // Just remove tabs, keep mux sessions running
this.sessions.clear(); this.sessions.clear();
this.activeSessionId = null; this.activeSessionId = null;
try { localStorage.removeItem('claudeman-active-session'); } catch {} try { localStorage.removeItem('codeman-active-session'); } catch {}
this.renderSessionTabs(); this.renderSessionTabs();
this.terminal.clear(); this.terminal.clear();
this.terminal.reset(); this.terminal.reset();
@@ -17554,12 +17554,25 @@ class ClaudemanApp {
escapeHtml(text) { escapeHtml(text) {
if (!text) return ''; if (!text) return '';
return text.replace(ClaudemanApp._htmlEscapePattern, char => ClaudemanApp._htmlEscapeMap[char]); return text.replace(CodemanApp._htmlEscapePattern, char => CodemanApp._htmlEscapeMap[char]);
} }
} }
// Migrate legacy localStorage keys (claudeman-* → codeman-*)
try {
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key && (key.startsWith('claudeman-') || key.startsWith('claudeman_'))) {
const newKey = key.replace(/^claudeman[-_]/, (m) => 'codeman' + m.charAt(m.length - 1));
if (localStorage.getItem(newKey) === null) {
localStorage.setItem(newKey, localStorage.getItem(key));
}
}
}
} catch {}
// Initialize // Initialize
const app = new ClaudemanApp(); const app = new CodemanApp();
// Expose for debugging/testing // Expose for debugging/testing
window.app = app; window.app = app;
+9 -9
View File
@@ -6,7 +6,7 @@
<meta name="description" content="Claude Code session manager with web interface"> <meta name="description" content="Claude Code session manager with web interface">
<meta name="theme-color" content="#0a0a0a"> <meta name="theme-color" content="#0a0a0a">
<meta name="google" content="notranslate"> <meta name="google" content="notranslate">
<title>Claudeman</title> <title>Codeman</title>
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Cdefs%3E%3ClinearGradient id='g' x1='0%25' y1='0%25' x2='100%25' y2='100%25'%3E%3Cstop offset='0%25' stop-color='%2360a5fa'/%3E%3Cstop offset='100%25' stop-color='%233b82f6'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='32' height='32' rx='6' fill='%230a0a0a'/%3E%3Cpath d='M18 4L8 18h6l-2 10 10-14h-6z' fill='url(%23g)'/%3E%3C/svg%3E"> <link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Cdefs%3E%3ClinearGradient id='g' x1='0%25' y1='0%25' x2='100%25' y2='100%25'%3E%3Cstop offset='0%25' stop-color='%2360a5fa'/%3E%3Cstop offset='100%25' stop-color='%233b82f6'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='32' height='32' rx='6' fill='%230a0a0a'/%3E%3Cpath d='M18 4L8 18h6l-2 10 10-14h-6z' fill='url(%23g)'/%3E%3C/svg%3E">
<link rel="stylesheet" href="styles.css?v=0.1631"> <link rel="stylesheet" href="styles.css?v=0.1631">
<link rel="stylesheet" href="mobile.css?v=0.1631" media="(max-width: 1023px)"> <link rel="stylesheet" href="mobile.css?v=0.1631" media="(max-width: 1023px)">
@@ -37,7 +37,7 @@
<!-- Loading skeleton — visible instantly before CSS/JS loads, hidden by app-loaded class --> <!-- Loading skeleton — visible instantly before CSS/JS loads, hidden by app-loaded class -->
<div class="loading-skeleton"> <div class="loading-skeleton">
<div class="skeleton-header"> <div class="skeleton-header">
<span class="skeleton-brand">Claudeman</span> <span class="skeleton-brand">Codeman</span>
<div class="skeleton-tabs"><div class="skeleton-tab"></div></div> <div class="skeleton-tabs"><div class="skeleton-tab"></div></div>
</div> </div>
<div class="skeleton-terminal"></div> <div class="skeleton-terminal"></div>
@@ -49,7 +49,7 @@
<!-- Compact Header with Session Tabs --> <!-- Compact Header with Session Tabs -->
<header class="header"> <header class="header">
<div class="header-brand"> <div class="header-brand">
<span class="logo" onclick="app.goHome()" title="Go to main page">Claudeman</span> <span class="logo" onclick="app.goHome()" title="Go to main page">Codeman</span>
</div> </div>
<!-- Session Tabs --> <!-- Session Tabs -->
@@ -231,7 +231,7 @@
<!-- Welcome Overlay (shown when no session active) --> <!-- Welcome Overlay (shown when no session active) -->
<div class="welcome-overlay" id="welcomeOverlay"> <div class="welcome-overlay" id="welcomeOverlay">
<div class="welcome-content"> <div class="welcome-content">
<h1 class="welcome-title">Claudeman</h1> <h1 class="welcome-title">Codeman</h1>
<p class="welcome-desc">Manage AI Coding tools in persistent tmux sessions.</p> <p class="welcome-desc">Manage AI Coding tools in persistent tmux sessions.</p>
<div class="welcome-actions"> <div class="welcome-actions">
<button class="welcome-btn welcome-btn-claude" onclick="app.setRunMode('claude'); app.runClaude()"> <button class="welcome-btn welcome-btn-claude" onclick="app.setRunMode('claude'); app.runClaude()">
@@ -379,7 +379,7 @@
</div> </div>
<div class="toolbar-center"> <div class="toolbar-center">
<span class="version-display" id="versionDisplay" title="Claudeman version">v0.0.0</span> <span class="version-display" id="versionDisplay" title="Codeman version">v0.0.0</span>
</div> </div>
<div class="toolbar-right"> <div class="toolbar-right">
@@ -1086,7 +1086,7 @@
<span class="settings-status" id="notifPermissionStatus">?</span> <span class="settings-status" id="notifPermissionStatus">?</span>
</div> </div>
</div> </div>
<span class="form-hint" style="grid-column: 1 / -1; margin-top: -8px;">For remote access, HTTPS is required. Start with: <code>claudeman web --https</code></span> <span class="form-hint" style="grid-column: 1 / -1; margin-top: -8px;">For remote access, HTTPS is required. Start with: <code>codeman web --https</code></span>
<!-- Alerts Section --> <!-- Alerts Section -->
<div class="settings-section-header">Alerts</div> <div class="settings-section-header">Alerts</div>
@@ -1222,7 +1222,7 @@
<div class="form-row" style="grid-column: 1 / -1;"> <div class="form-row" style="grid-column: 1 / -1;">
<label for="voiceKeyterms">Domain Keywords</label> <label for="voiceKeyterms">Domain Keywords</label>
<input type="text" id="voiceKeyterms" placeholder="claudeman, tmux, respawn, subagent" style="width: 100%; padding: 0.5rem 0.75rem; background: var(--bg-input); border: 1px solid var(--border); border-radius: 4px; color: var(--text); font-size: 0.8rem; font-family: inherit;"> <input type="text" id="voiceKeyterms" placeholder="codeman, tmux, respawn, subagent" style="width: 100%; padding: 0.5rem 0.75rem; background: var(--bg-input); border: 1px solid var(--border); border-radius: 4px; color: var(--text); font-size: 0.8rem; font-family: inherit;">
<span class="form-hint">Comma-separated terms to boost recognition accuracy</span> <span class="form-hint">Comma-separated terms to boost recognition accuracy</span>
</div> </div>
</div> </div>
@@ -1253,7 +1253,7 @@
<div class="form-row"> <div class="form-row">
<label>Case Name</label> <label>Case Name</label>
<input type="text" id="newCaseName" placeholder="my-project" pattern="[a-zA-Z0-9_-]+" autocomplete="off" autocapitalize="off" spellcheck="false"> <input type="text" id="newCaseName" placeholder="my-project" pattern="[a-zA-Z0-9_-]+" autocomplete="off" autocapitalize="off" spellcheck="false">
<span class="form-hint">Letters, numbers, hyphens, underscores only. Created in ~/claudeman-cases/</span> <span class="form-hint">Letters, numbers, hyphens, underscores only. Created in ~/codeman-cases/</span>
</div> </div>
<div class="form-row"> <div class="form-row">
<label>Description (optional)</label> <label>Description (optional)</label>
@@ -1265,7 +1265,7 @@
<div class="form-row"> <div class="form-row">
<label>Case Name</label> <label>Case Name</label>
<input type="text" id="linkCaseName" placeholder="my-project" pattern="[a-zA-Z0-9_-]+" autocomplete="off" autocapitalize="off" spellcheck="false"> <input type="text" id="linkCaseName" placeholder="my-project" pattern="[a-zA-Z0-9_-]+" autocomplete="off" autocapitalize="off" spellcheck="false">
<span class="form-hint">Name to identify this case in Claudeman</span> <span class="form-hint">Name to identify this case in Codeman</span>
</div> </div>
<div class="form-row"> <div class="form-row">
<label>Folder Path</label> <label>Folder Path</label>
+1 -1
View File
@@ -1,4 +1,4 @@
/* Claudeman - Terminal-First UI */ /* Codeman - Terminal-First UI */
:root { :root {
--bg-dark: #0a0a0a; --bg-dark: #0a0a0a;
+2 -2
View File
@@ -2,7 +2,7 @@
<html lang="en"><head> <html lang="en"><head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
<title>Upload Screenshot - Claudeman</title> <title>Upload Screenshot - Codeman</title>
<style> <style>
* { box-sizing: border-box; margin: 0; padding: 0; } * { box-sizing: border-box; margin: 0; padding: 0; }
body { body {
@@ -49,7 +49,7 @@
</style> </style>
</head><body> </head><body>
<div class="container"> <div class="container">
<a class="back" href="/">&larr; Back to Claudeman</a> <a class="back" href="/">&larr; Back to Codeman</a>
<h1>Upload Screenshot</h1> <h1>Upload Screenshot</h1>
<div class="drop-zone" id="drop"> <div class="drop-zone" id="drop">
<p>Tap to select image</p> <p>Tap to select image</p>
+1 -1
View File
@@ -39,7 +39,7 @@ const ALLOWED_ENV_PREFIXES = ['CLAUDE_CODE_', 'OPENCODE_'];
/** Env var keys that are always blocked (security-sensitive) */ /** Env var keys that are always blocked (security-sensitive) */
const BLOCKED_ENV_KEYS = new Set([ const BLOCKED_ENV_KEYS = new Set([
'PATH', 'LD_PRELOAD', 'LD_LIBRARY_PATH', 'NODE_OPTIONS', 'PATH', 'LD_PRELOAD', 'LD_LIBRARY_PATH', 'NODE_OPTIONS',
'CLAUDEMAN_MUX_NAME', 'CLAUDEMAN_TMUX', 'CODEMAN_MUX_NAME', 'CODEMAN_TMUX',
'OPENCODE_SERVER_PASSWORD', // Security-sensitive: server auth password 'OPENCODE_SERVER_PASSWORD', // Security-sensitive: server auth password
]); ]);
+35 -35
View File
@@ -1,5 +1,5 @@
/** /**
* @fileoverview Claudeman web server and REST API * @fileoverview Codeman web server and REST API
* *
* Provides a Fastify-based web server with: * Provides a Fastify-based web server with:
* - REST API for session management, respawn control, and monitoring * - REST API for session management, respawn control, and monitoring
@@ -142,7 +142,7 @@ const MAX_HOOK_DATA_SIZE = 8 * 1024;
// Maximum screenshot upload size (10MB) // Maximum screenshot upload size (10MB)
const MAX_SCREENSHOT_SIZE = 10 * 1024 * 1024; const MAX_SCREENSHOT_SIZE = 10 * 1024 * 1024;
// Screenshots directory // Screenshots directory
const SCREENSHOTS_DIR = join(homedir(), '.claudeman', 'screenshots'); const SCREENSHOTS_DIR = join(homedir(), '.codeman', 'screenshots');
// Stats collection interval (2 seconds) // Stats collection interval (2 seconds)
const STATS_COLLECTION_INTERVAL_MS = 2000; const STATS_COLLECTION_INTERVAL_MS = 2000;
// Session limit wait time before retrying (5 seconds) // Session limit wait time before retrying (5 seconds)
@@ -272,10 +272,10 @@ function autoConfigureRalph(session: Session, workingDir: string, broadcast: (ev
/** /**
* Get or generate a self-signed TLS certificate for HTTPS. * Get or generate a self-signed TLS certificate for HTTPS.
* Certs are stored in ~/.claudeman/certs/ and reused across restarts. * Certs are stored in ~/.codeman/certs/ and reused across restarts.
*/ */
function getOrCreateSelfSignedCert(): { key: string; cert: string } { function getOrCreateSelfSignedCert(): { key: string; cert: string } {
const certsDir = join(homedir(), '.claudeman', 'certs'); const certsDir = join(homedir(), '.codeman', 'certs');
const keyPath = join(certsDir, 'server.key'); const keyPath = join(certsDir, 'server.key');
const certPath = join(certsDir, 'server.crt'); const certPath = join(certsDir, 'server.crt');
@@ -292,7 +292,7 @@ function getOrCreateSelfSignedCert(): { key: string; cert: string } {
execSync( execSync(
`openssl req -x509 -newkey rsa:2048 -nodes ` + `openssl req -x509 -newkey rsa:2048 -nodes ` +
`-keyout "${keyPath}" -out "${certPath}" ` + `-keyout "${keyPath}" -out "${certPath}" ` +
`-days 365 -subj "/CN=claudeman" ` + `-days 365 -subj "/CN=codeman" ` +
`-addext "subjectAltName=DNS:localhost,IP:127.0.0.1,IP:0.0.0.0"`, `-addext "subjectAltName=DNS:localhost,IP:127.0.0.1,IP:0.0.0.0"`,
{ stdio: 'pipe' } { stdio: 'pipe' }
); );
@@ -582,10 +582,10 @@ export class WebServer extends EventEmitter {
threshold: 1024, threshold: 1024,
}); });
// Optional HTTP Basic Auth (set CLAUDEMAN_PASSWORD env var to enable) // Optional HTTP Basic Auth (set CODEMAN_PASSWORD env var to enable)
const authPassword = process.env.CLAUDEMAN_PASSWORD; const authPassword = process.env.CODEMAN_PASSWORD;
if (authPassword) { if (authPassword) {
const authUsername = process.env.CLAUDEMAN_USERNAME || 'admin'; const authUsername = process.env.CODEMAN_USERNAME || 'admin';
const expectedHeader = 'Basic ' + Buffer.from(`${authUsername}:${authPassword}`).toString('base64'); const expectedHeader = 'Basic ' + Buffer.from(`${authUsername}:${authPassword}`).toString('base64');
this.app.addHook('onRequest', (req, reply, done) => { this.app.addHook('onRequest', (req, reply, done) => {
const auth = req.headers.authorization; const auth = req.headers.authorization;
@@ -593,7 +593,7 @@ export class WebServer extends EventEmitter {
done(); done();
return; return;
} }
reply.header('WWW-Authenticate', 'Basic realm="Claudeman"'); reply.header('WWW-Authenticate', 'Basic realm="Codeman"');
reply.code(401).send('Unauthorized'); reply.code(401).send('Unauthorized');
}); });
} }
@@ -2350,7 +2350,7 @@ export class WebServer extends EventEmitter {
}); });
// Case management // Case management
const casesDir = join(homedir(), 'claudeman-cases'); const casesDir = join(homedir(), 'codeman-cases');
this.app.get('/api/cases', async (): Promise<CaseInfo[]> => { this.app.get('/api/cases', async (): Promise<CaseInfo[]> => {
const cases: CaseInfo[] = []; const cases: CaseInfo[] = [];
@@ -2372,7 +2372,7 @@ export class WebServer extends EventEmitter {
} }
// Get linked cases // Get linked cases
const linkedCasesFile = join(homedir(), '.claudeman', 'linked-cases.json'); const linkedCasesFile = join(homedir(), '.codeman', 'linked-cases.json');
try { try {
const linkedCases: Record<string, string> = JSON.parse(await fs.readFile(linkedCasesFile, 'utf-8')); const linkedCases: Record<string, string> = JSON.parse(await fs.readFile(linkedCasesFile, 'utf-8'));
for (const [name, path] of Object.entries(linkedCases)) { for (const [name, path] of Object.entries(linkedCases)) {
@@ -2456,11 +2456,11 @@ export class WebServer extends EventEmitter {
// Check if case name already exists in casesDir // Check if case name already exists in casesDir
const casePath = join(casesDir, name); const casePath = join(casesDir, name);
if (existsSync(casePath)) { if (existsSync(casePath)) {
return createErrorResponse(ApiErrorCode.ALREADY_EXISTS, 'A case with this name already exists in claudeman-cases.'); return createErrorResponse(ApiErrorCode.ALREADY_EXISTS, 'A case with this name already exists in codeman-cases.');
} }
// Load existing linked cases // Load existing linked cases
const linkedCasesFile = join(homedir(), '.claudeman', 'linked-cases.json'); const linkedCasesFile = join(homedir(), '.codeman', 'linked-cases.json');
let linkedCases: Record<string, string> = {}; let linkedCases: Record<string, string> = {};
try { try {
linkedCases = JSON.parse(await fs.readFile(linkedCasesFile, 'utf-8')); linkedCases = JSON.parse(await fs.readFile(linkedCasesFile, 'utf-8'));
@@ -2478,9 +2478,9 @@ export class WebServer extends EventEmitter {
// Save the linked case // Save the linked case
linkedCases[name] = expandedPath; linkedCases[name] = expandedPath;
try { try {
const claudemanDir = join(homedir(), '.claudeman'); const codemanDir = join(homedir(), '.codeman');
if (!existsSync(claudemanDir)) { if (!existsSync(codemanDir)) {
mkdirSync(claudemanDir, { recursive: true }); mkdirSync(codemanDir, { recursive: true });
} }
await fs.writeFile(linkedCasesFile, JSON.stringify(linkedCases, null, 2)); await fs.writeFile(linkedCasesFile, JSON.stringify(linkedCases, null, 2));
this.broadcast('case:linked', { name, path: expandedPath }); this.broadcast('case:linked', { name, path: expandedPath });
@@ -2494,7 +2494,7 @@ export class WebServer extends EventEmitter {
const { name } = req.params as { name: string }; const { name } = req.params as { name: string };
// First check linked cases // First check linked cases
const linkedCasesFile = join(homedir(), '.claudeman', 'linked-cases.json'); const linkedCasesFile = join(homedir(), '.codeman', 'linked-cases.json');
try { try {
const linkedCases: Record<string, string> = JSON.parse(await fs.readFile(linkedCasesFile, 'utf-8')); const linkedCases: Record<string, string> = JSON.parse(await fs.readFile(linkedCasesFile, 'utf-8'));
if (linkedCases[name]) { if (linkedCases[name]) {
@@ -2531,7 +2531,7 @@ export class WebServer extends EventEmitter {
// Get case path (check linked cases first, then casesDir) // Get case path (check linked cases first, then casesDir)
let casePath: string | null = null; let casePath: string | null = null;
const linkedCasesFile = join(homedir(), '.claudeman', 'linked-cases.json'); const linkedCasesFile = join(homedir(), '.codeman', 'linked-cases.json');
try { try {
const linkedCases: Record<string, string> = JSON.parse(await fs.readFile(linkedCasesFile, 'utf-8')); const linkedCases: Record<string, string> = JSON.parse(await fs.readFile(linkedCasesFile, 'utf-8'));
if (linkedCases[name]) { if (linkedCases[name]) {
@@ -2737,7 +2737,7 @@ export class WebServer extends EventEmitter {
// Save lastUsedCase to settings for TUI/web sync // Save lastUsedCase to settings for TUI/web sync
try { try {
const settingsFilePath = join(homedir(), '.claudeman', 'settings.json'); const settingsFilePath = join(homedir(), '.codeman', 'settings.json');
let settings: Record<string, unknown> = {}; let settings: Record<string, unknown> = {};
try { try {
settings = JSON.parse(await fs.readFile(settingsFilePath, 'utf-8')); settings = JSON.parse(await fs.readFile(settingsFilePath, 'utf-8'));
@@ -2974,7 +2974,7 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
// Determine output directory for saving wizard results // Determine output directory for saving wizard results
let outputDir: string | undefined; let outputDir: string | undefined;
if (caseName) { if (caseName) {
const casesDir = join(homedir(), 'claudeman-cases'); const casesDir = join(homedir(), 'codeman-cases');
const casePath = join(casesDir, caseName); const casePath = join(casesDir, caseName);
// Security: Path traversal protection - use relative path check // Security: Path traversal protection - use relative path check
const resolvedCase = resolve(casePath); const resolvedCase = resolve(casePath);
@@ -3101,7 +3101,7 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
// Get ralph-wizard files for a case (prompts and results) // Get ralph-wizard files for a case (prompts and results)
this.app.get('/api/cases/:caseName/ralph-wizard/files', async (req) => { this.app.get('/api/cases/:caseName/ralph-wizard/files', async (req) => {
const { caseName } = req.params as { caseName: string }; const { caseName } = req.params as { caseName: string };
const casesDir = join(homedir(), 'claudeman-cases'); const casesDir = join(homedir(), 'codeman-cases');
let casePath = join(casesDir, caseName); let casePath = join(casesDir, caseName);
// Security: Path traversal protection - use relative path check // Security: Path traversal protection - use relative path check
@@ -3114,7 +3114,7 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
// Check linked cases if path doesn't exist // Check linked cases if path doesn't exist
if (!existsSync(casePath)) { if (!existsSync(casePath)) {
const linkedCasesFile = join(homedir(), '.claudeman', 'linked-cases.json'); const linkedCasesFile = join(homedir(), '.codeman', 'linked-cases.json');
try { try {
const linkedCases: Record<string, string> = JSON.parse(await fs.readFile(linkedCasesFile, 'utf-8')); const linkedCases: Record<string, string> = JSON.parse(await fs.readFile(linkedCasesFile, 'utf-8'));
if (linkedCases[caseName]) { if (linkedCases[caseName]) {
@@ -3162,7 +3162,7 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
// Cache disabled to ensure fresh prompts when starting new plan generations // Cache disabled to ensure fresh prompts when starting new plan generations
this.app.get('/api/cases/:caseName/ralph-wizard/file/:filePath', async (req, reply) => { this.app.get('/api/cases/:caseName/ralph-wizard/file/:filePath', async (req, reply) => {
const { caseName, filePath } = req.params as { caseName: string; filePath: string }; const { caseName, filePath } = req.params as { caseName: string; filePath: string };
const casesDir = join(homedir(), 'claudeman-cases'); const casesDir = join(homedir(), 'codeman-cases');
let casePath = join(casesDir, caseName); let casePath = join(casesDir, caseName);
// Prevent browser caching - prompts change between plan generations // Prevent browser caching - prompts change between plan generations
@@ -3180,7 +3180,7 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
// Check linked cases if path doesn't exist // Check linked cases if path doesn't exist
if (!existsSync(casePath)) { if (!existsSync(casePath)) {
const linkedCasesFile = join(homedir(), '.claudeman', 'linked-cases.json'); const linkedCasesFile = join(homedir(), '.codeman', 'linked-cases.json');
try { try {
const linkedCases: Record<string, string> = JSON.parse(await fs.readFile(linkedCasesFile, 'utf-8')); const linkedCases: Record<string, string> = JSON.parse(await fs.readFile(linkedCasesFile, 'utf-8'));
if (linkedCases[caseName]) { if (linkedCases[caseName]) {
@@ -3369,7 +3369,7 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
}); });
// ============ App Settings Endpoints ============ // ============ App Settings Endpoints ============
const settingsPath = join(homedir(), '.claudeman', 'settings.json'); const settingsPath = join(homedir(), '.codeman', 'settings.json');
this.app.get('/api/settings', async () => { this.app.get('/api/settings', async () => {
try { try {
@@ -3520,7 +3520,7 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
// ============ Subagent Window State Endpoints ============ // ============ Subagent Window State Endpoints ============
// Persists minimized/open window states for cross-browser sync // Persists minimized/open window states for cross-browser sync
const windowStatesPath = join(homedir(), '.claudeman', 'subagent-window-states.json'); const windowStatesPath = join(homedir(), '.codeman', 'subagent-window-states.json');
this.app.get('/api/subagent-window-states', async () => { this.app.get('/api/subagent-window-states', async () => {
try { try {
@@ -3555,7 +3555,7 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
// ============ Subagent Parent Associations ============ // ============ Subagent Parent Associations ============
// Persists which TAB each agent window connects to. // Persists which TAB each agent window connects to.
// This is the PERMANENT record of agent -> tab associations. // This is the PERMANENT record of agent -> tab associations.
const parentMapPath = join(homedir(), '.claudeman', 'subagent-parents.json'); const parentMapPath = join(homedir(), '.codeman', 'subagent-parents.json');
this.app.get('/api/subagent-parents', async () => { this.app.get('/api/subagent-parents', async () => {
try { try {
@@ -3836,7 +3836,7 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
ext = map[ctMatch[1].toLowerCase()] ?? ext; ext = map[ctMatch[1].toLowerCase()] ?? ext;
} }
// Save to ~/.claudeman/screenshots/ // Save to ~/.codeman/screenshots/
if (!existsSync(SCREENSHOTS_DIR)) { if (!existsSync(SCREENSHOTS_DIR)) {
mkdirSync(SCREENSHOTS_DIR, { recursive: true }); mkdirSync(SCREENSHOTS_DIR, { recursive: true });
} }
@@ -4707,7 +4707,7 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
// Helper to get custom CLAUDE.md template path from settings // Helper to get custom CLAUDE.md template path from settings
private async getDefaultClaudeMdPath(): Promise<string | undefined> { private async getDefaultClaudeMdPath(): Promise<string | undefined> {
const settingsPath = join(homedir(), '.claudeman', 'settings.json'); const settingsPath = join(homedir(), '.codeman', 'settings.json');
try { try {
const content = await fs.readFile(settingsPath, 'utf-8'); const content = await fs.readFile(settingsPath, 'utf-8');
@@ -4723,7 +4723,7 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
return undefined; return undefined;
} }
// Read ~/.claudeman/settings.json once and return the parsed object. // Read ~/.codeman/settings.json once and return the parsed object.
// Cached for 2s to avoid redundant reads during session creation bursts. // Cached for 2s to avoid redundant reads during session creation bursts.
private _settingsCache: { data: Record<string, unknown>; ts: number } | null = null; private _settingsCache: { data: Record<string, unknown>; ts: number } | null = null;
private async readSettings(): Promise<Record<string, unknown>> { private async readSettings(): Promise<Record<string, unknown>> {
@@ -4731,7 +4731,7 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
if (this._settingsCache && now - this._settingsCache.ts < 2000) { if (this._settingsCache && now - this._settingsCache.ts < 2000) {
return this._settingsCache.data; return this._settingsCache.data;
} }
const settingsPath = join(homedir(), '.claudeman', 'settings.json'); const settingsPath = join(homedir(), '.codeman', 'settings.json');
try { try {
const content = await fs.readFile(settingsPath, 'utf-8'); const content = await fs.readFile(settingsPath, 'utf-8');
const data = JSON.parse(content) as Record<string, unknown>; const data = JSON.parse(content) as Record<string, unknown>;
@@ -5293,10 +5293,10 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
await this.app.listen({ port: this.port, host: '0.0.0.0' }); await this.app.listen({ port: this.port, host: '0.0.0.0' });
const protocol = this.https ? 'https' : 'http'; const protocol = this.https ? 'https' : 'http';
console.log(`Claudeman web interface running at ${protocol}://localhost:${this.port}`); console.log(`Codeman web interface running at ${protocol}://localhost:${this.port}`);
// Set API URL for child processes (MCP server, spawned sessions) // Set API URL for child processes (MCP server, spawned sessions)
process.env.CLAUDEMAN_API_URL = `${protocol}://localhost:${this.port}`; process.env.CODEMAN_API_URL = `${protocol}://localhost:${this.port}`;
// Start scheduled runs cleanup timer // Start scheduled runs cleanup timer
this.scheduledCleanupTimer = setInterval(() => { this.scheduledCleanupTimer = setInterval(() => {
@@ -5339,7 +5339,7 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
* Check if subagent tracking is enabled in settings (default: true) * Check if subagent tracking is enabled in settings (default: true)
*/ */
private async isSubagentTrackingEnabled(): Promise<boolean> { private async isSubagentTrackingEnabled(): Promise<boolean> {
const settingsPath = join(homedir(), '.claudeman', 'settings.json'); const settingsPath = join(homedir(), '.codeman', 'settings.json');
try { try {
const content = await fs.readFile(settingsPath, 'utf-8'); const content = await fs.readFile(settingsPath, 'utf-8');
const settings = JSON.parse(content); const settings = JSON.parse(content);
@@ -5357,7 +5357,7 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
* Check if image watcher is enabled in settings (default: false) * Check if image watcher is enabled in settings (default: false)
*/ */
private async isImageWatcherEnabled(): Promise<boolean> { private async isImageWatcherEnabled(): Promise<boolean> {
const settingsPath = join(homedir(), '.claudeman', 'settings.json'); const settingsPath = join(homedir(), '.codeman', 'settings.json');
try { try {
const content = await fs.readFile(settingsPath, 'utf-8'); const content = await fs.readFile(settingsPath, 'utf-8');
const settings = JSON.parse(content); const settings = JSON.parse(content);
+3 -3
View File
@@ -149,7 +149,7 @@ describe('AiIdleChecker', () => {
// Verify tmux was spawned with correct args // Verify tmux was spawned with correct args
expect(mockedSpawn).toHaveBeenCalledWith( expect(mockedSpawn).toHaveBeenCalledWith(
'tmux', 'tmux',
expect.arrayContaining(['new-session', '-d', '-s', expect.stringContaining('claudeman-aicheck-')]), expect.arrayContaining(['new-session', '-d', '-s', expect.stringContaining('codeman-aicheck-')]),
expect.objectContaining({ detached: true, stdio: 'ignore' }) expect.objectContaining({ detached: true, stdio: 'ignore' })
); );
}); });
@@ -163,7 +163,7 @@ describe('AiIdleChecker', () => {
// First call should try to kill existing session // First call should try to kill existing session
expect(mockedExecSync).toHaveBeenCalledWith( expect(mockedExecSync).toHaveBeenCalledWith(
expect.stringContaining('tmux kill-session -t "claudeman-aicheck-'), expect.stringContaining('tmux kill-session -t "codeman-aicheck-'),
expect.any(Object) expect.any(Object)
); );
}); });
@@ -176,7 +176,7 @@ describe('AiIdleChecker', () => {
await checkPromise; await checkPromise;
expect(mockedWriteFileSync).toHaveBeenCalledWith( expect(mockedWriteFileSync).toHaveBeenCalledWith(
expect.stringContaining('claudeman-aicheck-'), expect.stringContaining('codeman-aicheck-'),
'' ''
); );
}); });
+2 -2
View File
@@ -537,7 +537,7 @@ describe('API Response Structures', () => {
it('should validate case info', () => { it('should validate case info', () => {
const caseInfo: CaseInfo = { const caseInfo: CaseInfo = {
name: 'my-project', name: 'my-project',
path: '/home/user/claudeman-cases/my-project', path: '/home/user/codeman-cases/my-project',
hasClaudeMd: true, hasClaudeMd: true,
}; };
@@ -549,7 +549,7 @@ describe('API Response Structures', () => {
it('should handle optional hasClaudeMd', () => { it('should handle optional hasClaudeMd', () => {
const caseInfo: CaseInfo = { const caseInfo: CaseInfo = {
name: 'new-project', name: 'new-project',
path: '/home/user/claudeman-cases/new-project', path: '/home/user/codeman-cases/new-project',
}; };
expect(caseInfo.hasClaudeMd).toBeUndefined(); expect(caseInfo.hasClaudeMd).toBeUndefined();
+1 -1
View File
@@ -1,7 +1,7 @@
/** /**
* @fileoverview Tests for buffer management and memory limits * @fileoverview Tests for buffer management and memory limits
* *
* Tests the buffer management strategies used in Claudeman sessions * Tests the buffer management strategies used in Codeman sessions
* for handling large terminal output and message storage. * for handling large terminal output and message storage.
*/ */
+6 -6
View File
@@ -318,7 +318,7 @@ describe('CLI Command Parsing', () => {
describe('Help Text Generation', () => { describe('Help Text Generation', () => {
const generateHelp = (command: string, description: string, options: string[]): string => { const generateHelp = (command: string, description: string, options: string[]): string => {
let help = `Usage: claudeman ${command}\n\n`; let help = `Usage: codeman ${command}\n\n`;
help += `${description}\n`; help += `${description}\n`;
if (options.length > 0) { if (options.length > 0) {
help += '\nOptions:\n'; help += '\nOptions:\n';
@@ -331,7 +331,7 @@ describe('CLI Command Parsing', () => {
it('should generate basic help', () => { it('should generate basic help', () => {
const help = generateHelp('status', 'Show overall status', []); const help = generateHelp('status', 'Show overall status', []);
expect(help).toContain('Usage: claudeman status'); expect(help).toContain('Usage: codeman status');
expect(help).toContain('Show overall status'); expect(help).toContain('Show overall status');
}); });
@@ -724,8 +724,8 @@ describe('CLI Configuration', () => {
maxConcurrentSessions: 5, maxConcurrentSessions: 5,
defaultPort: 3000, defaultPort: 3000,
defaultHost: '0.0.0.0', defaultHost: '0.0.0.0',
casesDirectory: '~/claudeman-cases', casesDirectory: '~/codeman-cases',
stateDirectory: '~/.claudeman', stateDirectory: '~/.codeman',
}; };
const expandPath = (path: string): string => { const expandPath = (path: string): string => {
@@ -742,8 +742,8 @@ describe('CLI Configuration', () => {
}); });
it('should expand home directory paths', () => { it('should expand home directory paths', () => {
expect(expandPath('~/claudeman-cases')).toBe('/home/user/claudeman-cases'); expect(expandPath('~/codeman-cases')).toBe('/home/user/codeman-cases');
expect(expandPath('~/.claudeman')).toBe('/home/user/.claudeman'); expect(expandPath('~/.codeman')).toBe('/home/user/.codeman');
}); });
it('should not modify absolute paths', () => { it('should not modify absolute paths', () => {
+2 -2
View File
@@ -5,7 +5,7 @@ import { join } from 'node:path';
import { homedir } from 'node:os'; import { homedir } from 'node:os';
const TEST_PORT = 3110; const TEST_PORT = 3110;
const CASES_DIR = join(homedir(), 'claudeman-cases'); const CASES_DIR = join(homedir(), 'codeman-cases');
describe('Edge Cases and Error Handling', () => { describe('Edge Cases and Error Handling', () => {
let server: WebServer; let server: WebServer;
@@ -349,7 +349,7 @@ describe('Concurrent Session Handling', () => {
const { join } = await import('node:path'); const { join } = await import('node:path');
const { homedir } = await import('node:os'); const { homedir } = await import('node:os');
for (const name of createdCases) { for (const name of createdCases) {
const path = join(homedir(), 'claudeman-cases', name); const path = join(homedir(), 'codeman-cases', name);
if (existsSync(path)) { if (existsSync(path)) {
rmSync(path, { recursive: true, force: true }); rmSync(path, { recursive: true, force: true });
} }
+3 -3
View File
@@ -1,5 +1,5 @@
/** /**
* File Link Click Tests for Claudeman Web UI * File Link Click Tests for Codeman Web UI
* *
* Tests that file paths displayed in terminal output are clickable * Tests that file paths displayed in terminal output are clickable
* and open the log viewer window correctly. * and open the log viewer window correctly.
@@ -88,7 +88,7 @@ describe('File Link Click Tests', () => {
closeBrowser(); closeBrowser();
// Create test directory and log file // Create test directory and log file
testDir = join(tmpdir(), `claudeman-link-test-${Date.now()}`); testDir = join(tmpdir(), `codeman-link-test-${Date.now()}`);
mkdirSync(testDir, { recursive: true }); mkdirSync(testDir, { recursive: true });
testLogFile = join(testDir, 'test.log'); testLogFile = join(testDir, 'test.log');
writeFileSync(testLogFile, '=== Test Log Started ===\n'); writeFileSync(testLogFile, '=== Test Log Started ===\n');
@@ -102,7 +102,7 @@ describe('File Link Click Tests', () => {
browser(`open ${baseUrl}`); browser(`open ${baseUrl}`);
await new Promise(r => setTimeout(r, 2000)); await new Promise(r => setTimeout(r, 2000));
const title = browserJson<{ title: string }>('get title'); const title = browserJson<{ title: string }>('get title');
browserAvailable = title.title === 'Claudeman'; browserAvailable = title.title === 'Codeman';
} catch (e) { } catch (e) {
console.warn('Browser not available, skipping browser tests:', (e as Error).message); console.warn('Browser not available, skipping browser tests:', (e as Error).message);
browserAvailable = false; browserAvailable = false;
+5 -5
View File
@@ -54,8 +54,8 @@ describe('generateHooksConfig', () => {
const config = generateHooksConfig(); const config = generateHooksConfig();
const notifHooks = config.hooks.Notification as Array<{ hooks: Array<{ command: string }> }>; const notifHooks = config.hooks.Notification as Array<{ hooks: Array<{ command: string }> }>;
const cmd = notifHooks[0].hooks[0].command; const cmd = notifHooks[0].hooks[0].command;
expect(cmd).toContain('$CLAUDEMAN_API_URL'); expect(cmd).toContain('$CODEMAN_API_URL');
expect(cmd).toContain('$CLAUDEMAN_SESSION_ID'); expect(cmd).toContain('$CODEMAN_SESSION_ID');
expect(cmd).not.toContain('localhost'); expect(cmd).not.toContain('localhost');
}); });
@@ -99,7 +99,7 @@ describe('generateHooksConfig', () => {
}); });
describe('writeHooksConfig', () => { describe('writeHooksConfig', () => {
const testDir = join(tmpdir(), 'claudeman-hooks-test-' + Date.now()); const testDir = join(tmpdir(), 'codeman-hooks-test-' + Date.now());
beforeEach(() => { beforeEach(() => {
if (!existsSync(testDir)) { if (!existsSync(testDir)) {
@@ -648,8 +648,8 @@ describe('Hook Config Generation - Extended', () => {
const config = generateHooksConfig(); const config = generateHooksConfig();
const notifHooks = config.hooks.Notification as Array<{ hooks: Array<{ command: string }> }>; const notifHooks = config.hooks.Notification as Array<{ hooks: Array<{ command: string }> }>;
const cmd = notifHooks[0].hooks[0].command; const cmd = notifHooks[0].hooks[0].command;
expect(cmd).toContain('$CLAUDEMAN_API_URL'); expect(cmd).toContain('$CODEMAN_API_URL');
expect(cmd).toContain('$CLAUDEMAN_SESSION_ID'); expect(cmd).toContain('$CODEMAN_SESSION_ID');
}); });
it('should generate POST curl commands', () => { it('should generate POST curl commands', () => {
+1 -1
View File
@@ -96,7 +96,7 @@ async function main() {
page.on('console', msg => consoleLogs.push(`[${msg.type()}] ${msg.text()}`)); page.on('console', msg => consoleLogs.push(`[${msg.type()}] ${msg.text()}`));
try { try {
console.log('Opening Claudeman...'); console.log('Opening Codeman...');
await page.goto(BASE); await page.goto(BASE);
await page.waitForSelector('.xterm-screen', { timeout: 10000 }); await page.waitForSelector('.xterm-screen', { timeout: 10000 });
await page.waitForTimeout(2000); await page.waitForTimeout(2000);
+1 -1
View File
@@ -5,7 +5,7 @@ import { join } from 'node:path';
import { homedir } from 'node:os'; import { homedir } from 'node:os';
const TEST_PORT = 3115; const TEST_PORT = 3115;
const CASES_DIR = join(homedir(), 'claudeman-cases'); const CASES_DIR = join(homedir(), 'codeman-cases');
/** /**
* Integration tests for complete user flows * Integration tests for complete user flows
+2 -2
View File
@@ -149,9 +149,9 @@ async function main() {
// Enable local echo // Enable local echo
await page.evaluate(() => { await page.evaluate(() => {
const s = JSON.parse(localStorage.getItem('claudeman-app-settings') || '{}'); const s = JSON.parse(localStorage.getItem('codeman-app-settings') || '{}');
s.localEchoEnabled = true; s.localEchoEnabled = true;
localStorage.setItem('claudeman-app-settings', JSON.stringify(s)); localStorage.setItem('codeman-app-settings', JSON.stringify(s));
window.app?._updateLocalEchoState?.(); window.app?._updateLocalEchoState?.();
}); });
+2 -2
View File
@@ -117,9 +117,9 @@ async function main() {
// Enable local echo // Enable local echo
await page.evaluate(() => { await page.evaluate(() => {
const s = JSON.parse(localStorage.getItem('claudeman-app-settings') || '{}'); const s = JSON.parse(localStorage.getItem('codeman-app-settings') || '{}');
s.localEchoEnabled = true; s.localEchoEnabled = true;
localStorage.setItem('claudeman-app-settings', JSON.stringify(s)); localStorage.setItem('codeman-app-settings', JSON.stringify(s));
window.app?._updateLocalEchoState?.(); window.app?._updateLocalEchoState?.();
}); });
+3 -3
View File
@@ -120,9 +120,9 @@ async function main() {
// Enable local echo // Enable local echo
await page.evaluate(() => { await page.evaluate(() => {
const s = JSON.parse(localStorage.getItem('claudeman-app-settings') || '{}'); const s = JSON.parse(localStorage.getItem('codeman-app-settings') || '{}');
s.localEchoEnabled = true; s.localEchoEnabled = true;
localStorage.setItem('claudeman-app-settings', JSON.stringify(s)); localStorage.setItem('codeman-app-settings', JSON.stringify(s));
window.app?._updateLocalEchoState?.(); window.app?._updateLocalEchoState?.();
}); });
@@ -307,7 +307,7 @@ async function main() {
await page.keyboard.type('will-be-lost', { delay: 20 }); await page.keyboard.type('will-be-lost', { delay: 20 });
await page.waitForTimeout(200); await page.waitForTimeout(200);
const lsBefore = await page.evaluate(() => localStorage.getItem('claudeman_local_echo_pending')); const lsBefore = await page.evaluate(() => localStorage.getItem('codeman_local_echo_pending'));
if (!lsBefore) pass('localStorage not written (persist is no-op)'); if (!lsBefore) pass('localStorage not written (persist is no-op)');
else fail('localStorage written', `value="${lsBefore}"`); else fail('localStorage written', `value="${lsBefore}"`);
+3 -3
View File
@@ -227,7 +227,7 @@ async function main() {
try { try {
// ── SETUP ── // ── SETUP ──
console.log('Opening Claudeman...'); console.log('Opening Codeman...');
await page.goto(BASE, { waitUntil: 'domcontentloaded' }); await page.goto(BASE, { waitUntil: 'domcontentloaded' });
await page.waitForSelector('.xterm-screen', { timeout: 10000 }); await page.waitForSelector('.xterm-screen', { timeout: 10000 });
await page.waitForTimeout(2000); await page.waitForTimeout(2000);
@@ -235,9 +235,9 @@ async function main() {
// Enable local echo via settings // Enable local echo via settings
console.log('Enabling local echo...'); console.log('Enabling local echo...');
await page.evaluate(() => { await page.evaluate(() => {
const settings = JSON.parse(localStorage.getItem('claudeman-app-settings') || '{}'); const settings = JSON.parse(localStorage.getItem('codeman-app-settings') || '{}');
settings.localEchoEnabled = true; settings.localEchoEnabled = true;
localStorage.setItem('claudeman-app-settings', JSON.stringify(settings)); localStorage.setItem('codeman-app-settings', JSON.stringify(settings));
window.app?._updateLocalEchoState?.(); window.app?._updateLocalEchoState?.();
}); });
await page.waitForTimeout(300); await page.waitForTimeout(300);
+2 -2
View File
@@ -126,9 +126,9 @@ async function main() {
// Enable local echo // Enable local echo
await page.evaluate(() => { await page.evaluate(() => {
const s = JSON.parse(localStorage.getItem('claudeman-app-settings') || '{}'); const s = JSON.parse(localStorage.getItem('codeman-app-settings') || '{}');
s.localEchoEnabled = true; s.localEchoEnabled = true;
localStorage.setItem('claudeman-app-settings', JSON.stringify(s)); localStorage.setItem('codeman-app-settings', JSON.stringify(s));
window.app?._updateLocalEchoState?.(); window.app?._updateLocalEchoState?.();
}); });
+2 -2
View File
@@ -144,7 +144,7 @@ async function main() {
try { try {
// ── SETUP ── // ── SETUP ──
console.log('Opening Claudeman...'); console.log('Opening Codeman...');
await page.goto(BASE, { waitUntil: 'domcontentloaded' }); await page.goto(BASE, { waitUntil: 'domcontentloaded' });
await page.waitForSelector('.xterm-screen', { timeout: 10000 }); await page.waitForSelector('.xterm-screen', { timeout: 10000 });
await page.waitForTimeout(2500); await page.waitForTimeout(2500);
@@ -152,7 +152,7 @@ async function main() {
// Enable local echo // Enable local echo
console.log('Enabling local echo...'); console.log('Enabling local echo...');
await page.evaluate(() => { await page.evaluate(() => {
const key = window.app?.getSettingsStorageKey?.() || 'claudeman-app-settings'; const key = window.app?.getSettingsStorageKey?.() || 'codeman-app-settings';
const settings = JSON.parse(localStorage.getItem(key) || '{}'); const settings = JSON.parse(localStorage.getItem(key) || '{}');
settings.localEchoEnabled = true; settings.localEchoEnabled = true;
localStorage.setItem(key, JSON.stringify(settings)); localStorage.setItem(key, JSON.stringify(settings));
+4 -4
View File
@@ -241,9 +241,9 @@ async function main() {
// Enable local echo // Enable local echo
await page.evaluate(() => { await page.evaluate(() => {
const s = JSON.parse(localStorage.getItem('claudeman-app-settings') || '{}'); const s = JSON.parse(localStorage.getItem('codeman-app-settings') || '{}');
s.localEchoEnabled = true; s.localEchoEnabled = true;
localStorage.setItem('claudeman-app-settings', JSON.stringify(s)); localStorage.setItem('codeman-app-settings', JSON.stringify(s));
window.app?._updateLocalEchoState?.(); window.app?._updateLocalEchoState?.();
}); });
@@ -916,9 +916,9 @@ async function main() {
await page.evaluate((id) => document.querySelector(`.session-tab[data-id="${id}"]`)?.click(), sA); await page.evaluate((id) => document.querySelector(`.session-tab[data-id="${id}"]`)?.click(), sA);
await page.waitForTimeout(2000); await page.waitForTimeout(2000);
await page.evaluate(() => { await page.evaluate(() => {
const s = JSON.parse(localStorage.getItem('claudeman-app-settings') || '{}'); const s = JSON.parse(localStorage.getItem('codeman-app-settings') || '{}');
s.localEchoEnabled = true; s.localEchoEnabled = true;
localStorage.setItem('claudeman-app-settings', JSON.stringify(s)); localStorage.setItem('codeman-app-settings', JSON.stringify(s));
window.app?._updateLocalEchoState?.(); window.app?._updateLocalEchoState?.();
}); });
await page.waitForTimeout(300); await page.waitForTimeout(300);
+1 -1
View File
@@ -2,7 +2,7 @@
* @fileoverview Tests for memory leak prevention patterns * @fileoverview Tests for memory leak prevention patterns
* *
* These tests verify the cleanup patterns used to prevent memory leaks * These tests verify the cleanup patterns used to prevent memory leaks
* in long-running Claudeman sessions (P1 improvements from P0 fixes). * in long-running Codeman sessions (P1 improvements from P0 fixes).
* *
* Port: N/A (unit tests, no server) * Port: N/A (unit tests, no server)
*/ */
+2 -2
View File
@@ -1,5 +1,5 @@
/** /**
* Performance tests: Claudeman responsiveness under subagent load * Performance tests: Codeman responsiveness under subagent load
* *
* Measures event loop lag caused by: * Measures event loop lag caused by:
* 1. SubagentWatcher processing many agent files simultaneously * 1. SubagentWatcher processing many agent files simultaneously
@@ -112,7 +112,7 @@ describe('SubagentWatcher performance', () => {
let projectsDir: string; let projectsDir: string;
beforeEach(() => { beforeEach(() => {
tmpDir = mkdtempSync(join(tmpdir(), 'claudeman-perf-')); tmpDir = mkdtempSync(join(tmpdir(), 'codeman-perf-'));
projectsDir = join(tmpDir, 'projects'); projectsDir = join(tmpDir, 'projects');
mkdirSync(projectsDir, { recursive: true }); mkdirSync(projectsDir, { recursive: true });
}); });
+1 -1
View File
@@ -5,7 +5,7 @@ import { join } from 'node:path';
import { homedir } from 'node:os'; import { homedir } from 'node:os';
const TEST_PORT = 3099; const TEST_PORT = 3099;
const CASES_DIR = join(homedir(), 'claudeman-cases'); const CASES_DIR = join(homedir(), 'codeman-cases');
describe('Quick Start API', () => { describe('Quick Start API', () => {
let server: WebServer; let server: WebServer;
+1 -1
View File
@@ -18,7 +18,7 @@ import { join } from 'node:path';
import { homedir } from 'node:os'; import { homedir } from 'node:os';
const TEST_PORT = 3125; const TEST_PORT = 3125;
const CASES_DIR = join(homedir(), 'claudeman-cases'); const CASES_DIR = join(homedir(), 'codeman-cases');
describe('Ralph Integration Tests', () => { describe('Ralph Integration Tests', () => {
let server: WebServer; let server: WebServer;
+11 -11
View File
@@ -2,10 +2,10 @@
**Date**: 2026-01-25 **Date**: 2026-01-25
**Analyzed Files**: **Analyzed Files**:
- `/home/arkon/default/claudeman/src/respawn-controller.ts` - `/home/arkon/default/codeman/src/respawn-controller.ts`
- `/home/arkon/default/claudeman/src/ai-idle-checker.ts` - `/home/arkon/default/codeman/src/ai-idle-checker.ts`
- `/home/arkon/default/claudeman/src/ai-plan-checker.ts` - `/home/arkon/default/codeman/src/ai-plan-checker.ts`
- `/home/arkon/default/claudeman/test/respawn-controller.test.ts` - `/home/arkon/default/codeman/test/respawn-controller.test.ts`
--- ---
@@ -55,7 +55,7 @@ The following areas lack test coverage:
### Issue 1: E2BIG Error in ai-plan-checker.ts (HIGH) ### Issue 1: E2BIG Error in ai-plan-checker.ts (HIGH)
**File**: `/home/arkon/default/claudeman/src/ai-plan-checker.ts` **File**: `/home/arkon/default/codeman/src/ai-plan-checker.ts`
**Lines**: 341-346 **Lines**: 341-346
**Severity**: HIGH **Severity**: HIGH
@@ -84,7 +84,7 @@ const claudeCmd = `cat "${this.checkPromptFile}" | claude -p ${modelArg} --outpu
### Issue 2: Missing Prompt File Cleanup on Timeout (MEDIUM) ### Issue 2: Missing Prompt File Cleanup on Timeout (MEDIUM)
**File**: `/home/arkon/default/claudeman/src/ai-idle-checker.ts` **File**: `/home/arkon/default/codeman/src/ai-idle-checker.ts`
**Lines**: 392-398 **Lines**: 392-398
**Severity**: MEDIUM **Severity**: MEDIUM
@@ -98,7 +98,7 @@ const claudeCmd = `cat "${this.checkPromptFile}" | claude -p ${modelArg} --outpu
### Issue 3: Race Condition in cancel() with Promise Resolution (MEDIUM) ### Issue 3: Race Condition in cancel() with Promise Resolution (MEDIUM)
**File**: `/home/arkon/default/claudeman/src/ai-idle-checker.ts` **File**: `/home/arkon/default/codeman/src/ai-idle-checker.ts`
**Lines**: 265-279 **Lines**: 265-279
**Severity**: MEDIUM **Severity**: MEDIUM
@@ -127,7 +127,7 @@ cancel(): void {
### Issue 4: Stale Screen Name Collision (LOW) ### Issue 4: Stale Screen Name Collision (LOW)
**File**: `/home/arkon/default/claudeman/src/ai-idle-checker.ts`, `ai-plan-checker.ts` **File**: `/home/arkon/default/codeman/src/ai-idle-checker.ts`, `ai-plan-checker.ts`
**Lines**: 326-329 (idle), 333-336 (plan) **Lines**: 326-329 (idle), 333-336 (plan)
**Severity**: LOW **Severity**: LOW
@@ -135,21 +135,21 @@ cancel(): void {
**Code**: **Code**:
```typescript ```typescript
this.checkScreenName = `claudeman-aicheck-${shortId}`; this.checkScreenName = `codeman-aicheck-${shortId}`;
``` ```
**Impact**: Very unlikely in practice since checks have cooldowns, but theoretically possible. **Impact**: Very unlikely in practice since checks have cooldowns, but theoretically possible.
**Suggested Fix**: Already partially mitigated by the `timestamp` in temp file names. Could add timestamp to screen name too: **Suggested Fix**: Already partially mitigated by the `timestamp` in temp file names. Could add timestamp to screen name too:
```typescript ```typescript
this.checkScreenName = `claudeman-aicheck-${shortId}-${timestamp}`; this.checkScreenName = `codeman-aicheck-${shortId}-${timestamp}`;
``` ```
--- ---
### Issue 5: DetectionStatus Calculation During ai_checking State (LOW) ### Issue 5: DetectionStatus Calculation During ai_checking State (LOW)
**File**: `/home/arkon/default/claudeman/src/respawn-controller.ts` **File**: `/home/arkon/default/codeman/src/respawn-controller.ts`
**Lines**: 759-762 **Lines**: 759-762
**Severity**: LOW **Severity**: LOW
+1 -1
View File
@@ -243,7 +243,7 @@ afterEach(() => {
The global `test/setup.ts` handles: The global `test/setup.ts` handles:
1. **Screen Cleanup**: Kills any orphaned `claudeman-*` screens created during tests 1. **Screen Cleanup**: Kills any orphaned `codeman-*` screens created during tests
2. **Process Cleanup**: Kills any Claude processes spawned by tests 2. **Process Cleanup**: Kills any Claude processes spawned by tests
3. **Pre-existing Protection**: Never kills screens that existed before tests started 3. **Pre-existing Protection**: Never kills screens that existed before tests started
+1 -1
View File
@@ -98,7 +98,7 @@ export class MockSession extends EventEmitter {
constructor(id: string = 'mock-session-id') { constructor(id: string = 'mock-session-id') {
super(); super();
this.id = id; this.id = id;
this._muxName = `claudeman-test-${id.slice(0, 8)}`; this._muxName = `codeman-test-${id.slice(0, 8)}`;
} }
/** Direct PTY write (used by session.write()) */ /** Direct PTY write (used by session.write()) */
+1 -1
View File
@@ -5,7 +5,7 @@ import { join } from 'node:path';
import { homedir } from 'node:os'; import { homedir } from 'node:os';
const TEST_PORT = 3120; const TEST_PORT = 3120;
const CASES_DIR = join(homedir(), 'claudeman-cases'); const CASES_DIR = join(homedir(), 'codeman-cases');
/** /**
* Session Cleanup Tests * Session Cleanup Tests

Some files were not shown because too many files have changed in this diff Show More