mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-09-30 12:39:42 +02:00
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:
@@ -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` |
|
||||
| Type check | `tsc --noEmit` |
|
||||
| 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
|
||||
|
||||
**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
|
||||
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":
|
||||
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`)
|
||||
|
||||
## 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
|
||||
|
||||
@@ -63,24 +63,24 @@ npx tsx src/index.ts web --https # With TLS (only needed for remote access)
|
||||
npm run typecheck # Type check
|
||||
tsc --noEmit --watch # Continuous type checking
|
||||
|
||||
# Testing (NEVER run full suite from inside Claudeman — kills tmux sessions)
|
||||
# npx vitest run # ALL tests — DANGEROUS inside Claudeman
|
||||
# Testing (NEVER run full suite from inside Codeman — kills tmux sessions)
|
||||
# npx vitest run # ALL tests — DANGEROUS inside Codeman
|
||||
npx vitest run test/<file>.test.ts # Single file (SAFE)
|
||||
npx vitest run -t "pattern" # Tests matching name
|
||||
npm run test:coverage # With coverage report
|
||||
|
||||
# Production
|
||||
npm run build
|
||||
systemctl --user restart claudeman-web
|
||||
journalctl --user -u claudeman-web -f
|
||||
systemctl --user restart codeman-web
|
||||
journalctl --user -u codeman-web -f
|
||||
```
|
||||
|
||||
## Common Gotchas
|
||||
|
||||
- **`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
|
||||
- **Don't kill tmux sessions blindly** — Check `$CLAUDEMAN_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.
|
||||
- **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 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
|
||||
- **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
|
||||
@@ -103,7 +103,7 @@ journalctl --user -u claudeman-web -f
|
||||
| `src/mux-factory.ts` | Create tmux multiplexer instance |
|
||||
| `src/tmux-manager.ts` | tmux session management |
|
||||
| `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/ralph-tracker.ts` | Detects `<promise>PHRASE</promise>`, todos |
|
||||
| `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/transcript-watcher.ts` | Watches Claude's transcript files for changes |
|
||||
| `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/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 |
|
||||
@@ -169,7 +169,7 @@ journalctl --user -u claudeman-web -f
|
||||
1. Session spawns `claude --dangerously-skip-permissions` via node-pty
|
||||
2. PTY output buffered, ANSI stripped, parsed for JSON messages
|
||||
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
|
||||
|
||||
@@ -214,11 +214,11 @@ The frontend is a single 16K-line vanilla JS file with these key systems:
|
||||
|
||||
### 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
|
||||
- **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
|
||||
- **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
|
||||
|
||||
### SSE Event Categories
|
||||
@@ -269,9 +269,9 @@ The frontend is a single 16K-line vanilla JS file with these key systems:
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `~/.claudeman/state.json` | Sessions, settings, tokens, respawn config |
|
||||
| `~/.claudeman/mux-sessions.json` | Tmux session metadata for recovery |
|
||||
| `~/.claudeman/settings.json` | User preferences |
|
||||
| `~/.codeman/state.json` | Sessions, settings, tokens, respawn config |
|
||||
| `~/.codeman/mux-sessions.json` | Tmux session metadata for recovery |
|
||||
| `~/.codeman/settings.json` | User preferences |
|
||||
|
||||
## Default Settings
|
||||
|
||||
@@ -281,7 +281,7 @@ UI defaults are set in `src/web/public/app.js` using `??` fallbacks. To change d
|
||||
|
||||
## 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
|
||||
# Safe: run individual test files
|
||||
@@ -290,7 +290,7 @@ npx vitest run test/<specific-file>.test.ts
|
||||
# Safe: run tests matching a 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
|
||||
```
|
||||
|
||||
@@ -306,10 +306,10 @@ npx vitest run -t "pattern"
|
||||
|
||||
## 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
|
||||
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
|
||||
```
|
||||
|
||||
@@ -322,7 +322,7 @@ tmux list-sessions # List tmux sessions
|
||||
tmux attach-session -t <name> # Attach (Ctrl+B D to detach)
|
||||
curl localhost:3000/api/sessions # Check sessions
|
||||
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/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 |
|
||||
| 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 {}` |
|
||||
| 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
|
||||
|
||||
@@ -352,7 +352,7 @@ The app must stay fast with 20 sessions and 50 agent windows:
|
||||
|
||||
## 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
|
||||
@@ -395,14 +395,14 @@ Use `LRUMap` for bounded caches with eviction, `StaleExpirationMap` for TTL-base
|
||||
| **Terminal anti-flicker** | `docs/terminal-anti-flicker.md` |
|
||||
| **API routes** | `src/web/server.ts:buildServer()` or README.md (full endpoint tables) |
|
||||
| **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) |
|
||||
| **Session statuses** | `SessionStatus` type in `src/types.ts` |
|
||||
| **Error codes** | `createErrorResponse()` in `src/types.ts` |
|
||||
| **Test utilities** | `test/respawn-test-utils.ts` |
|
||||
| **Memory leak patterns** | `test/memory-leak-prevention.test.ts` |
|
||||
| **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 |
|
||||
| **Agent prompts** | `src/prompts/` directory |
|
||||
| **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/watch-subagents.ts` | Real-time subagent transcript watcher (list, follow by session/agent ID) |
|
||||
| `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
|
||||
|
||||
@@ -464,7 +464,7 @@ Run `npx vitest run test/memory-leak-prevention.test.ts` to verify patterns.
|
||||
|
||||
## 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()`.
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<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>
|
||||
|
||||
<h2 align="center">The missing control plane for Claude Code</h2>
|
||||
@@ -17,7 +17,7 @@
|
||||
</p>
|
||||
|
||||
<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>
|
||||
|
||||
---
|
||||
@@ -25,19 +25,19 @@
|
||||
## Quick Start - Installation
|
||||
|
||||
```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
|
||||
claudeman web
|
||||
codeman web
|
||||
# Open http://localhost:3000 — press Ctrl+Enter to start your first session
|
||||
```
|
||||
|
||||
**Update to latest version:**
|
||||
```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>
|
||||
@@ -45,12 +45,12 @@ curl -fsSL https://raw.githubusercontent.com/Ark0N/claudeman/master/install.sh |
|
||||
|
||||
**Linux (systemd):**
|
||||
```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):**
|
||||
```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>
|
||||
|
||||
@@ -58,10 +58,10 @@ mkdir -p ~/Library/LaunchAgents && printf '<?xml version="1.0" encoding="UTF-8"?
|
||||
<summary><strong>Windows (WSL)</strong></summary>
|
||||
|
||||
```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>
|
||||
|
||||
---
|
||||
@@ -74,7 +74,7 @@ The most responsive Claude Code experience on any phone. Full xterm.js terminal
|
||||
<tr>
|
||||
<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>Claudeman Mobile</th>
|
||||
<th>Codeman Mobile</th>
|
||||
</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>
|
||||
@@ -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
|
||||
|
||||
```bash
|
||||
claudeman web --https
|
||||
codeman web --https
|
||||
# Open on your phone: https://<your-ip>:3000
|
||||
```
|
||||
|
||||
@@ -104,7 +104,7 @@ claudeman web --https
|
||||
|
||||
## 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">
|
||||
<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
|
||||
|
||||
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.
|
||||
|
||||
@@ -186,7 +186,7 @@ Click the chart icon on any session tab to see a timeline of everything that hap
|
||||
|
||||
### 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)
|
||||
@@ -268,7 +268,7 @@ Single-digit selection (1-9), color-coded status, token counts, auto-refresh. De
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph Claudeman["CLAUDEMAN"]
|
||||
subgraph Codeman["CODEMAN"]
|
||||
subgraph Frontend["Frontend Layer"]
|
||||
UI["Web UI<br/><small>xterm.js + Agent Windows</small>"]
|
||||
API["REST API<br/><small>Fastify</small>"]
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -122,7 +122,7 @@ Message types observed:
|
||||
|
||||
**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:
|
||||
- No separate PIDs to track per teammate
|
||||
@@ -138,7 +138,7 @@ This means:
|
||||
| **tmux** | `--teammate-mode tmux` | Split panes | tmux installed |
|
||||
| **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:**
|
||||
- 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 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)
|
||||
|
||||
@@ -168,7 +168,7 @@ These are configured in `.claude/settings.local.json` alongside existing Claudem
|
||||
~/.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:**
|
||||
- Description field starts with `<teammate-message teammate_id= team`
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -15,7 +15,7 @@ Monitors `~/.claude/teams/` for team creation/removal and tracks active teams.
|
||||
**Discovery mechanism:**
|
||||
- Poll `~/.claude/teams/` for directories (team names) every 3-5 seconds
|
||||
- When found: parse `config.json` to get:
|
||||
- `leadSessionId` → map to Claudeman session
|
||||
- `leadSessionId` → map to Codeman session
|
||||
- `members` array → teammate names, agentIds, colors, models
|
||||
- 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
|
||||
|
||||
**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
|
||||
- `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)
|
||||
|
||||
**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:**
|
||||
- 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):**
|
||||
- Write to teammate inbox files to inject messages
|
||||
- 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)
|
||||
|
||||
@@ -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
|
||||
4. **Team tasks API + UI** — `GET /api/sessions/:id/team-tasks` + task list panel
|
||||
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
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Agent Teams Experiment Log
|
||||
|
||||
> Experiment date: 2026-02-12
|
||||
> Test case: `~/claudeman-cases/agent-teams-test/`
|
||||
> Test case: `~/codeman-cases/agent-teams-test/`
|
||||
> Team name: `research-watchers`
|
||||
> Teammates: 3 (fs-researcher, perf-researcher, api-researcher)
|
||||
> 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:
|
||||
```
|
||||
~/.claude/projects/-home-arkon-claudeman-cases-agent-teams-test/
|
||||
~/.claude/projects/-home-arkon-codeman-cases-agent-teams-test/
|
||||
└── 461daa80.../
|
||||
├── 461daa80...jsonl # Lead session transcript
|
||||
└── subagents/
|
||||
@@ -102,7 +102,7 @@ Key observations:
|
||||
- **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
|
||||
|
||||
**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.
|
||||
|
||||
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
|
||||
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",
|
||||
"joinedAt": 1770875105373,
|
||||
"tmuxPaneId": "",
|
||||
"cwd": "/home/arkon/claudeman-cases/agent-teams-test",
|
||||
"cwd": "/home/arkon/codeman-cases/agent-teams-test",
|
||||
"subscriptions": []
|
||||
},
|
||||
{
|
||||
@@ -169,7 +169,7 @@ Agent: a29de32 Status: active Tools: 7 Model: claude-opus-4-6
|
||||
"planModeRequired": false,
|
||||
"joinedAt": 1770875126680,
|
||||
"tmuxPaneId": "in-process",
|
||||
"cwd": "/home/arkon/claudeman-cases/agent-teams-test",
|
||||
"cwd": "/home/arkon/codeman-cases/agent-teams-test",
|
||||
"subscriptions": [],
|
||||
"backendType": "in-process"
|
||||
},
|
||||
@@ -183,7 +183,7 @@ Agent: a29de32 Status: active Tools: 7 Model: claude-opus-4-6
|
||||
"planModeRequired": false,
|
||||
"joinedAt": 1770875130344,
|
||||
"tmuxPaneId": "in-process",
|
||||
"cwd": "/home/arkon/claudeman-cases/agent-teams-test",
|
||||
"cwd": "/home/arkon/codeman-cases/agent-teams-test",
|
||||
"subscriptions": [],
|
||||
"backendType": "in-process"
|
||||
},
|
||||
@@ -197,7 +197,7 @@ Agent: a29de32 Status: active Tools: 7 Model: claude-opus-4-6
|
||||
"planModeRequired": false,
|
||||
"joinedAt": 1770875134997,
|
||||
"tmuxPaneId": "in-process",
|
||||
"cwd": "/home/arkon/claudeman-cases/agent-teams-test",
|
||||
"cwd": "/home/arkon/codeman-cases/agent-teams-test",
|
||||
"subscriptions": [],
|
||||
"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
|
||||
|
||||
**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:
|
||||
- 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)
|
||||
- Lead displayed "Befuddling..." spinner while waiting for teammates
|
||||
- Token count climbed from 27k → 33k during the experiment
|
||||
- The `stop` hook DID fire at the end when the team was cleaned up
|
||||
|
||||
**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
|
||||
|
||||
**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.**
|
||||
|
||||
```
|
||||
# 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)
|
||||
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
|
||||
|
||||
@@ -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:
|
||||
```
|
||||
CLAUDEMAN_SCREEN=1
|
||||
CLAUDEMAN_SESSION_ID=461daa80-94ec-4e5e-a1bb-0518f78311bc
|
||||
CLAUDEMAN_SCREEN_NAME=claudeman-461daa80
|
||||
CLAUDEMAN_API_URL=http://localhost:3000
|
||||
CODEMAN_SCREEN=1
|
||||
CODEMAN_SESSION_ID=461daa80-94ec-4e5e-a1bb-0518f78311bc
|
||||
CODEMAN_SCREEN_NAME=codeman-461daa80
|
||||
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.
|
||||
@@ -358,7 +358,7 @@ Teammates create transcript files in the standard subagent path:
|
||||
~/.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`
|
||||
2. They can be cross-referenced with `~/.claude/teams/{name}/config.json`
|
||||
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
|
||||
|
||||
**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)
|
||||
- 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).
|
||||
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
|
||||
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:
|
||||
|
||||
1. **Tab switch loses PTY state** — switching sessions saves overlay text to `localEchoTextCache` (a Map), but the actual Claude Code Ink process has no knowledge of what was typed. If respawn or `/clear` fires on that session, the cached text is meaningless.
|
||||
2. **Session death loses input** — if the session crashes or respawns while text is pending in the overlay, that input is gone (localStorage backup `claudeman_local_echo_pending` only survives page reloads, not session resets).
|
||||
2. **Session death loses input** — if the session crashes or respawns while text is pending in the overlay, that input is gone (localStorage backup `codeman_local_echo_pending` only survives page reloads, not session resets).
|
||||
3. **Tab completion impossible** — pressing Tab with pending overlay text sends the raw Tab character to a PTY that has no knowledge of the typed text, so completion fails.
|
||||
|
||||
## Goal
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
When local echo is enabled, keystrokes accumulate **only** in `LocalEchoOverlay.pendingText` (a client-side string). Nothing reaches the server PTY until Enter is pressed. This creates three failure modes:
|
||||
|
||||
1. **Tab switch loses PTY state** — switching sessions saves overlay text to `localEchoTextCache` (a Map), but the actual Claude Code Ink process has no knowledge of what was typed. If respawn or `/clear` fires on that session, the cached text is meaningless.
|
||||
2. **Session death loses input** — if the session crashes or respawns while text is pending in the overlay, that input is gone (localStorage backup `claudeman_local_echo_pending` only survives page reloads, not session resets).
|
||||
2. **Session death loses input** — if the session crashes or respawns while text is pending in the overlay, that input is gone (localStorage backup `codeman_local_echo_pending` only survives page reloads, not session resets).
|
||||
3. **Tab completion impossible** — pressing Tab with pending overlay text sends the raw Tab character to a PTY that has no knowledge of the typed text, so completion fails.
|
||||
|
||||
## Goal
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Browser Testing Guide for Claudeman
|
||||
# Browser Testing Guide for Codeman
|
||||
|
||||
This guide documents the browser testing infrastructure, framework comparison results, and best practices for testing the Claudeman web UI.
|
||||
This guide documents the browser testing infrastructure, framework comparison results, and best practices for testing the Codeman web UI.
|
||||
|
||||
## Quick Start
|
||||
|
||||
@@ -14,7 +14,7 @@ npm test -- test/browser-e2e.test.ts
|
||||
|
||||
## Framework Comparison Results
|
||||
|
||||
We tested three browser automation frameworks against the Claudeman web UI:
|
||||
We tested three browser automation frameworks against the Codeman web UI:
|
||||
|
||||
| Framework | Avg Duration | Best For |
|
||||
|-----------|--------------|----------|
|
||||
@@ -114,7 +114,7 @@ await page.waitForSelector('.session-tab', { state: 'visible' });
|
||||
|
||||
// Assertions with expect
|
||||
await expect(page.locator('.header')).toBeVisible();
|
||||
await expect(page).toHaveTitle('Claudeman');
|
||||
await expect(page).toHaveTitle('Codeman');
|
||||
|
||||
await browser.close();
|
||||
```
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Codebase Cleanup Findings
|
||||
|
||||
Compiled from parallel analysis of the entire Claudeman codebase by 3 research agents (2026-02-19).
|
||||
Compiled from parallel analysis of the entire Codeman codebase by 3 research agents (2026-02-19).
|
||||
|
||||
## P0 — Bug Fix
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
**Date**: 2026-02-18
|
||||
**Audit by**: 4-agent team (css-analyst, js-analyst, server-analyst, deps-analyst)
|
||||
**Scope**: First browser load of Claudeman web UI at `/`
|
||||
**Scope**: First browser load of Codeman web UI at `/`
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 859 KiB After Width: | Height: | Size: 859 KiB |
@@ -1,3 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 60">
|
||||
<text x="160" y="48" font-family="system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif" font-size="52" font-weight="700" fill="#60a5fa" text-anchor="middle">Claudeman</text>
|
||||
<text x="160" y="48" font-family="system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif" font-size="52" font-weight="700" fill="#60a5fa" text-anchor="middle">Codeman</text>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 249 B After Width: | Height: | Size: 247 B |
@@ -2,7 +2,7 @@
|
||||
|
||||
## Context
|
||||
|
||||
User accesses Claudeman remotely from Thailand to Switzerland over Tailscale (~200-300ms RTT).
|
||||
User accesses Codeman remotely from Thailand to Switzerland over Tailscale (~200-300ms RTT).
|
||||
Every keystroke is invisible for 200-300ms before the server echoes it back. This makes typing
|
||||
painfully slow on mobile. Previous attempts to write directly to xterm.js buffer failed because
|
||||
Ink (Claude Code's terminal framework) does full-screen redraws that corrupt injected characters.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# OpenCode Integration Plan for Claudeman
|
||||
# OpenCode Integration Plan for Codeman
|
||||
|
||||
> **Author**: Claude Opus 4.6 | **Date**: 2026-02-26
|
||||
> **Status**: Draft — Re-reviewed, MVP scope finalized. NOT pushed to GitHub
|
||||
@@ -36,13 +36,13 @@
|
||||
23. [Appendix A: OpenCode CLI Reference](#appendix-a-opencode-cli-reference)
|
||||
24. [Appendix B: OpenCode Plugin Events](#appendix-b-opencode-plugin-events)
|
||||
25. [Appendix C: OpenCode Permission Config](#appendix-c-opencode-permission-config)
|
||||
26. [Appendix D: Current Claudeman Session Spawn Flow (Annotated)](#appendix-d-current-claudeman-session-spawn-flow-annotated)
|
||||
26. [Appendix D: Current Codeman Session Spawn Flow (Annotated)](#appendix-d-current-codeman-session-spawn-flow-annotated)
|
||||
|
||||
---
|
||||
|
||||
## 1. Executive Summary
|
||||
|
||||
This plan details how to integrate [OpenCode](https://opencode.ai) — the popular open-source AI coding CLI (111k+ GitHub stars, 75+ model providers) — into Claudeman as a first-class session type alongside Claude Code and shell sessions.
|
||||
This plan details how to integrate [OpenCode](https://opencode.ai) — the popular open-source AI coding CLI (111k+ GitHub stars, 75+ model providers) — into Codeman as a first-class session type alongside Claude Code and shell sessions.
|
||||
|
||||
### Core Approach
|
||||
|
||||
@@ -53,7 +53,7 @@ Extend the existing `SessionMode` type from `'claude' | 'shell'` to `'claude' |
|
||||
| Strategy | Approach | Complexity | Value |
|
||||
|----------|----------|------------|-------|
|
||||
| **A: TUI-in-tmux** | Spawn `opencode` CLI in tmux, render in xterm.js | Medium | Full visual parity with Claude Code |
|
||||
| **B: Server API bridge** | Run `opencode serve` + proxy its API through Claudeman | High | Structured data, session control, token tracking |
|
||||
| **B: Server API bridge** | Run `opencode serve` + proxy its API through Codeman | High | Structured data, session control, token tracking |
|
||||
|
||||
**Recommended path**: Start with Strategy A (TUI-in-tmux) since it mirrors the existing Claude Code pattern exactly. Then layer Strategy B on top for advanced features like structured token tracking and model switching.
|
||||
|
||||
@@ -71,7 +71,7 @@ Extend the existing `SessionMode` type from `'claude' | 'shell'` to `'claude' |
|
||||
> | **Hooks plugin bridge** | Plugin event names are speculative/unverified. Requires Phase 0 validation that hasn't happened. | Phase 0 plugin verification |
|
||||
> | **AI idle checker** | Spawns `claude -p` for analysis. Won't work if only OpenCode is installed. | Claude CLI availability or `opencode run` fallback |
|
||||
>
|
||||
> **What ships in the MVP**: Phases 0-3 (spawn in tmux, render in xterm.js) + Phase 5 (API routes, mode selector, tab badges, create/kill sessions). Users can interact with OpenCode manually — type prompts, see output, manage sessions from the Claudeman web UI. This alone is the core value: multi-model AI sessions in one management interface.
|
||||
> **What ships in the MVP**: Phases 0-3 (spawn in tmux, render in xterm.js) + Phase 5 (API routes, mode selector, tab badges, create/kill sessions). Users can interact with OpenCode manually — type prompts, see output, manage sessions from the Codeman web UI. This alone is the core value: multi-model AI sessions in one management interface.
|
||||
|
||||
### Why OpenCode?
|
||||
|
||||
@@ -79,7 +79,7 @@ Extend the existing `SessionMode` type from `'claude' | 'shell'` to `'claude' |
|
||||
- **Privacy-first**: Can run fully local via Ollama — no code ever leaves the machine
|
||||
- **Open source**: MIT licensed, active community (700+ contributors)
|
||||
- **Client/server**: Built-in `opencode serve` mode enables richer programmatic integration
|
||||
- **Plugin system**: JS/TS plugins with rich event hooks (including `session.idle` — perfect for Claudeman)
|
||||
- **Plugin system**: JS/TS plugins with rich event hooks (including `session.idle` — perfect for Codeman)
|
||||
|
||||
---
|
||||
|
||||
@@ -222,7 +222,7 @@ OPENCODE_CLIENT=... # Client identifier (default: "cli")
|
||||
|
||||
## 3. Architecture Comparison: Claude Code vs OpenCode
|
||||
|
||||
### Current Claudeman Session Flow (Claude Code)
|
||||
### Current Codeman Session Flow (Claude Code)
|
||||
|
||||
```
|
||||
POST /api/sessions → new Session({mode: 'claude', mux: TmuxManager})
|
||||
@@ -230,10 +230,10 @@ POST /api/sessions/:id/interactive → session.startInteractive()
|
||||
↓
|
||||
TmuxManager.createSession()
|
||||
↓
|
||||
tmux new-session -ds "claudeman-<id>"
|
||||
tmux new-session -ds "codeman-<id>"
|
||||
tmux respawn-pane -k -t ... "claude --dangerously-skip-permissions --session-id <id>"
|
||||
↓
|
||||
pty.spawn('tmux', ['attach-session', '-t', 'claudeman-<id>'])
|
||||
pty.spawn('tmux', ['attach-session', '-t', 'codeman-<id>'])
|
||||
↓
|
||||
ptyProcess.onData() → emit('terminal') → SSE broadcast → xterm.js
|
||||
```
|
||||
@@ -246,10 +246,10 @@ POST /api/sessions/:id/interactive → session.startInteractive()
|
||||
↓
|
||||
TmuxManager.createSession()
|
||||
↓
|
||||
tmux new-session -ds "claudeman-<id>"
|
||||
tmux new-session -ds "codeman-<id>"
|
||||
tmux respawn-pane -k -t ... "opencode --model <model>"
|
||||
↓
|
||||
pty.spawn('tmux', ['attach-session', '-t', 'claudeman-<id>'])
|
||||
pty.spawn('tmux', ['attach-session', '-t', 'codeman-<id>'])
|
||||
↓
|
||||
ptyProcess.onData() → emit('terminal') → SSE broadcast → xterm.js
|
||||
```
|
||||
@@ -268,7 +268,7 @@ Strategy A (TUI-in-tmux) for terminal rendering
|
||||
+
|
||||
opencode serve (background, port 4096+N)
|
||||
↓
|
||||
Claudeman proxy routes → GET /session/current, POST /session/message, SSE /events
|
||||
Codeman proxy routes → GET /session/current, POST /session/message, SSE /events
|
||||
↓
|
||||
Structured data for: token tracking, session management, model switching
|
||||
```
|
||||
@@ -301,7 +301,7 @@ These systems work identically for OpenCode sessions:
|
||||
- Terminal data streaming via `ptyProcess.onData()`
|
||||
- SSE event broadcasting via `broadcast()`
|
||||
- xterm.js terminal rendering in the browser
|
||||
- State persistence to `~/.claudeman/state.json`
|
||||
- State persistence to `~/.codeman/state.json`
|
||||
- Session CRUD API routes (create, get, delete)
|
||||
- Tab management in frontend
|
||||
- Session kill/cleanup logic (`TmuxManager.killSession()`)
|
||||
@@ -324,7 +324,7 @@ These systems work identically for OpenCode sessions:
|
||||
| Subagent detection | `BashToolParser` + `SubagentWatcher` | Different tool output format | DEFERRED |
|
||||
| Ralph completion | `<promise>PHRASE</promise>` tags | Not applicable (needs alternative) | DEFERRED |
|
||||
| Hooks events | `permission_prompt`, `idle_prompt`, `stop` | `permission.asked`, `session.idle`, `session.status` | DEFERRED |
|
||||
| Auto-compact | Claudeman sends `/compact` at token threshold | OpenCode has built-in `compaction.auto: true` | DEFERRED |
|
||||
| Auto-compact | Codeman sends `/compact` at token threshold | OpenCode has built-in `compaction.auto: true` | DEFERRED |
|
||||
|
||||
---
|
||||
|
||||
@@ -346,7 +346,7 @@ opencode --version
|
||||
# 3. Test interactive TUI
|
||||
opencode
|
||||
|
||||
# 4. Test in tmux (simulating Claudeman's spawn pattern)
|
||||
# 4. Test in tmux (simulating Codeman's spawn pattern)
|
||||
tmux new-session -ds "test-opencode" -c /tmp -x 120 -y 40
|
||||
tmux set-option -t "test-opencode" remain-on-exit on
|
||||
tmux respawn-pane -k -t "test-opencode" 'opencode --model anthropic/claude-sonnet-4-5'
|
||||
@@ -774,10 +774,10 @@ function buildEnvExports(mode: string, sessionId: string, muxName: string, openC
|
||||
`export LANG=en_US.UTF-8`,
|
||||
`export LC_ALL=en_US.UTF-8`,
|
||||
`unset COLORTERM`, // Prevent color issues in tmux
|
||||
`export CLAUDEMAN_MUX=1`,
|
||||
`export CLAUDEMAN_SESSION_ID=${sessionId}`,
|
||||
`export CLAUDEMAN_MUX_NAME=${muxName}`,
|
||||
`export CLAUDEMAN_API_URL=${process.env.CLAUDEMAN_API_URL || 'http://localhost:3000'}`,
|
||||
`export CODEMAN_MUX=1`,
|
||||
`export CODEMAN_SESSION_ID=${sessionId}`,
|
||||
`export CODEMAN_MUX_NAME=${muxName}`,
|
||||
`export CODEMAN_API_URL=${process.env.CODEMAN_API_URL || 'http://localhost:3000'}`,
|
||||
];
|
||||
|
||||
if (mode === 'opencode') {
|
||||
@@ -905,7 +905,7 @@ const mode = cmd.includes('opencode') ? 'opencode' : 'claude';
|
||||
|
||||
### Known Limitations (from review)
|
||||
|
||||
> **[REVIEW P1-6]** OpenCode does not have `--session-id <claudeman-id>` for initial session creation (only `--session <id>` for continuing existing sessions). This means:
|
||||
> **[REVIEW P1-6]** OpenCode does not have `--session-id <codeman-id>` for initial session creation (only `--session <id>` for continuing existing sessions). This means:
|
||||
> - `_claudeSessionId = this.id` correlation (session.ts:405) won't work for OpenCode
|
||||
> - Subagent-session correlation via Session ID matching won't work
|
||||
> - Transcript watching via Claude session ID path won't work
|
||||
@@ -1121,7 +1121,7 @@ private parseTokens(data: string): void {
|
||||
|
||||
### Working/Idle State Tracking
|
||||
|
||||
For Claude, Claudeman uses spinner characters and keywords. For OpenCode:
|
||||
For Claude, Codeman uses spinner characters and keywords. For OpenCode:
|
||||
|
||||
> **[REVIEW]** The original logic had a bug: `Date.now() - this._lastActivityAt > 100` is checked AFTER setting `_lastActivityAt = Date.now()`, so the condition would never be true (0 > 100 = false). Fixed below.
|
||||
|
||||
@@ -1373,7 +1373,7 @@ if (session.mode === 'claude' && session.pid) {
|
||||
> **[REVIEW C3] Phase reordered**: This was originally Phase 7 but has been moved before Respawn. The plugin bridge provides the reliable `session.idle` event that the respawn controller needs. Without it, respawn relies on output-silence-only detection, which is unreliable with Bubble Tea TUIs (see Review C1). **Do not implement respawn (Phase 7) until this phase is complete and tested.**
|
||||
|
||||
### Goal
|
||||
Bridge Claudeman's hook system with OpenCode's plugin system for rich event forwarding.
|
||||
Bridge Codeman's hook system with OpenCode's plugin system for rich event forwarding.
|
||||
|
||||
### Background: Two Different Approaches
|
||||
|
||||
@@ -1383,19 +1383,19 @@ Bridge Claudeman's hook system with OpenCode's plugin system for rich event forw
|
||||
| Trigger | Hook name matches event type | Event name subscription |
|
||||
| Key events | `stop`, `idle_prompt`, `permission_prompt`, `elicitation_dialog` | `session.idle`, `permission.asked`, `session.status` |
|
||||
| Communication | Exit codes + environment variables | Function context + return values |
|
||||
| Install | Auto-generated by Claudeman | Must be placed in `.opencode/plugins/` |
|
||||
| Install | Auto-generated by Codeman | Must be placed in `.opencode/plugins/` |
|
||||
|
||||
### Claudeman Plugin for OpenCode
|
||||
### Codeman Plugin for OpenCode
|
||||
|
||||
Create a Claudeman plugin that OpenCode loads, which communicates back to Claudeman's API:
|
||||
Create a Codeman plugin that OpenCode loads, which communicates back to Codeman's API:
|
||||
|
||||
**File: `.opencode/plugins/claudeman-bridge.js`** (generated per session)
|
||||
**File: `.opencode/plugins/codeman-bridge.js`** (generated per session)
|
||||
|
||||
```javascript
|
||||
// This plugin bridges OpenCode events to Claudeman's API
|
||||
export const claudemanBridge = async ({ project, $ }) => {
|
||||
const apiUrl = process.env.CLAUDEMAN_API_URL || 'http://localhost:3000';
|
||||
const sessionId = process.env.CLAUDEMAN_SESSION_ID;
|
||||
// This plugin bridges OpenCode events to Codeman's API
|
||||
export const codemanBridge = async ({ project, $ }) => {
|
||||
const apiUrl = process.env.CODEMAN_API_URL || 'http://localhost:3000';
|
||||
const sessionId = process.env.CODEMAN_SESSION_ID;
|
||||
|
||||
if (!sessionId) return {};
|
||||
|
||||
@@ -1440,7 +1440,7 @@ export const claudemanBridge = async ({ project, $ }) => {
|
||||
|
||||
### Plugin Installation
|
||||
|
||||
When creating an OpenCode session, Claudeman generates this plugin in the project's `.opencode/plugins/` directory:
|
||||
When creating an OpenCode session, Codeman generates this plugin in the project's `.opencode/plugins/` directory:
|
||||
|
||||
```typescript
|
||||
// In session.ts or a new opencode-hooks.ts:
|
||||
@@ -1448,8 +1448,8 @@ async function installOpenCodePlugin(workingDir: string, sessionId: string): Pro
|
||||
const pluginDir = join(workingDir, '.opencode', 'plugins');
|
||||
await mkdirp(pluginDir);
|
||||
|
||||
const pluginContent = generateClaudemanBridgePlugin(sessionId);
|
||||
await writeFile(join(pluginDir, 'claudeman-bridge.js'), pluginContent);
|
||||
const pluginContent = generateCodemanBridgePlugin(sessionId);
|
||||
await writeFile(join(pluginDir, 'codeman-bridge.js'), pluginContent);
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1458,7 +1458,7 @@ async function installOpenCodePlugin(workingDir: string, sessionId: string): Pro
|
||||
> **[REVIEW]** Add `session.compacted` to the plugin bridge — it's free and useful for tracking when OpenCode auto-compacts (relevant for token tracking and respawn timing).
|
||||
|
||||
```javascript
|
||||
// Add to claudeman-bridge.js return object:
|
||||
// Add to codeman-bridge.js return object:
|
||||
'session.compacted': async (info) => {
|
||||
await notifyClademan('session_compacted', { info });
|
||||
},
|
||||
@@ -1472,12 +1472,12 @@ async function installOpenCodePlugin(workingDir: string, sessionId: string): Pro
|
||||
|
||||
### Benefits of the Plugin Bridge
|
||||
|
||||
Once installed, Claudeman receives structured events from OpenCode:
|
||||
Once installed, Codeman receives structured events from OpenCode:
|
||||
- **`session.idle`** → Definitive idle detection (replaces output-silence guessing)
|
||||
- **`permission.asked`** → Show permission prompts in Claudeman UI
|
||||
- **`permission.asked`** → Show permission prompts in Codeman UI
|
||||
- **`tool.execute.*`** → Tool call tracking (similar to BashToolParser for Claude)
|
||||
- **`todo.updated`** → OpenCode's built-in todo system → Claudeman can display it
|
||||
- **`session.error`** → Error surfacing in Claudeman UI
|
||||
- **`todo.updated`** → OpenCode's built-in todo system → Codeman can display it
|
||||
- **`session.error`** → Error surfacing in Codeman UI
|
||||
- **`session.compacted`** → Track auto-compaction events
|
||||
|
||||
---
|
||||
@@ -1664,7 +1664,7 @@ tmux session ─── opencode TUI (interactive, port N/A)
|
||||
│
|
||||
├── xterm.js (terminal rendering, Strategy A)
|
||||
│
|
||||
Claudeman ──── opencode serve (port 4096+N, background process)
|
||||
Codeman ──── opencode serve (port 4096+N, background process)
|
||||
│
|
||||
├── GET /session/* → structured session data
|
||||
├── POST /session/message → send prompt programmatically
|
||||
@@ -1678,7 +1678,7 @@ Claudeman ──── opencode serve (port 4096+N, background process)
|
||||
> **[REVIEW M8]** Running both `opencode` (TUI) and `opencode serve` in the same project directory creates a **shared SQLite state problem**. The TUI creates sessions in SQLite; the server reads from the same SQLite. But they're separate processes — there's no guarantee of session ID consistency between them.
|
||||
>
|
||||
> **Consider alternatives**:
|
||||
> - (a) Use `opencode serve` INSTEAD of the TUI (not alongside it) — server becomes the single backend, Claudeman renders its own UI
|
||||
> - (a) Use `opencode serve` INSTEAD of the TUI (not alongside it) — server becomes the single backend, Codeman renders its own UI
|
||||
> - (b) Use `opencode attach` to connect the TUI to the server, making the server the single source of truth
|
||||
> - (c) Accept the dual-process model with explicit documentation of limitations
|
||||
>
|
||||
@@ -1805,7 +1805,7 @@ if (this.mode === 'opencode' && this._openCodeConfig?.serverPort) {
|
||||
|------|---------|-------|--------|
|
||||
| `src/utils/ansi-content-filter.ts` | Strip cosmetic ANSI sequences for idle detection | 4 | DEFERRED |
|
||||
| `src/completion-detector.ts` | `CompletionDetector` interface + implementations | 7 | DEFERRED |
|
||||
| `src/opencode-plugin-generator.ts` | Generate `.opencode/plugins/claudeman-bridge.js` | 6 | DEFERRED |
|
||||
| `src/opencode-plugin-generator.ts` | Generate `.opencode/plugins/codeman-bridge.js` | 6 | DEFERRED |
|
||||
| `src/opencode-api-client.ts` | Client for OpenCode's REST API (Strategy B) | 8 | DEFERRED |
|
||||
| `test/opencode-respawn.test.ts` | Tests for OpenCode respawn cycle (port 3156) | 7 | DEFERRED |
|
||||
| `test/ansi-content-filter.test.ts` | Tests for ANSI content filter | 4 | DEFERRED |
|
||||
@@ -1898,7 +1898,7 @@ npx vitest run test/opencode-respawn.test.ts
|
||||
|
||||
### Safety Rules
|
||||
|
||||
- **Never run OpenCode tests that spawn real tmux sessions inside Claudeman** (same safety rule as Claude tests)
|
||||
- **Never run OpenCode tests that spawn real tmux sessions inside Codeman** (same safety rule as Claude tests)
|
||||
- **Use MockSession** from `test/respawn-test-utils.ts` for respawn testing
|
||||
- **Mock the opencode binary** for unit tests (`jest.mock` or stub)
|
||||
- **Use unique test ports** (3155+) — never port 3000
|
||||
@@ -1939,12 +1939,12 @@ npx vitest run test/opencode-respawn.test.ts
|
||||
|
||||
5. **Should we auto-generate `opencode.json` in the working directory?**
|
||||
- Option A: Let OpenCode use existing project config (respect user settings)
|
||||
- Option B: Generate a temporary one with Claudeman's settings
|
||||
- Recommendation: Option A (use `OPENCODE_CONFIG_CONTENT` env var for Claudeman-specific overrides, don't modify project files)
|
||||
- Option B: Generate a temporary one with Codeman's settings
|
||||
- Recommendation: Option A (use `OPENCODE_CONFIG_CONTENT` env var for Codeman-specific overrides, don't modify project files)
|
||||
|
||||
6. **How should OpenCode session IDs map to Claudeman session IDs?**
|
||||
6. **How should OpenCode session IDs map to Codeman session IDs?**
|
||||
- OpenCode manages its own sessions (SQLite DB)
|
||||
- We could pass `--session <claudeman-id>` but OpenCode IDs have different format
|
||||
- We could pass `--session <codeman-id>` but OpenCode IDs have different format
|
||||
- Recommendation: Let OpenCode manage its own sessions, store the mapping in SessionState
|
||||
|
||||
---
|
||||
@@ -1970,7 +1970,7 @@ Phase 3: Tmux spawn (2-3 hours) ← CRITICAL INTEGRATION POINT
|
||||
↓
|
||||
Phase 5: API + frontend (2-3 hours) ← FIRST USER-VISIBLE RESULT
|
||||
↓
|
||||
✅ MVP COMPLETE — OpenCode sessions managed from Claudeman web UI
|
||||
✅ MVP COMPLETE — OpenCode sessions managed from Codeman web UI
|
||||
|
||||
═══════════════════════════════════════════════════════
|
||||
DEFERRED (requires real PTY data + verified APIs)
|
||||
@@ -1991,7 +1991,7 @@ Phase 8: Server API — DEFERRED, optional advanced feature
|
||||
|
||||
| Milestone | Phase | What You Can Do | Scope |
|
||||
|-----------|-------|-----------------|-------|
|
||||
| **M1: "It renders"** | 0-3 | OpenCode TUI visible in xterm.js via Claudeman | **MVP** |
|
||||
| **M1: "It renders"** | 0-3 | OpenCode TUI visible in xterm.js via Codeman | **MVP** |
|
||||
| **M2: "It's usable"** | 5 | Create OpenCode sessions from web UI, type and interact, manage tabs | **MVP** |
|
||||
| **M3: "It's observable"** | 4 | Idle/working state detection, ANSI content filter | Deferred |
|
||||
| **M4: "It's smart"** | 6 | Plugin bridge provides definitive idle/permission/tool events | Deferred |
|
||||
@@ -2012,7 +2012,7 @@ Phase 8: Server API — DEFERRED, optional advanced feature
|
||||
|
||||
## 21. Review Findings
|
||||
|
||||
> **Reviewed**: 2026-02-26 by a 4-agent team. Each agent reviewed a different area of the plan against the actual Claudeman codebase.
|
||||
> **Reviewed**: 2026-02-26 by a 4-agent team. Each agent reviewed a different area of the plan against the actual Codeman codebase.
|
||||
|
||||
### Review Team
|
||||
|
||||
@@ -2182,7 +2182,7 @@ Environment Variables:
|
||||
|
||||
Full list of subscribable events in OpenCode's plugin system:
|
||||
|
||||
| Category | Event | Description | Claudeman Relevance |
|
||||
| Category | Event | Description | Codeman Relevance |
|
||||
|----------|-------|-------------|---------------------|
|
||||
| **Command** | `command.executed` | Slash command run | Low |
|
||||
| **Files** | `file.edited` | File modified | Medium (track changes) |
|
||||
@@ -2194,7 +2194,7 @@ Full list of subscribable events in OpenCode's plugin system:
|
||||
| | `message.updated` | Complete message | High (completion detection) |
|
||||
| | `message.removed` | Message deleted | Low |
|
||||
| | `message.part.removed` | Part deleted | Low |
|
||||
| **Permissions** | `permission.asked` | Tool approval needed | **Critical** (show in Claudeman) |
|
||||
| **Permissions** | `permission.asked` | Tool approval needed | **Critical** (show in Codeman) |
|
||||
| | `permission.replied` | User responded | High (track approvals) |
|
||||
| **Server** | `server.connected` | Server started | Medium |
|
||||
| **Sessions** | `session.idle` | Agent finished working | **Critical** (idle detection!) |
|
||||
@@ -2254,7 +2254,7 @@ Full list of subscribable events in OpenCode's plugin system:
|
||||
- `"ask"` — Prompt user for approval
|
||||
- `"deny"` — Block the action
|
||||
|
||||
### Delivery Method for Claudeman
|
||||
### Delivery Method for Codeman
|
||||
|
||||
Use `OPENCODE_CONFIG_CONTENT` environment variable to inject permissions without modifying project files:
|
||||
|
||||
@@ -2265,7 +2265,7 @@ opencode --model anthropic/claude-sonnet-4-5
|
||||
|
||||
---
|
||||
|
||||
## Appendix D: Current Claudeman Session Spawn Flow (Annotated)
|
||||
## Appendix D: Current Codeman Session Spawn Flow (Annotated)
|
||||
|
||||
### Exact Code Path (for reference during implementation)
|
||||
|
||||
@@ -2285,15 +2285,15 @@ opencode --model anthropic/claude-sonnet-4-5
|
||||
→ If none: this._mux.createSession(id, workingDir, 'claude', ...)
|
||||
|
||||
4. TmuxManager.createSession() (tmux-manager.ts:225)
|
||||
→ tmux new-session -ds "claudeman-<shortId>" -c <workingDir> -x 120 -y 40
|
||||
→ tmux set-option -t "claudeman-<shortId>" remain-on-exit on
|
||||
→ tmux new-session -ds "codeman-<shortId>" -c <workingDir> -x 120 -y 40
|
||||
→ tmux set-option -t "codeman-<shortId>" remain-on-exit on
|
||||
→ Build command: "export PATH=... && export LANG=... && claude --dangerously-skip-permissions --session-id <id>"
|
||||
→ tmux respawn-pane -k -t "claudeman-<shortId>" '<command>'
|
||||
→ tmux respawn-pane -k -t "codeman-<shortId>" '<command>'
|
||||
→ Wait 100ms, configure tmux, get PID
|
||||
→ Return MuxSession { muxName, pid, mode }
|
||||
|
||||
5. Back in startInteractive() (session.ts:~950)
|
||||
→ pty.spawn('tmux', ['attach-session', '-t', 'claudeman-<shortId>'], {
|
||||
→ pty.spawn('tmux', ['attach-session', '-t', 'codeman-<shortId>'], {
|
||||
name: 'xterm-256color',
|
||||
cols: 120, rows: 40,
|
||||
env: { LANG, LC_ALL, TERM }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Performance Audit: First Page Load
|
||||
|
||||
**Date**: 2026-02-18
|
||||
**Scope**: Browser first-load of Claudeman web UI (`/`)
|
||||
**Scope**: Browser first-load of Codeman web UI (`/`)
|
||||
**Method**: Static analysis by 4 parallel audit agents (server, frontend, SSE/xterm, asset pipeline)
|
||||
|
||||
---
|
||||
@@ -40,7 +40,7 @@ Browser hits /
|
||||
│
|
||||
│ [FIRST PAINT blocked until ALL CSS downloaded + parsed]
|
||||
│
|
||||
├── JS executes: new ClaudemanApp().init()
|
||||
├── JS executes: new CodemanApp().init()
|
||||
│ ├── initTerminal() ................... (SYNC: new Terminal() + terminal.open() → canvas creation)
|
||||
│ ├── connectSSE() → /api/events ....... (SSE → fires 'init' with getLightState())
|
||||
│ ├── loadState() → /api/status ........ (DUPLICATE #1: same data as SSE init!)
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
# Claudeman Performance Investigation Report
|
||||
# Codeman Performance Investigation Report
|
||||
|
||||
**Date**: 2026-02-20
|
||||
**Scope**: Why Claudeman feels sluggish when multiple Claude tabs are very busy
|
||||
**Scope**: Why Codeman feels sluggish when multiple Claude tabs are very busy
|
||||
**Method**: 4-agent parallel analysis of server, PTY pipeline, frontend, and background systems
|
||||
|
||||
---
|
||||
|
||||
## Executive Summary
|
||||
|
||||
When multiple Claude sessions are actively producing heavy terminal output (e.g., building, writing files, running tests), Claudeman's UI becomes sluggish. This investigation identified **14 bottlenecks** across 4 layers of the stack. The root cause is **cumulative event loop blocking** — no single operation is catastrophically slow, but dozens of small synchronous operations run on every PTY data chunk, and with N busy sessions producing chunks every few milliseconds, the event loop gets saturated.
|
||||
When multiple Claude sessions are actively producing heavy terminal output (e.g., building, writing files, running tests), Codeman's UI becomes sluggish. This investigation identified **14 bottlenecks** across 4 layers of the stack. The root cause is **cumulative event loop blocking** — no single operation is catastrophically slow, but dozens of small synchronous operations run on every PTY data chunk, and with N busy sessions producing chunks every few milliseconds, the event loop gets saturated.
|
||||
|
||||
The most impactful findings are ranked by severity below.
|
||||
|
||||
|
||||
@@ -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,7 +2,7 @@
|
||||
|
||||
## Overview
|
||||
|
||||
This plan details improvements to Claudeman's Ralph Loop system based on best practices from the Ralph Claude Code repository (https://github.com/frankbria/ralph-claude-code).
|
||||
This plan details improvements to Codeman's Ralph Loop system based on best practices from the Ralph Claude Code repository (https://github.com/frankbria/ralph-claude-code).
|
||||
|
||||
## Key Concepts to Implement
|
||||
|
||||
@@ -181,7 +181,7 @@ Three states: CLOSED → HALF_OPEN → OPEN
|
||||
### 3.1 Template Library
|
||||
- Bug Fix, Feature, Refactoring, Test Coverage, Documentation templates
|
||||
- Template selector in wizard
|
||||
- Custom templates in `~/.claudeman/templates/`
|
||||
- Custom templates in `~/.codeman/templates/`
|
||||
|
||||
### 3.2 Tool Permissions
|
||||
- Configure allowed Claude tools per loop
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
8. [Prompt Templates](#prompt-templates)
|
||||
9. [When to Use (and Not Use)](#when-to-use-and-not-use)
|
||||
10. [Real-World Examples](#real-world-examples)
|
||||
11. [Claudeman Implementation](#claudeman-implementation)
|
||||
11. [Codeman Implementation](#codeman-implementation)
|
||||
12. [Troubleshooting](#troubleshooting)
|
||||
|
||||
---
|
||||
@@ -148,18 +148,18 @@ The completion phrase pattern is the core contract between Claude and the loop s
|
||||
|
||||
### False Positive Prevention
|
||||
|
||||
The official implementation (and Claudeman) prevents false positives when completion phrases appear in:
|
||||
The official implementation (and Codeman) prevents false positives when completion phrases appear in:
|
||||
- Initial prompts
|
||||
- Documentation or examples
|
||||
- Comments
|
||||
|
||||
**Solution**: Claudeman uses **occurrence-based detection** to distinguish prompts from actual completions:
|
||||
**Solution**: Codeman uses **occurrence-based detection** to distinguish prompts from actual completions:
|
||||
- **1st occurrence**: Store as expected phrase (likely in the prompt)
|
||||
- **2nd occurrence**: Emit `completionDetected` (actual completion)
|
||||
- **If loop already active**: Emit immediately (explicit loop start via `/ralph-loop:ralph-loop`)
|
||||
|
||||
```typescript
|
||||
// From claudeman/src/ralph-tracker.ts
|
||||
// From codeman/src/ralph-tracker.ts
|
||||
private handleCompletionPhrase(phrase: string): void {
|
||||
const count = (this._completionPhraseCount.get(phrase) || 0) + 1;
|
||||
this._completionPhraseCount.set(phrase, count);
|
||||
@@ -631,9 +631,9 @@ Ask yourself:
|
||||
|
||||
---
|
||||
|
||||
## Claudeman Implementation
|
||||
## Codeman Implementation
|
||||
|
||||
Claudeman implements Ralph Wiggum tracking via the `RalphTracker` class in `src/ralph-tracker.ts`.
|
||||
Codeman implements Ralph Wiggum tracking via the `RalphTracker` class in `src/ralph-tracker.ts`.
|
||||
|
||||
### Auto-Detection Patterns
|
||||
|
||||
@@ -833,7 +833,7 @@ POST /api/sessions/:id/auto-clear
|
||||
- [Claude Fast - Autonomous Agent Loops](https://claudefa.st/blog/guide/mechanics/autonomous-agent-loops)
|
||||
- [DeepWiki - Ralph Loop](https://deepwiki.com/anthropics/claude-plugins-official/5.2.2-ralph-loop)
|
||||
|
||||
### Related Claudeman Files
|
||||
### Related Codeman Files
|
||||
- `src/ralph-tracker.ts` - Core detection engine
|
||||
- `src/ralph-loop.ts` - Task orchestration
|
||||
- `src/respawn-controller.ts` - Session cycling
|
||||
@@ -843,4 +843,4 @@ POST /api/sessions/:id/auto-clear
|
||||
|
||||
---
|
||||
|
||||
*This documentation is maintained as part of the Claudeman project. For updates, see the main [CLAUDE.md](../CLAUDE.md).*
|
||||
*This documentation is maintained as part of the Codeman project. For updates, see the main [CLAUDE.md](../CLAUDE.md).*
|
||||
|
||||
@@ -4,7 +4,7 @@ Date: 2026-02-17
|
||||
|
||||
## Scope
|
||||
|
||||
Full audit of the Claudeman notification system covering:
|
||||
Full audit of the Codeman notification system covering:
|
||||
- Backend event pipeline (server.ts, hooks-config.ts, team-watcher.ts, subagent-watcher.ts)
|
||||
- Frontend notification manager (app.js NotificationManager class, 4-layer architecture)
|
||||
- Settings UI and persistence (localStorage + server backup)
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# Claudeman Notification System - Backend Research Report
|
||||
# Codeman Notification System - Backend Research Report
|
||||
|
||||
Date: 2026-02-17
|
||||
|
||||
## Executive Summary
|
||||
|
||||
The Claudeman notification system is a **multi-layer, event-driven pipeline** that flows from backend event emitters, through SSE broadcasts, to a frontend `NotificationManager` class. The backend itself has no concept of "notifications" -- it broadcasts structured SSE events, and the frontend decides which events warrant user notification (browser notifications, audio alerts, tab title flashing, in-app notification drawer, tab alert badges).
|
||||
The Codeman notification system is a **multi-layer, event-driven pipeline** that flows from backend event emitters, through SSE broadcasts, to a frontend `NotificationManager` class. The backend itself has no concept of "notifications" -- it broadcasts structured SSE events, and the frontend decides which events warrant user notification (browser notifications, audio alerts, tab title flashing, in-app notification drawer, tab alert badges).
|
||||
|
||||
The system handles ~25 distinct notification-triggering SSE events across 5 categories: hook events, session lifecycle, respawn state machine, Ralph Loop, and UI actions.
|
||||
|
||||
@@ -96,14 +96,14 @@ Terminal and output data use separate batching pipelines that bypass `broadcast(
|
||||
|
||||
### 2.1 Hook Configuration Generator (Lines 24-67)
|
||||
|
||||
The `generateHooksConfig()` function creates `.claude/settings.local.json` entries that make Claude Code POST to Claudeman when hooks fire:
|
||||
The `generateHooksConfig()` function creates `.claude/settings.local.json` entries that make Claude Code POST to Codeman when hooks fire:
|
||||
|
||||
```typescript
|
||||
const curlCmd = (event: HookEventType) =>
|
||||
`HOOK_DATA=$(cat 2>/dev/null || echo '{}'); ` +
|
||||
`curl -s -X POST "$CLAUDEMAN_API_URL/api/hook-event" ` +
|
||||
`curl -s -X POST "$CODEMAN_API_URL/api/hook-event" ` +
|
||||
`-H 'Content-Type: application/json' ` +
|
||||
`-d "{\\"event\\":\\"${event}\\",\\"sessionId\\":\\"$CLAUDEMAN_SESSION_ID\\",\\"data\\":$HOOK_DATA}" ` +
|
||||
`-d "{\\"event\\":\\"${event}\\",\\"sessionId\\":\\"$CODEMAN_SESSION_ID\\",\\"data\\":$HOOK_DATA}" ` +
|
||||
`2>/dev/null || true`;
|
||||
```
|
||||
|
||||
@@ -143,8 +143,8 @@ The `sanitizeHookData()` function limits what gets broadcast:
|
||||
### 2.4 Environment Variables (Lines 70-101)
|
||||
|
||||
Two env vars are set per case directory via `updateCaseEnvVars()`:
|
||||
- `CLAUDEMAN_API_URL` -- server URL (e.g., `http://localhost:3000`)
|
||||
- `CLAUDEMAN_SESSION_ID` -- session identifier
|
||||
- `CODEMAN_API_URL` -- server URL (e.g., `http://localhost:3000`)
|
||||
- `CODEMAN_SESSION_ID` -- session identifier
|
||||
|
||||
These are resolved at runtime by the shell, so the hook config is static per case.
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
## Overview
|
||||
|
||||
Claudeman implements a **4-layer notification system** managed by the `NotificationManager` class (app.js lines 860-1265). The layers are:
|
||||
Codeman implements a **4-layer notification system** managed by the `NotificationManager` class (app.js lines 860-1265). The layers are:
|
||||
|
||||
1. **In-app notification drawer** (Layer 1) - badge + list UI
|
||||
2. **Document title flashing** (Layer 2) - tab title blinks when hidden
|
||||
@@ -32,19 +32,19 @@ updateTabTitle() {
|
||||
this.titleFlashInterval = setInterval(() => {
|
||||
this.titleFlashState = !this.titleFlashState;
|
||||
document.title = this.titleFlashState
|
||||
? `\u26A0\uFE0F (${this.unreadCount}) Claudeman`
|
||||
? `\u26A0\uFE0F (${this.unreadCount}) Codeman`
|
||||
: this.originalTitle;
|
||||
}, TITLE_FLASH_INTERVAL_MS);
|
||||
// Set immediately
|
||||
document.title = `\u26A0\uFE0F (${this.unreadCount}) Claudeman`;
|
||||
document.title = `\u26A0\uFE0F (${this.unreadCount}) Codeman`;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The title alternates between:
|
||||
- Warning emoji + unread count: `"(3) Claudeman"`
|
||||
- Original title: `"Claudeman"`
|
||||
- Warning emoji + unread count: `"(3) Codeman"`
|
||||
- Original title: `"Codeman"`
|
||||
|
||||
### What Triggers It
|
||||
Title flashing starts when `notify()` is called **while the tab is not visible** (`!this.isTabVisible`). The check is at lines 1026-1028:
|
||||
@@ -126,7 +126,7 @@ It shows a lightning bolt icon on a dark background. The favicon is **static** a
|
||||
|
||||
Browser notifications reference `/favicon.ico` as their icon (line 1130):
|
||||
```js
|
||||
const notif = new Notification(`Claudeman: ${title}`, {
|
||||
const notif = new Notification(`Codeman: ${title}`, {
|
||||
body,
|
||||
tag,
|
||||
icon: '/favicon.ico',
|
||||
@@ -306,7 +306,7 @@ Key detail: Title flash always stops when tab becomes visible, but **unread coun
|
||||
## 5. Focus/Blur Handling
|
||||
|
||||
### No window focus/blur listeners
|
||||
Claudeman does **not** use `window.addEventListener('focus')` or `window.addEventListener('blur')`. It relies solely on the Page Visibility API (`visibilitychange` + `pageshow`).
|
||||
Codeman does **not** use `window.addEventListener('focus')` or `window.addEventListener('blur')`. It relies solely on the Page Visibility API (`visibilitychange` + `pageshow`).
|
||||
|
||||
This is the correct modern approach. The `focus`/`blur` events are unreliable (fire for devtools, iframe changes, etc.) while `visibilitychange` accurately reflects whether the user can see the tab.
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# Claudeman Frontend Notification System -- Detailed Report
|
||||
# Codeman Frontend Notification System -- Detailed Report
|
||||
|
||||
> Generated: 2026-02-17
|
||||
> Source files analyzed:
|
||||
> - `/home/arkon/default/claudeman/src/web/public/app.js` (main frontend, ~15k lines)
|
||||
> - `/home/arkon/default/claudeman/src/web/public/index.html`
|
||||
> - `/home/arkon/default/claudeman/src/web/public/styles.css`
|
||||
> - `/home/arkon/default/claudeman/src/web/public/mobile.css`
|
||||
> - `/home/arkon/default/codeman/src/web/public/app.js` (main frontend, ~15k lines)
|
||||
> - `/home/arkon/default/codeman/src/web/public/index.html`
|
||||
> - `/home/arkon/default/codeman/src/web/public/styles.css`
|
||||
> - `/home/arkon/default/codeman/src/web/public/mobile.css`
|
||||
|
||||
---
|
||||
|
||||
@@ -87,8 +87,8 @@ const defaults = {
|
||||
#### Storage Keys (lines 952-956)
|
||||
|
||||
Device-specific localStorage keys prevent mobile settings from overriding desktop settings:
|
||||
- Desktop: `claudeman-notification-prefs`
|
||||
- Mobile: `claudeman-notification-prefs-mobile`
|
||||
- Desktop: `codeman-notification-prefs`
|
||||
- Mobile: `codeman-notification-prefs-mobile`
|
||||
|
||||
#### Version Migrations (lines 928-940)
|
||||
|
||||
@@ -260,8 +260,8 @@ Mobile override: full-width with safe area padding (mobile.css lines 1049-1058).
|
||||
|
||||
When the tab is not visible and there are unread notifications:
|
||||
1. `setInterval` at 1500ms toggles between:
|
||||
- Warning emoji + unread count: `"(3) Claudeman"`
|
||||
- Original title: `"Claudeman"`
|
||||
- Warning emoji + unread count: `"(3) Codeman"`
|
||||
- Original title: `"Codeman"`
|
||||
2. Set immediately on first notification (no wait for first interval tick)
|
||||
|
||||
### Stopping
|
||||
@@ -311,7 +311,7 @@ sendBrowserNotif() called
|
||||
### Notification Object (lines 1127-1143)
|
||||
|
||||
```js
|
||||
new Notification(`Claudeman: ${title}`, {
|
||||
new Notification(`Codeman: ${title}`, {
|
||||
body,
|
||||
tag, // Groups same-tag notifications (replaces previous with same tag)
|
||||
icon: '/favicon.ico',
|
||||
@@ -341,10 +341,10 @@ In settings (index.html line 993), a `<span class="settings-status" id="notifPer
|
||||
|
||||
The settings UI shows a hint (index.html line 996):
|
||||
```
|
||||
For remote access, HTTPS is required. Start with: claudeman web --https
|
||||
For remote access, HTTPS is required. Start with: codeman web --https
|
||||
```
|
||||
|
||||
Browser Notification API requires a secure context (HTTPS or localhost). This hint warns users who access Claudeman remotely over HTTP.
|
||||
Browser Notification API requires a secure context (HTTPS or localhost). This hint warns users who access Codeman remotely over HTTP.
|
||||
|
||||
---
|
||||
|
||||
@@ -579,7 +579,7 @@ On mobile devices:
|
||||
|
||||
### Mobile Storage Key (lines 952-956)
|
||||
|
||||
Mobile uses a separate localStorage key (`claudeman-notification-prefs-mobile`) so that enabling notifications on desktop does not accidentally enable them on a mobile device viewing the same Claudeman instance.
|
||||
Mobile uses a separate localStorage key (`codeman-notification-prefs-mobile`) so that enabling notifications on desktop does not accidentally enable them on a mobile device viewing the same Codeman instance.
|
||||
|
||||
### Mobile Drawer Styling (mobile.css lines 1049-1058)
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ The App Settings modal has tabs: Display, Claude CLI, Models, Paths, **Notificat
|
||||
|
||||
The Browser row includes an "Ask" button that calls `requestPermission()` and a status badge showing the current `Notification.permission` state.
|
||||
|
||||
There is also a hint: _"For remote access, HTTPS is required. Start with: `claudeman web --https`"_
|
||||
There is also a hint: _"For remote access, HTTPS is required. Start with: `codeman web --https`"_
|
||||
|
||||
#### Alerts Section
|
||||
| Setting | Element ID | Type | Default |
|
||||
@@ -54,16 +54,16 @@ Notification preferences are stored in **two places simultaneously**:
|
||||
|
||||
#### Layer 1: localStorage (primary, device-specific)
|
||||
|
||||
- **Storage key**: `claudeman-notification-prefs` (desktop) or `claudeman-notification-prefs-mobile` (mobile)
|
||||
- **Storage key**: `codeman-notification-prefs` (desktop) or `codeman-notification-prefs-mobile` (mobile)
|
||||
- Determined by `NotificationManager.getStorageKey()` at line 953, which calls `MobileDetection.getDeviceType()`
|
||||
- Device type is based on `window.innerWidth`: `<430` = mobile, `430-768` = tablet, `>=768` = desktop
|
||||
- Read in `loadPreferences()` (line 896), written in `savePreferences()` (line 958)
|
||||
|
||||
#### Layer 2: Server-side (`~/.claudeman/settings.json`)
|
||||
#### Layer 2: Server-side (`~/.codeman/settings.json`)
|
||||
|
||||
- On save, notification prefs are bundled with app settings: `{ ...settings, notificationPreferences: notifPrefsToSave }` (line 9475)
|
||||
- Sent via `PUT /api/settings` to the Fastify server
|
||||
- Server does a shallow merge: `const merged = { ...existing, ...settings }` then writes to `~/.claudeman/settings.json` (line 3098 of server.ts)
|
||||
- Server does a shallow merge: `const merged = { ...existing, ...settings }` then writes to `~/.codeman/settings.json` (line 3098 of server.ts)
|
||||
- The `notificationPreferences` key sits at the top level of the settings JSON alongside app settings
|
||||
|
||||
#### Load priority
|
||||
@@ -109,8 +109,8 @@ On startup, `loadAppSettingsFromServer()` (line 9787) fetches from server and:
|
||||
### App settings (separate from notification prefs)
|
||||
|
||||
App settings use a different device-specific localStorage key:
|
||||
- Desktop: `claudeman-app-settings`
|
||||
- Mobile: `claudeman-app-settings-mobile`
|
||||
- Desktop: `codeman-app-settings`
|
||||
- Mobile: `codeman-app-settings-mobile`
|
||||
- Determined by `getSettingsStorageKey()` at line 9562
|
||||
|
||||
|
||||
@@ -213,8 +213,8 @@ const AUTO_CLOSE_NOTIFICATION_MS = 8000; // Auto-close browser notifications
|
||||
### Separate storage keys - YES
|
||||
|
||||
Desktop and mobile use completely separate localStorage keys:
|
||||
- **Notification prefs**: `claudeman-notification-prefs` vs `claudeman-notification-prefs-mobile`
|
||||
- **App settings**: `claudeman-app-settings` vs `claudeman-app-settings-mobile`
|
||||
- **Notification prefs**: `codeman-notification-prefs` vs `codeman-notification-prefs-mobile`
|
||||
- **App settings**: `codeman-app-settings` vs `codeman-app-settings-mobile`
|
||||
|
||||
### Different defaults - YES
|
||||
|
||||
@@ -303,7 +303,7 @@ The settings UI shows the current permission state via a status badge:
|
||||
|
||||
### HTTPS requirement
|
||||
|
||||
Browser notifications require HTTPS for remote access. The settings UI includes a hint: _"For remote access, HTTPS is required. Start with: `claudeman web --https`"_. On localhost, HTTP works fine.
|
||||
Browser notifications require HTTPS for remote access. The settings UI includes a hint: _"For remote access, HTTPS is required. Start with: `codeman web --https`"_. On localhost, HTTP works fine.
|
||||
|
||||
|
||||
## 7. Audio Setting
|
||||
@@ -354,7 +354,7 @@ Yes, given the prerequisites above are met. However, due to the category key mis
|
||||
|
||||
Notification preferences are **strictly global**. There is no per-session notification configuration.
|
||||
|
||||
- The `NotificationManager` is a singleton on the `ClaudemanApp` instance (line 1404)
|
||||
- The `NotificationManager` is a singleton on the `CodemanApp` instance (line 1404)
|
||||
- Preferences are loaded once from localStorage (line 878)
|
||||
- All sessions share the same notification rules
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Terminal Anti-Flicker System
|
||||
|
||||
Claude Code uses [Ink](https://github.com/vadimdemedes/ink) (React for terminals), which redraws the entire screen on every state change. Without special handling, users see constant flickering. Claudeman implements a 6-layer anti-flicker pipeline.
|
||||
Claude Code uses [Ink](https://github.com/vadimdemedes/ink) (React for terminals), which redraws the entire screen on every state change. Without special handling, users see constant flickering. Codeman implements a 6-layer anti-flicker pipeline.
|
||||
|
||||
## Pipeline Overview
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Claudeman TypeScript Improvement Suggestions
|
||||
# Codeman TypeScript Improvement Suggestions
|
||||
|
||||
**Generated**: February 2026
|
||||
**Based on**: Research into TypeScript best practices (2024-2025) and codebase analysis
|
||||
@@ -229,33 +229,33 @@ Replace string-based errors with typed errors:
|
||||
|
||||
```typescript
|
||||
// src/errors.ts
|
||||
export class ClaudemanError extends Error {
|
||||
export class CodemanError extends Error {
|
||||
constructor(
|
||||
message: string,
|
||||
public code: string,
|
||||
public context?: Record<string, unknown>
|
||||
) {
|
||||
super(message);
|
||||
Object.setPrototypeOf(this, ClaudemanError.prototype);
|
||||
this.name = 'ClaudemanError';
|
||||
Object.setPrototypeOf(this, CodemanError.prototype);
|
||||
this.name = 'CodemanError';
|
||||
}
|
||||
}
|
||||
|
||||
export class SessionError extends ClaudemanError {
|
||||
export class SessionError extends CodemanError {
|
||||
constructor(message: string, code: string, public sessionId: string) {
|
||||
super(message, code, { sessionId });
|
||||
this.name = 'SessionError';
|
||||
}
|
||||
}
|
||||
|
||||
export class ValidationError extends ClaudemanError {
|
||||
export class ValidationError extends CodemanError {
|
||||
constructor(message: string, public field: string, public value: unknown) {
|
||||
super(message, 'VALIDATION_ERROR', { field, value });
|
||||
this.name = 'ValidationError';
|
||||
}
|
||||
}
|
||||
|
||||
export class ScreenError extends ClaudemanError {
|
||||
export class ScreenError extends CodemanError {
|
||||
constructor(message: string, public screenName: string, public operation: string) {
|
||||
super(message, 'SCREEN_ERROR', { screenName, operation });
|
||||
this.name = 'ScreenError';
|
||||
@@ -324,7 +324,7 @@ Enforce ID formats at compile time:
|
||||
|
||||
```typescript
|
||||
type CycleIdFormat = `${string}:cycle-${number}`;
|
||||
type ScreenSessionName = `claudeman-${string}`;
|
||||
type ScreenSessionName = `codeman-${string}`;
|
||||
|
||||
interface RespawnCycleMetrics {
|
||||
cycleId: CycleIdFormat; // Enforces format at compile time
|
||||
|
||||
@@ -56,7 +56,7 @@ The blue-tinted voice button in the accessory bar is distinctive but subtle. Whe
|
||||
|
||||
Confirmed by research: Web Speech API is the right choice.
|
||||
- **Free, fast (150-300ms interim), trivial complexity**
|
||||
- Chrome + Safari = ~70% of users, ~95% of Claudeman's target audience (devs on Chrome)
|
||||
- Chrome + Safari = ~70% of users, ~95% of Codeman's target audience (devs on Chrome)
|
||||
- Works on localhost without HTTPS
|
||||
- Accuracy is adequate for English command dictation
|
||||
- Deepgram streaming (Phase 2 optional) only if accuracy complaints arise
|
||||
|
||||
+40
-40
@@ -1,14 +1,14 @@
|
||||
#!/usr/bin/env bash
|
||||
# Claudeman Universal Installer
|
||||
# https://github.com/Ark0N/claudeman
|
||||
# Codeman Universal Installer
|
||||
# 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:
|
||||
# CLAUDEMAN_NONINTERACTIVE=1 - Skip all prompts (for CI/automation)
|
||||
# CLAUDEMAN_INSTALL_DIR - Custom install directory (default: ~/.claudeman/app)
|
||||
# CLAUDEMAN_SKIP_SYSTEMD=1 - Skip systemd service setup prompt
|
||||
# CLAUDEMAN_NODE_VERSION - Node.js major version to install (default: 22)
|
||||
# CODEMAN_NONINTERACTIVE=1 - Skip all prompts (for CI/automation)
|
||||
# CODEMAN_INSTALL_DIR - Custom install directory (default: ~/.codeman/app)
|
||||
# CODEMAN_SKIP_SYSTEMD=1 - Skip systemd service setup prompt
|
||||
# CODEMAN_NODE_VERSION - Node.js major version to install (default: 22)
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
@@ -16,12 +16,12 @@ set -euo pipefail
|
||||
# Configuration
|
||||
# ============================================================================
|
||||
|
||||
INSTALL_DIR="${CLAUDEMAN_INSTALL_DIR:-$HOME/.claudeman/app}"
|
||||
REPO_URL="https://github.com/Ark0N/claudeman.git"
|
||||
INSTALL_DIR="${CODEMAN_INSTALL_DIR:-$HOME/.codeman/app}"
|
||||
REPO_URL="https://github.com/Ark0N/Codeman.git"
|
||||
MIN_NODE_VERSION=18
|
||||
TARGET_NODE_VERSION="${CLAUDEMAN_NODE_VERSION:-22}"
|
||||
NONINTERACTIVE="${CLAUDEMAN_NONINTERACTIVE:-0}"
|
||||
SKIP_SYSTEMD="${CLAUDEMAN_SKIP_SYSTEMD:-0}"
|
||||
TARGET_NODE_VERSION="${CODEMAN_NODE_VERSION:-22}"
|
||||
NONINTERACTIVE="${CODEMAN_NONINTERACTIVE:-0}"
|
||||
SKIP_SYSTEMD="${CODEMAN_SKIP_SYSTEMD:-0}"
|
||||
|
||||
# Claude CLI search paths (from src/session.ts)
|
||||
CLAUDE_SEARCH_PATHS=(
|
||||
@@ -611,15 +611,15 @@ add_to_path() {
|
||||
|
||||
if [[ "$shell_name" == "fish" ]]; then
|
||||
echo "" >> "$profile"
|
||||
echo "# Added by Claudeman installer" >> "$profile"
|
||||
echo "# Added by Codeman installer" >> "$profile"
|
||||
echo "fish_add_path $bin_dir" >> "$profile"
|
||||
else
|
||||
echo "" >> "$profile"
|
||||
echo "# Added by Claudeman installer" >> "$profile"
|
||||
echo "# Added by Codeman installer" >> "$profile"
|
||||
echo "export PATH=\"$bin_dir:\$PATH\"" >> "$profile"
|
||||
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"
|
||||
|
||||
success "Added to $profile"
|
||||
@@ -636,7 +636,7 @@ setup_sc_alias() {
|
||||
fi
|
||||
|
||||
echo "" >> "$profile"
|
||||
echo "# Claudeman tmux session shortcut" >> "$profile"
|
||||
echo "# Codeman tmux session shortcut" >> "$profile"
|
||||
echo "alias sc='tmux-chooser'" >> "$profile"
|
||||
|
||||
info "Added 'sc' alias for tmux-chooser"
|
||||
@@ -648,7 +648,7 @@ setup_sc_alias() {
|
||||
|
||||
setup_systemd_service() {
|
||||
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..."
|
||||
|
||||
@@ -661,7 +661,7 @@ setup_systemd_service() {
|
||||
# Create service file
|
||||
cat > "$service_file" << EOF
|
||||
[Unit]
|
||||
Description=Claudeman Web Server
|
||||
Description=Codeman Web Server
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
@@ -681,7 +681,7 @@ EOF
|
||||
systemctl --user daemon-reload
|
||||
|
||||
# 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)
|
||||
if command -v loginctl &>/dev/null; then
|
||||
@@ -689,7 +689,7 @@ EOF
|
||||
fi
|
||||
|
||||
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
|
||||
install_dependency "git" "$os" "$distro"
|
||||
else
|
||||
die "Git is required to install Claudeman."
|
||||
die "Git is required to install Codeman."
|
||||
fi
|
||||
else
|
||||
success "Git is installed"
|
||||
@@ -781,7 +781,7 @@ main() {
|
||||
# Rehash to pick up new node
|
||||
hash -r 2>/dev/null || true
|
||||
else
|
||||
die "Node.js $MIN_NODE_VERSION+ is required to run Claudeman."
|
||||
die "Node.js $MIN_NODE_VERSION+ is required to run Codeman."
|
||||
fi
|
||||
else
|
||||
local node_ver
|
||||
@@ -832,7 +832,7 @@ main() {
|
||||
# Clone/Update Repository
|
||||
# ========================================================================
|
||||
|
||||
info "Installing Claudeman to $INSTALL_DIR..."
|
||||
info "Installing Codeman to $INSTALL_DIR..."
|
||||
|
||||
if [[ -d "$INSTALL_DIR/.git" ]]; then
|
||||
info "Existing installation found, updating..."
|
||||
@@ -882,8 +882,8 @@ main() {
|
||||
local symlink_dir="$HOME/.local/bin"
|
||||
mkdir -p "$symlink_dir" 2>/dev/null || true
|
||||
if [[ -d "$symlink_dir" ]]; then
|
||||
ln -sf "$INSTALL_DIR/dist/index.js" "$symlink_dir/claudeman"
|
||||
info "Created symlink: $symlink_dir/claudeman"
|
||||
ln -sf "$INSTALL_DIR/dist/index.js" "$symlink_dir/codeman"
|
||||
info "Created symlink: $symlink_dir/codeman"
|
||||
|
||||
# Install tmux-chooser as 'tmux-chooser' command
|
||||
if [[ -f "$INSTALL_DIR/scripts/tmux-chooser.sh" ]]; then
|
||||
@@ -916,16 +916,16 @@ main() {
|
||||
|
||||
echo ""
|
||||
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 ""
|
||||
echo -e " ${BOLD}Quick Start:${NC}"
|
||||
echo ""
|
||||
echo -e " ${CYAN}# Start the web server${NC}"
|
||||
echo -e " claudeman web"
|
||||
echo -e " codeman web"
|
||||
echo ""
|
||||
echo -e " ${CYAN}# Start with HTTPS (only needed for remote access)${NC}"
|
||||
echo -e " claudeman web --https"
|
||||
echo -e " codeman web --https"
|
||||
echo ""
|
||||
echo -e " ${CYAN}# Open in browser${NC}"
|
||||
echo -e " http://localhost:3000"
|
||||
@@ -937,21 +937,21 @@ main() {
|
||||
echo -e " ${CYAN}sc -h${NC} # Help"
|
||||
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 ""
|
||||
echo -e " ${CYAN}systemctl --user start claudeman-web${NC} # Start"
|
||||
echo -e " ${CYAN}systemctl --user status claudeman-web${NC} # Check status"
|
||||
echo -e " ${CYAN}journalctl --user -u claudeman-web -f${NC} # View logs"
|
||||
echo -e " ${CYAN}systemctl --user start codeman-web${NC} # Start"
|
||||
echo -e " ${CYAN}systemctl --user status codeman-web${NC} # Check status"
|
||||
echo -e " ${CYAN}journalctl --user -u codeman-web -f${NC} # View logs"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
echo -e " ${BOLD}Documentation:${NC}"
|
||||
echo -e " https://github.com/Ark0N/claudeman"
|
||||
echo -e " https://github.com/Ark0N/Codeman"
|
||||
echo ""
|
||||
|
||||
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 ""
|
||||
fi
|
||||
@@ -959,20 +959,20 @@ main() {
|
||||
# Check if PATH needs reload in user's shell
|
||||
local profile
|
||||
profile=$(detect_shell_profile)
|
||||
if ! command -v claudeman &>/dev/null 2>&1; then
|
||||
echo -e " ${YELLOW}Run this to start using claudeman now:${NC}"
|
||||
if ! command -v codeman &>/dev/null 2>&1; then
|
||||
echo -e " ${YELLOW}Run this to start using codeman now:${NC}"
|
||||
echo ""
|
||||
echo -e " ${CYAN}source $profile && claudeman web${NC}"
|
||||
echo -e " ${CYAN}source $profile && codeman web${NC}"
|
||||
echo ""
|
||||
fi
|
||||
}
|
||||
|
||||
update() {
|
||||
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
|
||||
|
||||
info "Updating Claudeman..."
|
||||
info "Updating Codeman..."
|
||||
cd "$INSTALL_DIR"
|
||||
git fetch --quiet origin
|
||||
git reset --hard origin/master --quiet
|
||||
@@ -980,7 +980,7 @@ update() {
|
||||
npm run build --quiet 2>/dev/null || npm run build
|
||||
success "Updated to $(node -e "console.log(require('./package.json').version)")"
|
||||
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 ""
|
||||
}
|
||||
|
||||
|
||||
@@ -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.**
|
||||
|
||||
## 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
|
||||
- **Touch/swipe interactions** — CDP trusted events (Chromium) + synthetic fallback (WebKit)
|
||||
|
||||
@@ -126,7 +126,7 @@ export const BODY_CLASSES = {
|
||||
|
||||
// Settings localStorage keys
|
||||
export const STORAGE_KEYS = {
|
||||
SETTINGS_MOBILE: 'claudeman-app-settings-mobile',
|
||||
SETTINGS_DESKTOP: 'claudeman-app-settings',
|
||||
NOTIFICATION_PREFS_MOBILE: 'claudeman-notification-prefs-mobile',
|
||||
SETTINGS_MOBILE: 'codeman-app-settings-mobile',
|
||||
SETTINGS_DESKTOP: 'codeman-app-settings',
|
||||
NOTIFICATION_PREFS_MOBILE: 'codeman-notification-prefs-mobile',
|
||||
} as const;
|
||||
|
||||
@@ -43,7 +43,7 @@ describe('Settings Modal', () => {
|
||||
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(() => {
|
||||
return (window as any).app?.getSettingsStorageKey?.() ?? null;
|
||||
});
|
||||
@@ -53,10 +53,10 @@ describe('Settings Modal', () => {
|
||||
} else {
|
||||
// Verify by checking what key is used when saving settings
|
||||
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(() => {
|
||||
return localStorage.getItem('claudeman-app-settings-mobile');
|
||||
return localStorage.getItem('codeman-app-settings-mobile');
|
||||
});
|
||||
expect(stored).toContain('testFlag');
|
||||
}
|
||||
|
||||
Generated
+3
-3
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "claudeman",
|
||||
"name": "codeman",
|
||||
"version": "0.1619",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "claudeman",
|
||||
"name": "codeman",
|
||||
"version": "0.1619",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
@@ -25,7 +25,7 @@
|
||||
"zod": "^4.3.6"
|
||||
},
|
||||
"bin": {
|
||||
"claudeman": "dist/index.js"
|
||||
"codeman": "dist/index.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.19.33",
|
||||
|
||||
+5
-5
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "claudeman",
|
||||
"name": "codeman",
|
||||
"version": "0.1650",
|
||||
"description": "The missing control plane for Claude Code - run 20 autonomous agents with real-time monitoring and session persistence",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"bin": {
|
||||
"claudeman": "./dist/index.js"
|
||||
"codeman": "./dist/index.js"
|
||||
},
|
||||
"scripts": {
|
||||
"postinstall": "node scripts/postinstall.js",
|
||||
@@ -71,12 +71,12 @@
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/Ark0N/claudeman.git"
|
||||
"url": "git+https://github.com/Ark0N/Codeman.git"
|
||||
},
|
||||
"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": [
|
||||
"dist",
|
||||
"scripts/postinstall.js",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
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
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -39,7 +39,7 @@ Server echoes 'h' ←───────────────────
|
||||
|
||||
## 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
|
||||
|
||||
@@ -375,4 +375,4 @@ Overlay hides when scrolled up (`viewportY !== baseY`). Debounced re-render when
|
||||
|
||||
## License
|
||||
|
||||
MIT — [Claudeman](https://github.com/Ark0N/Claudeman) Contributors
|
||||
MIT — [Codeman](https://github.com/Ark0N/Codeman) Contributors
|
||||
|
||||
@@ -32,10 +32,10 @@
|
||||
"addon"
|
||||
],
|
||||
"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": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/Ark0N/Claudeman.git",
|
||||
"url": "git+https://github.com/Ark0N/Codeman.git",
|
||||
"directory": "packages/xterm-zerolag-input"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
@@ -88,7 +88,7 @@ async function main() {
|
||||
const page = await playwrightBrowser.newPage();
|
||||
await page.goto(`http://localhost:${PORTS.playwright}`);
|
||||
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();
|
||||
});
|
||||
|
||||
@@ -98,7 +98,7 @@ async function main() {
|
||||
await page.goto(`http://localhost:${PORTS.playwright}`);
|
||||
const logo = await page.locator('.header-brand .logo').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');
|
||||
await page.close();
|
||||
});
|
||||
@@ -149,7 +149,7 @@ async function main() {
|
||||
const page = await puppeteerBrowser.newPage();
|
||||
await page.goto(`http://localhost:${PORTS.puppeteer}`);
|
||||
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();
|
||||
});
|
||||
|
||||
@@ -159,7 +159,7 @@ async function main() {
|
||||
await page.goto(`http://localhost:${PORTS.puppeteer}`);
|
||||
const logo = await page.$eval('.header-brand .logo', 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');
|
||||
await page.close();
|
||||
});
|
||||
@@ -202,7 +202,7 @@ async function main() {
|
||||
agentBrowser(`open http://localhost:${PORTS.agentBrowser}`);
|
||||
await new Promise(r => setTimeout(r, 2000));
|
||||
const title = agentBrowserJson('get title');
|
||||
agentBrowserAvailable = title.title === 'Claudeman';
|
||||
agentBrowserAvailable = title.title === 'Codeman';
|
||||
console.log(' Browser launched');
|
||||
|
||||
// Test 1: Page load
|
||||
@@ -210,14 +210,14 @@ async function main() {
|
||||
agentBrowser(`open http://localhost:${PORTS.agentBrowser}`);
|
||||
await new Promise(r => setTimeout(r, 1000));
|
||||
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
|
||||
await runBenchmark('element-selection', 'agent-browser', async () => {
|
||||
const logo = agentBrowserJson('get text ".header-brand .logo"');
|
||||
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');
|
||||
});
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ function makeSession(id, name, mode, status, extra = {}) {
|
||||
id,
|
||||
pid: status === 'idle' ? null : 12345 + Math.floor(Math.random() * 10000),
|
||||
status,
|
||||
workingDir: '/home/arkon/claudeman-cases/testcase',
|
||||
workingDir: '/home/arkon/codeman-cases/testcase',
|
||||
currentTaskId: null,
|
||||
createdAt: Date.now() - 3600000,
|
||||
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} ${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}`,
|
||||
@@ -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`,
|
||||
`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`,
|
||||
'',
|
||||
`${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 = {
|
||||
'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: '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' },
|
||||
],
|
||||
'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/claudeman/vitest.config.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/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' },
|
||||
],
|
||||
};
|
||||
@@ -436,9 +436,9 @@ const SUBAGENT_ACTIVITY = {
|
||||
// Subagent spawn terminal content
|
||||
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}`,
|
||||
'',
|
||||
`${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() {
|
||||
console.log('='.repeat(60));
|
||||
console.log('Claudeman README Screenshot Capture');
|
||||
console.log('Codeman README Screenshot Capture');
|
||||
console.log('='.repeat(60));
|
||||
console.log(`Port: ${PORT} | Viewport: ${VIEWPORT.width}x${VIEWPORT.height}`);
|
||||
console.log('');
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/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,
|
||||
* sends a prompt that spawns 3 parallel agents, and captures a tight
|
||||
@@ -69,7 +69,7 @@ async function capture() {
|
||||
try {
|
||||
// Create case and seed with real source files
|
||||
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 caseTest = join(caseDir, 'test');
|
||||
mkdirSync(caseSrc, { recursive: true });
|
||||
@@ -123,7 +123,7 @@ async function capture() {
|
||||
|
||||
// Configure settings: enable subagents, disable Files pane
|
||||
await setupPage.evaluate(() => {
|
||||
const existing = JSON.parse(localStorage.getItem('claudeman-app-settings') || '{}');
|
||||
const existing = JSON.parse(localStorage.getItem('codeman-app-settings') || '{}');
|
||||
Object.assign(existing, {
|
||||
showSubagents: true,
|
||||
subagentTrackingEnabled: true,
|
||||
@@ -132,7 +132,7 @@ async function capture() {
|
||||
showProjectInsights: 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 sleep(1000);
|
||||
@@ -227,7 +227,7 @@ async function capture() {
|
||||
|
||||
// Re-apply settings (new context = fresh localStorage)
|
||||
await recordPage.evaluate(() => {
|
||||
const existing = JSON.parse(localStorage.getItem('claudeman-app-settings') || '{}');
|
||||
const existing = JSON.parse(localStorage.getItem('codeman-app-settings') || '{}');
|
||||
Object.assign(existing, {
|
||||
showSubagents: true,
|
||||
subagentTrackingEnabled: true,
|
||||
@@ -236,7 +236,7 @@ async function capture() {
|
||||
showProjectInsights: 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 sleep(1000);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/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.
|
||||
*
|
||||
@@ -23,7 +23,7 @@ import { mkdirSync, existsSync, unlinkSync, rmSync } from 'fs';
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const PROJECT_ROOT = join(__dirname, '..');
|
||||
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
|
||||
const PORT = 3198; // Unique port for capture script (avoid 3199 conflicts)
|
||||
@@ -58,10 +58,10 @@ let serverProcess = null;
|
||||
let browser = null;
|
||||
|
||||
/**
|
||||
* Start the Claudeman web server
|
||||
* Start the Codeman web server
|
||||
*/
|
||||
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)], {
|
||||
cwd: PROJECT_ROOT,
|
||||
@@ -222,7 +222,7 @@ async function configureSettings(page) {
|
||||
subagentActiveTabOnly: false, // Show all subagents regardless of active tab
|
||||
showMonitor: true,
|
||||
};
|
||||
localStorage.setItem('claudeman-app-settings', JSON.stringify(settings));
|
||||
localStorage.setItem('codeman-app-settings', JSON.stringify(settings));
|
||||
});
|
||||
await page.reload({ waitUntil: 'domcontentloaded' });
|
||||
await sleep(500);
|
||||
@@ -292,7 +292,7 @@ function convertToGif(inputPath, outputPath) {
|
||||
*/
|
||||
async function capture() {
|
||||
console.log('='.repeat(60));
|
||||
console.log('Claudeman Subagent Screenshot Capture');
|
||||
console.log('Codeman Subagent Screenshot Capture');
|
||||
console.log('='.repeat(60));
|
||||
console.log(`Output directory: ${OUTPUT_DIR}`);
|
||||
console.log(`Case name: ${CASE_NAME}`);
|
||||
@@ -323,8 +323,8 @@ async function capture() {
|
||||
const page = await context.newPage();
|
||||
page.setDefaultTimeout(30000);
|
||||
|
||||
// Navigate to Claudeman
|
||||
console.log('Loading Claudeman...');
|
||||
// Navigate to Codeman
|
||||
console.log('Loading Codeman...');
|
||||
await page.goto(BASE_URL, { waitUntil: 'domcontentloaded' });
|
||||
await sleep(1000);
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
[Unit]
|
||||
Description=Claudeman Web Server
|
||||
Description=Codeman Web Server
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
WorkingDirectory=/home/arkon/default/claudeman
|
||||
WorkingDirectory=/home/arkon/default/codeman
|
||||
ExecStart=/usr/bin/node dist/index.js web --https
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
@@ -15,7 +15,7 @@ Environment=HOME=/home/arkon
|
||||
# Logging
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
SyslogIdentifier=claudeman
|
||||
SyslogIdentifier=codeman
|
||||
|
||||
# Resource limits
|
||||
LimitNOFILE=65536
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/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)
|
||||
* Includes keyboard simulation
|
||||
*/
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
|
||||
SESSION_ID="${1:-5ecdf859-3605-42d8-b0a2-f8f0a1b5ff98}"
|
||||
DURATION_HOURS="${2:-12}"
|
||||
ISSUES_FILE="/home/arkon/default/claudeman/respawn-monitor-issues.md"
|
||||
FIXES_FILE="/home/arkon/default/claudeman/respawn-monitor-fixes.md"
|
||||
ISSUES_FILE="/home/arkon/default/codeman/respawn-monitor-issues.md"
|
||||
FIXES_FILE="/home/arkon/default/codeman/respawn-monitor-fixes.md"
|
||||
API_URL="https://localhost:3000"
|
||||
POLL_INTERVAL=60 # seconds
|
||||
|
||||
@@ -54,7 +54,7 @@ while [ $(date +%s) -lt $END_TIME ]; do
|
||||
|
||||
if [ -z "$RESP" ]; then
|
||||
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
|
||||
continue
|
||||
fi
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Claudeman postinstall verification script
|
||||
* Codeman postinstall verification script
|
||||
* Runs after `npm install` to check environment readiness
|
||||
*/
|
||||
|
||||
@@ -94,7 +94,7 @@ function getTmuxInstallInstructions() {
|
||||
// Main Checks
|
||||
// ============================================================================
|
||||
|
||||
console.log(colors.bold('Claudeman postinstall check...'));
|
||||
console.log(colors.bold('Codeman postinstall check...'));
|
||||
console.log('');
|
||||
|
||||
let hasWarnings = false;
|
||||
@@ -259,11 +259,11 @@ if (hasErrors) {
|
||||
|
||||
console.log(colors.bold('Next steps:'));
|
||||
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'));
|
||||
} else {
|
||||
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'));
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import { join } from 'node:path';
|
||||
import { homedir } from 'node:os';
|
||||
|
||||
const BASE_URL = 'https://localhost:3000';
|
||||
const CASE_NAME = 'claudeman-ios';
|
||||
const CASE_NAME = 'codeman-ios';
|
||||
|
||||
async function main() {
|
||||
console.log('🎬 Starting Ralph Loop Wizard demo...\n');
|
||||
@@ -25,7 +25,7 @@ async function main() {
|
||||
});
|
||||
const page = await context.newPage();
|
||||
|
||||
console.log('📱 Opening Claudeman web UI...');
|
||||
console.log('📱 Opening Codeman web UI...');
|
||||
await page.goto(BASE_URL);
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
@@ -41,8 +41,8 @@ async function main() {
|
||||
await page.selectOption('#ralphCaseSelect', CASE_NAME);
|
||||
|
||||
// Load initprompt.md if it exists
|
||||
const initPromptPath = join(homedir(), 'claudeman-cases', CASE_NAME, 'initprompt.md');
|
||||
let taskDescription = 'Build an awesome iOS app for Claudeman!';
|
||||
const initPromptPath = join(homedir(), 'codeman-cases', CASE_NAME, 'initprompt.md');
|
||||
let taskDescription = 'Build an awesome iOS app for Codeman!';
|
||||
if (existsSync(initPromptPath)) {
|
||||
taskDescription = readFileSync(initPromptPath, 'utf-8').trim();
|
||||
console.log(`📝 Loaded initprompt.md (${taskDescription.length} chars)`);
|
||||
|
||||
@@ -10,8 +10,8 @@ import { join } from 'node:path';
|
||||
import { homedir } from 'node:os';
|
||||
|
||||
const BASE_URL = 'https://localhost:3000';
|
||||
const CASE_NAME = 'claudeman-ios';
|
||||
const CASE_PATH = join(homedir(), 'claudeman-cases', CASE_NAME);
|
||||
const CASE_NAME = 'codeman-ios';
|
||||
const CASE_PATH = join(homedir(), 'codeman-cases', CASE_NAME);
|
||||
|
||||
async function main() {
|
||||
// Read initprompt.md
|
||||
@@ -49,7 +49,7 @@ async function main() {
|
||||
page.on('pageerror', err => console.log(`[Browser Error] ${err.message}`));
|
||||
|
||||
try {
|
||||
console.log('[1/10] Navigating to Claudeman...');
|
||||
console.log('[1/10] Navigating to Codeman...');
|
||||
await page.goto(BASE_URL, { timeout: 10000 });
|
||||
|
||||
console.log('[2/10] Clicking Ralph Loop button (.btn-ralph)...');
|
||||
@@ -122,7 +122,7 @@ async function main() {
|
||||
});
|
||||
|
||||
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];
|
||||
|
||||
if (session) {
|
||||
@@ -151,7 +151,7 @@ async function main() {
|
||||
console.log('SUCCESS! Ralph Loop started.');
|
||||
console.log('========================================');
|
||||
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('');
|
||||
console.log('Session is now running. View at: https://localhost:3000');
|
||||
|
||||
@@ -8,7 +8,7 @@ import { join } from 'node:path';
|
||||
import { homedir } from 'node:os';
|
||||
|
||||
const BASE_URL = 'https://localhost:3000';
|
||||
const CASE_NAME = 'claudeman-ios';
|
||||
const CASE_NAME = 'codeman-ios';
|
||||
|
||||
async function main() {
|
||||
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 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.waitForLoadState('networkidle');
|
||||
|
||||
@@ -36,8 +36,8 @@ async function main() {
|
||||
await page.selectOption('#ralphCaseSelect', CASE_NAME);
|
||||
|
||||
// Load initprompt.md
|
||||
const initPromptPath = join(homedir(), 'claudeman-cases', CASE_NAME, 'initprompt.md');
|
||||
let taskDescription = 'Build an awesome iOS app for Claudeman!';
|
||||
const initPromptPath = join(homedir(), 'codeman-cases', CASE_NAME, 'initprompt.md');
|
||||
let taskDescription = 'Build an awesome iOS app for Codeman!';
|
||||
if (existsSync(initPromptPath)) {
|
||||
taskDescription = readFileSync(initPromptPath, 'utf-8').trim();
|
||||
console.log(`5. Loaded initprompt.md (${taskDescription.length} chars)`);
|
||||
@@ -87,13 +87,13 @@ async function main() {
|
||||
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('SUCCESS! Ralph Loop started.');
|
||||
console.log('========================================');
|
||||
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('\nThe session is now running on your production server.');
|
||||
console.log('View it at: https://localhost:3000');
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/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
|
||||
*/
|
||||
|
||||
@@ -33,8 +33,8 @@ async function main() {
|
||||
const page = await context.newPage();
|
||||
|
||||
try {
|
||||
// Navigate to Claudeman
|
||||
console.log('Navigating to Claudeman...');
|
||||
// Navigate to Codeman
|
||||
console.log('Navigating to Codeman...');
|
||||
await page.goto(BASE_URL, { waitUntil: 'networkidle', timeout: 30000 });
|
||||
console.log('Page loaded: ' + await page.title());
|
||||
|
||||
@@ -63,9 +63,9 @@ async function main() {
|
||||
await page.waitForTimeout(2000);
|
||||
}
|
||||
|
||||
// Get active session (use claudeman session if exists)
|
||||
const claudemanSession = sessions.find(s => s.workingDir?.includes('claudeman'));
|
||||
const sessionId = claudemanSession?.id || sessions[0]?.id;
|
||||
// Get active session (use codeman session if exists)
|
||||
const codemanSession = sessions.find(s => s.workingDir?.includes('codeman'));
|
||||
const sessionId = codemanSession?.id || sessions[0]?.id;
|
||||
console.log(`Using session: ${sessionId}\n`);
|
||||
|
||||
// Send a command that outputs file paths
|
||||
@@ -81,8 +81,8 @@ async function main() {
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// Take screenshot
|
||||
await page.screenshot({ path: '/tmp/claudeman-link-test-1.png' });
|
||||
console.log('Screenshot saved: /tmp/claudeman-link-test-1.png\n');
|
||||
await page.screenshot({ path: '/tmp/codeman-link-test-1.png' });
|
||||
console.log('Screenshot saved: /tmp/codeman-link-test-1.png\n');
|
||||
|
||||
// Check if terminal contains our file path
|
||||
const terminalContent = await page.evaluate(() => {
|
||||
@@ -132,8 +132,8 @@ async function main() {
|
||||
console.log('Log viewer windows:', logViewerCount);
|
||||
|
||||
// Take final screenshot
|
||||
await page.screenshot({ path: '/tmp/claudeman-link-test-2.png' });
|
||||
console.log('\nFinal screenshot: /tmp/claudeman-link-test-2.png');
|
||||
await page.screenshot({ path: '/tmp/codeman-link-test-2.png' });
|
||||
console.log('\nFinal screenshot: /tmp/codeman-link-test-2.png');
|
||||
|
||||
// Test the link provider registration
|
||||
console.log('\n--- Link Provider Debug ---');
|
||||
@@ -155,8 +155,8 @@ async function main() {
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error:', error.message);
|
||||
await page.screenshot({ path: '/tmp/claudeman-link-test-error.png' });
|
||||
console.log('Error screenshot saved to /tmp/claudeman-link-test-error.png');
|
||||
await page.screenshot({ path: '/tmp/codeman-link-test-error.png' });
|
||||
console.log('Error screenshot saved to /tmp/codeman-link-test-error.png');
|
||||
} finally {
|
||||
await browser.close();
|
||||
console.log('\nBrowser closed');
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#!/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
|
||||
|
||||
LOGFILE="/tmp/claudeman-test-$(date +%s).log"
|
||||
LOGFILE="/tmp/codeman-test-$(date +%s).log"
|
||||
|
||||
echo "Created log file: $LOGFILE"
|
||||
echo ""
|
||||
|
||||
+29
-29
@@ -1,6 +1,6 @@
|
||||
#!/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)
|
||||
# ============================================================================
|
||||
#
|
||||
@@ -8,7 +8,7 @@
|
||||
# - Single-digit selection (1-9) for fast thumb typing
|
||||
# - Compact display, no wasted space
|
||||
# - Color-coded status for quick scanning
|
||||
# - Names pulled from Claudeman state.json
|
||||
# - Names pulled from Codeman state.json
|
||||
# - Minimal keystrokes to attach
|
||||
#
|
||||
# Usage:
|
||||
@@ -29,8 +29,8 @@ set -e
|
||||
# Configuration
|
||||
# ============================================================================
|
||||
|
||||
CLAUDEMAN_STATE="$HOME/.claudeman/state.json"
|
||||
CLAUDEMAN_SESSIONS="$HOME/.claudeman/mux-sessions.json"
|
||||
CODEMAN_STATE="$HOME/.codeman/state.json"
|
||||
CODEMAN_SESSIONS="$HOME/.codeman/mux-sessions.json"
|
||||
|
||||
|
||||
# iPhone 17 Pro portrait width (conservative)
|
||||
@@ -106,22 +106,22 @@ truncate() {
|
||||
find_full_session_id() {
|
||||
local short_id="$1"
|
||||
|
||||
if [ -f "$CLAUDEMAN_STATE" ]; then
|
||||
if [ -f "$CODEMAN_STATE" ]; then
|
||||
local full_id
|
||||
full_id=$(jq -r --arg short "$short_id" '
|
||||
.sessions | keys[] | select(startswith($short))
|
||||
' "$CLAUDEMAN_STATE" 2>/dev/null | head -1)
|
||||
' "$CODEMAN_STATE" 2>/dev/null | head -1)
|
||||
if [ -n "$full_id" ]; then
|
||||
echo "$full_id"
|
||||
return
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -f "$CLAUDEMAN_SESSIONS" ]; then
|
||||
if [ -f "$CODEMAN_SESSIONS" ]; then
|
||||
local full_id
|
||||
full_id=$(jq -r --arg short "$short_id" '
|
||||
.[] | select(.sessionId | startswith($short)) | .sessionId
|
||||
' "$CLAUDEMAN_SESSIONS" 2>/dev/null | head -1)
|
||||
' "$CODEMAN_SESSIONS" 2>/dev/null | head -1)
|
||||
if [ -n "$full_id" ]; then
|
||||
echo "$full_id"
|
||||
return
|
||||
@@ -136,22 +136,22 @@ get_session_name() {
|
||||
local name=""
|
||||
local workdir=""
|
||||
|
||||
if [ -f "$CLAUDEMAN_SESSIONS" ]; then
|
||||
if [ -f "$CODEMAN_SESSIONS" ]; then
|
||||
local result
|
||||
result=$(jq -r --arg id "$session_id" '
|
||||
.[] | 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
|
||||
name="${result%% *}"
|
||||
workdir="${result#* }"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "$name" ] && [ -f "$CLAUDEMAN_STATE" ]; then
|
||||
if [ -z "$name" ] && [ -f "$CODEMAN_STATE" ]; then
|
||||
local result
|
||||
result=$(jq -r --arg id "$session_id" '
|
||||
.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
|
||||
name="${result%% *}"
|
||||
[ -z "$workdir" ] && workdir="${result#* }"
|
||||
@@ -174,22 +174,22 @@ get_session_name() {
|
||||
get_working_dir() {
|
||||
local session_id="$1"
|
||||
|
||||
if [ -f "$CLAUDEMAN_SESSIONS" ]; then
|
||||
if [ -f "$CODEMAN_SESSIONS" ]; then
|
||||
local dir
|
||||
dir=$(jq -r --arg id "$session_id" '
|
||||
.[] | 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
|
||||
echo "${dir/#$HOME/~}"
|
||||
return
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -f "$CLAUDEMAN_STATE" ]; then
|
||||
if [ -f "$CODEMAN_STATE" ]; then
|
||||
local dir
|
||||
dir=$(jq -r --arg id "$session_id" '
|
||||
.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
|
||||
echo "${dir/#$HOME/~}"
|
||||
return
|
||||
@@ -201,12 +201,12 @@ get_working_dir() {
|
||||
get_tokens() {
|
||||
local session_id="$1"
|
||||
|
||||
if [ -f "$CLAUDEMAN_STATE" ]; then
|
||||
if [ -f "$CODEMAN_STATE" ]; then
|
||||
local tokens
|
||||
tokens=$(jq -r --arg id "$session_id" '
|
||||
.sessions | to_entries[] | select(.key | startswith($id)) |
|
||||
((.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 [ "$tokens" -gt 1000 ]; then
|
||||
@@ -223,11 +223,11 @@ get_tokens() {
|
||||
get_respawn_status() {
|
||||
local session_id="$1"
|
||||
|
||||
if [ -f "$CLAUDEMAN_SESSIONS" ]; then
|
||||
if [ -f "$CODEMAN_SESSIONS" ]; then
|
||||
local respawn_enabled
|
||||
respawn_enabled=$(jq -r --arg id "$session_id" '
|
||||
.[] | 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
|
||||
echo "R"
|
||||
@@ -273,8 +273,8 @@ parse_sessions() {
|
||||
while IFS= read -r line; do
|
||||
local session_name="${line%%:*}"
|
||||
|
||||
# Only show claudeman sessions
|
||||
if [[ "$session_name" != claudeman-* ]]; then
|
||||
# Only show codeman sessions
|
||||
if [[ "$session_name" != codeman-* ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
@@ -292,9 +292,9 @@ parse_sessions() {
|
||||
MUX_NAMES+=("$session_name")
|
||||
SESSION_STATES+=("$state")
|
||||
|
||||
# Extract session ID from claudeman session name
|
||||
# Extract session ID from codeman session name
|
||||
local session_id=""
|
||||
local cm_regex='^claudeman-(.+)$'
|
||||
local cm_regex='^codeman-(.+)$'
|
||||
if [[ "$session_name" =~ $cm_regex ]]; then
|
||||
session_id="${BASH_REMATCH[1]}"
|
||||
fi
|
||||
@@ -327,7 +327,7 @@ clear_screen() {
|
||||
|
||||
print_header() {
|
||||
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}"
|
||||
}
|
||||
|
||||
@@ -395,13 +395,13 @@ print_footer() {
|
||||
|
||||
print_no_sessions() {
|
||||
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 ""
|
||||
echo -e " ${YELLOW}No tmux sessions found${R}"
|
||||
echo ""
|
||||
echo -e " ${D}Start one with:${R}"
|
||||
echo -e " ${WHITE}claudeman web${R}"
|
||||
echo -e " ${WHITE}codeman web${R}"
|
||||
echo ""
|
||||
echo -e "${D}$(printf '%.0s─' {1..32})${R}"
|
||||
echo -e " ${GRAY}[${WHITE}r${GRAY}]efresh [${WHITE}q${GRAY}]uit${R}"
|
||||
@@ -584,7 +584,7 @@ quick_attach() {
|
||||
|
||||
show_help() {
|
||||
cat << 'EOF'
|
||||
Claudeman Sessions - Mobile-friendly Tmux Session Chooser
|
||||
Codeman Sessions - Mobile-friendly Tmux Session Chooser
|
||||
|
||||
USAGE:
|
||||
sc Interactive chooser
|
||||
@@ -607,7 +607,7 @@ INDICATORS:
|
||||
|
||||
TIPS:
|
||||
- Detach from tmux: Ctrl+B D
|
||||
- Session names from Claudeman state
|
||||
- Session names from Codeman state
|
||||
- Optimized for Termius/iPhone
|
||||
|
||||
EOF
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Claudeman Tmux Session Manager
|
||||
# Codeman Tmux Session Manager
|
||||
# Interactive tool with arrow key navigation
|
||||
# Reads from ~/.claudeman/mux-sessions.json
|
||||
# Reads from ~/.codeman/mux-sessions.json
|
||||
#
|
||||
|
||||
# Colors
|
||||
@@ -17,8 +17,8 @@ DIM='\033[2m'
|
||||
NC='\033[0m'
|
||||
REVERSE='\033[7m'
|
||||
|
||||
# Use the same path as claudeman (src/tmux-manager.ts)
|
||||
SESSIONS_FILE="${HOME}/.claudeman/mux-sessions.json"
|
||||
# Use the same path as codeman (src/tmux-manager.ts)
|
||||
SESSIONS_FILE="${HOME}/.codeman/mux-sessions.json"
|
||||
|
||||
|
||||
# Cached data
|
||||
@@ -133,7 +133,7 @@ kill_session() {
|
||||
# Draw header (only once)
|
||||
draw_header() {
|
||||
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}╚══════════════════════════════════════════════════════════╝${NC}"
|
||||
echo ""
|
||||
@@ -401,11 +401,11 @@ quick_list() {
|
||||
local now=$(date +%s)
|
||||
|
||||
if [[ $CACHED_COUNT -eq 0 ]]; then
|
||||
echo "No claudeman sessions found."
|
||||
echo "No codeman sessions found."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Claudeman Sessions ($SESSIONS_FILE):"
|
||||
echo "Codeman Sessions ($SESSIONS_FILE):"
|
||||
echo ""
|
||||
printf "%-4s %-22s %-14s %-10s %-8s\n" "#" "NAME" "RUNNING" "STATUS" "MODE"
|
||||
echo "─────────────────────────────────────────────────────────────────"
|
||||
@@ -432,7 +432,7 @@ quick_list() {
|
||||
# Usage
|
||||
usage() {
|
||||
cat << EOF
|
||||
Claudeman Tmux Session Manager
|
||||
Codeman Tmux Session Manager
|
||||
|
||||
Reads from: $SESSIONS_FILE
|
||||
|
||||
|
||||
@@ -11,9 +11,9 @@
|
||||
* - Event emission for state changes
|
||||
*
|
||||
* 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__')
|
||||
* - `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]')
|
||||
* - `buildPrompt()`: Build the prompt from terminal buffer
|
||||
* - `parseVerdict()`: Parse the verdict from AI output
|
||||
@@ -143,13 +143,13 @@ export abstract class AiCheckerBase<
|
||||
|
||||
// ========== 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;
|
||||
|
||||
/** Marker written to temp file when check is complete */
|
||||
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;
|
||||
|
||||
/** Prefix for log messages (e.g., '[AiIdleChecker]') */
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
* ## How It Works
|
||||
*
|
||||
* 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
|
||||
* 4. Parse the file content for IDLE/WORKING on the first word
|
||||
* 5. Kill tmux session and delete temp file
|
||||
@@ -129,9 +129,9 @@ export class AiIdleChecker extends AiCheckerBase<
|
||||
AiCheckResult,
|
||||
AiCheckState
|
||||
> {
|
||||
protected readonly muxNamePrefix = 'claudeman-aicheck-';
|
||||
protected readonly muxNamePrefix = 'codeman-aicheck-';
|
||||
protected readonly doneMarker = '__AICHECK_DONE__';
|
||||
protected readonly tempFilePrefix = 'claudeman-aicheck';
|
||||
protected readonly tempFilePrefix = 'codeman-aicheck';
|
||||
protected readonly logPrefix = '[AiIdleChecker]';
|
||||
protected readonly checkDescription = 'AI idle check';
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
* ## How It Works
|
||||
*
|
||||
* 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
|
||||
* 4. Parse the file content for PLAN_MODE/NOT_PLAN_MODE on the first line
|
||||
* 5. Kill tmux session and delete temp file
|
||||
@@ -91,9 +91,9 @@ export class AiPlanChecker extends AiCheckerBase<
|
||||
AiPlanCheckResult,
|
||||
AiPlanCheckState
|
||||
> {
|
||||
protected readonly muxNamePrefix = 'claudeman-plancheck-';
|
||||
protected readonly muxNamePrefix = 'codeman-plancheck-';
|
||||
protected readonly doneMarker = '__PLANCHECK_DONE__';
|
||||
protected readonly tempFilePrefix = 'claudeman-plancheck';
|
||||
protected readonly tempFilePrefix = 'codeman-plancheck';
|
||||
protected readonly logPrefix = '[AiPlanChecker]';
|
||||
protected readonly checkDescription = 'AI plan check';
|
||||
|
||||
|
||||
+4
-4
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @fileoverview Claudeman CLI command definitions
|
||||
* @fileoverview Codeman CLI command definitions
|
||||
*
|
||||
* Defines all CLI commands and subcommands for managing Claude sessions,
|
||||
* tasks, Ralph loops, and the web server.
|
||||
@@ -22,7 +22,7 @@ const pkg = require('../package.json') as { version: string };
|
||||
const program = new Command();
|
||||
|
||||
program
|
||||
.name('claudeman')
|
||||
.name('codeman')
|
||||
.description('Claude Code session manager with autonomous Ralph Loop')
|
||||
.version(pkg.version);
|
||||
|
||||
@@ -407,7 +407,7 @@ program
|
||||
? sessions.filter((s) => s.isBusy()).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(chalk.bold('\nSessions:'));
|
||||
@@ -504,7 +504,7 @@ program
|
||||
const https = !!options.https;
|
||||
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 {
|
||||
const server = await startWebServer(port, https);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* @fileoverview Centralized buffer size limits for memory management.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Memory Budget Rationale (for 20 concurrent sessions):
|
||||
|
||||
+6
-6
@@ -2,9 +2,9 @@
|
||||
* @fileoverview Claude Code hooks configuration generator
|
||||
*
|
||||
* Generates .claude/settings.local.json with hook definitions that POST
|
||||
* to Claudeman's /api/hook-event endpoint when Claude Code fires
|
||||
* notification or stop hooks. Uses $CLAUDEMAN_API_URL and
|
||||
* $CLAUDEMAN_SESSION_ID env vars (set on every managed session) so the
|
||||
* to Codeman's /api/hook-event endpoint when Claude Code fires
|
||||
* notification or stop hooks. Uses $CODEMAN_API_URL and
|
||||
* $CODEMAN_SESSION_ID env vars (set on every managed session) so the
|
||||
* 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
|
||||
*
|
||||
* 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
|
||||
* 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.
|
||||
const curlCmd = (event: HookEventType) =>
|
||||
`HOOK_DATA=$(cat 2>/dev/null || echo '{}'); ` +
|
||||
`curl -s -X POST "$CLAUDEMAN_API_URL/api/hook-event" ` +
|
||||
`curl -s -X POST "$CODEMAN_API_URL/api/hook-event" ` +
|
||||
`-H 'Content-Type: application/json' ` +
|
||||
`-d "{\\"event\\":\\"${event}\\",\\"sessionId\\":\\"$CLAUDEMAN_SESSION_ID\\",\\"data\\":$HOOK_DATA}" ` +
|
||||
`-d "{\\"event\\":\\"${event}\\",\\"sessionId\\":\\"$CODEMAN_SESSION_ID\\",\\"data\\":$HOOK_DATA}" ` +
|
||||
`2>/dev/null || true`;
|
||||
|
||||
return {
|
||||
|
||||
@@ -137,7 +137,7 @@ export class ImageWatcher extends EventEmitter {
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
watchSession(sessionId: string, workingDir: string): void {
|
||||
@@ -200,7 +200,7 @@ export class ImageWatcher extends EventEmitter {
|
||||
/**
|
||||
* 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 {
|
||||
const watcher = this.sessionWatchers.get(sessionId);
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
#!/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.
|
||||
*
|
||||
* @module index
|
||||
|
||||
@@ -13,9 +13,9 @@ import type { ProcessStats, PersistedRespawnConfig, NiceConfig, ClaudeMode, Sess
|
||||
* Multiplexer session metadata.
|
||||
*/
|
||||
export interface MuxSession {
|
||||
/** Claudeman session ID */
|
||||
/** Codeman session ID */
|
||||
sessionId: string;
|
||||
/** Multiplexer session name (e.g., "claudeman-abc12345") */
|
||||
/** Multiplexer session name (e.g., "codeman-abc12345") */
|
||||
muxName: string;
|
||||
/** Process PID */
|
||||
pid: number;
|
||||
@@ -105,7 +105,7 @@ export interface TerminalMultiplexer extends EventEmitter {
|
||||
/** Get all tracked sessions */
|
||||
getSessions(): MuxSession[];
|
||||
|
||||
/** Get a session by Claudeman session ID */
|
||||
/** Get a session by Codeman session ID */
|
||||
getSession(sessionId: string): MuxSession | undefined;
|
||||
|
||||
/** Get all sessions with process resource statistics */
|
||||
|
||||
+1
-1
@@ -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
|
||||
* with a consolidated "what happened while I was away" view.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* @fileoverview Append-only JSONL audit log for session lifecycle events.
|
||||
*
|
||||
* 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).
|
||||
*
|
||||
* @module session-lifecycle-log
|
||||
@@ -22,7 +22,7 @@ export class SessionLifecycleLog {
|
||||
private writeQueue: Promise<void> = Promise.resolve();
|
||||
|
||||
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);
|
||||
if (!existsSync(dir)) {
|
||||
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
||||
|
||||
+15
-15
@@ -400,8 +400,8 @@ export class Session extends EventEmitter {
|
||||
this.mode = config.mode || 'claude';
|
||||
this._name = config.name || '';
|
||||
this._lastActivityAt = this.createdAt;
|
||||
// Set claudeSessionId immediately — Claudeman always passes --session-id ${this.id}
|
||||
// to Claude CLI, so the Claude session ID always matches the Claudeman session ID.
|
||||
// Set claudeSessionId immediately — Codeman always passes --session-id ${this.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
|
||||
// startInteractive() hasn't been called yet).
|
||||
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.');
|
||||
}
|
||||
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
|
||||
const args = [...this._buildPermissionArgs(), '--session-id', this.id];
|
||||
if (this._model) args.push('--model', this._model);
|
||||
@@ -1058,10 +1058,10 @@ export class Session extends EventEmitter {
|
||||
TERM: 'xterm-256color',
|
||||
COLORTERM: undefined,
|
||||
CLAUDECODE: undefined,
|
||||
// Inform Claude it's running within Claudeman (helps prevent self-termination)
|
||||
CLAUDEMAN_MUX: '1',
|
||||
CLAUDEMAN_SESSION_ID: this.id,
|
||||
CLAUDEMAN_API_URL: process.env.CLAUDEMAN_API_URL || 'http://localhost:3000',
|
||||
// Inform Claude it's running within Codeman (helps prevent self-termination)
|
||||
CODEMAN_MUX: '1',
|
||||
CODEMAN_SESSION_ID: this.id,
|
||||
CODEMAN_API_URL: process.env.CODEMAN_API_URL || 'http://localhost:3000',
|
||||
},
|
||||
});
|
||||
} catch (spawnErr) {
|
||||
@@ -1376,9 +1376,9 @@ export class Session extends EventEmitter {
|
||||
LANG: 'en_US.UTF-8',
|
||||
LC_ALL: 'en_US.UTF-8',
|
||||
TERM: 'xterm-256color',
|
||||
CLAUDEMAN_MUX: '1',
|
||||
CLAUDEMAN_SESSION_ID: this.id,
|
||||
CLAUDEMAN_API_URL: process.env.CLAUDEMAN_API_URL || 'http://localhost:3000',
|
||||
CODEMAN_MUX: '1',
|
||||
CODEMAN_SESSION_ID: this.id,
|
||||
CODEMAN_API_URL: process.env.CODEMAN_API_URL || 'http://localhost:3000',
|
||||
},
|
||||
});
|
||||
} catch (spawnErr) {
|
||||
@@ -1509,10 +1509,10 @@ export class Session extends EventEmitter {
|
||||
TERM: 'xterm-256color',
|
||||
COLORTERM: undefined,
|
||||
CLAUDECODE: undefined,
|
||||
// Inform Claude it's running within Claudeman
|
||||
CLAUDEMAN_MUX: '1',
|
||||
CLAUDEMAN_SESSION_ID: this.id,
|
||||
CLAUDEMAN_API_URL: process.env.CLAUDEMAN_API_URL || 'http://localhost:3000',
|
||||
// Inform Claude it's running within Codeman
|
||||
CODEMAN_MUX: '1',
|
||||
CODEMAN_SESSION_ID: this.id,
|
||||
CODEMAN_API_URL: process.env.CODEMAN_API_URL || 'http://localhost:3000',
|
||||
},
|
||||
});
|
||||
} catch (spawnErr) {
|
||||
@@ -1867,7 +1867,7 @@ export class Session extends EventEmitter {
|
||||
}
|
||||
|
||||
// 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
|
||||
private parseClaudeCodeInfo(cleanData: string): void {
|
||||
// Only parse once per session (during startup)
|
||||
|
||||
+19
-3
@@ -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
|
||||
* 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.json`: Main app state (sessions, tasks, config)
|
||||
@@ -68,7 +68,23 @@ export class StateStore {
|
||||
private _saveInFlight: Promise<void> | null = null;
|
||||
|
||||
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.state = this.load();
|
||||
this.state.config.stateFilePath = this.filePath;
|
||||
|
||||
+10
-10
@@ -310,7 +310,7 @@ export class SubagentWatcher extends EventEmitter {
|
||||
/**
|
||||
* Run pgrep once and read /proc info for all Claude PIDs in parallel.
|
||||
* 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.
|
||||
*/
|
||||
private async getClaudePids(): Promise<Map<number, { environ: string; cmdline: string }>> {
|
||||
@@ -330,8 +330,8 @@ export class SubagentWatcher extends EventEmitter {
|
||||
let cmdline = '';
|
||||
try { environ = await readFile(`/proc/${pid}/environ`, 'utf8'); } catch { /* skip */ }
|
||||
try { cmdline = await readFile(`/proc/${pid}/cmdline`, 'utf8'); } catch { /* skip */ }
|
||||
// Skip main Claudeman-managed Claude processes — only track subagents
|
||||
if (environ.includes('CLAUDEMAN_MUX=1')) return;
|
||||
// Skip main Codeman-managed Claude processes — only track subagents
|
||||
if (environ.includes('CODEMAN_MUX=1')) return;
|
||||
if (environ || cmdline) {
|
||||
result.set(pid, { environ, cmdline });
|
||||
}
|
||||
@@ -562,8 +562,8 @@ export class SubagentWatcher extends EventEmitter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get subagents for a specific Claudeman session
|
||||
* Maps Claudeman working directory to Claude's project hash
|
||||
* Get subagents for a specific Codeman session
|
||||
* Maps Codeman working directory to Claude's project hash
|
||||
*/
|
||||
getSubagentsForSession(workingDir: string): SubagentInfo[] {
|
||||
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.
|
||||
* All sessions in the same workingDir share a projectHash, so filtering
|
||||
* 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.
|
||||
* 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.
|
||||
*/
|
||||
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
|
||||
}
|
||||
|
||||
// Defense-in-depth: never kill a main Claudeman-managed Claude process.
|
||||
// Main processes have CLAUDEMAN_MUX=1 in their environment; subagents don't.
|
||||
if (environ.includes('CLAUDEMAN_MUX=1')) continue;
|
||||
// Defense-in-depth: never kill a main Codeman-managed Claude process.
|
||||
// Main processes have CODEMAN_MUX=1 in their environment; subagents don't.
|
||||
if (environ.includes('CODEMAN_MUX=1')) continue;
|
||||
|
||||
if (environ.includes(sessionId)) {
|
||||
// Cache PID on the matching agent
|
||||
|
||||
+2
-2
@@ -2,7 +2,7 @@
|
||||
* @fileoverview Agent Teams Watcher
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* @module team-watcher
|
||||
@@ -70,7 +70,7 @@ export class TeamWatcher extends EventEmitter {
|
||||
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 {
|
||||
const teamName = this.sessionToTeam.get(sessionId);
|
||||
if (teamName) {
|
||||
|
||||
@@ -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
|
||||
- The session persists across disconnects - your work is safe
|
||||
- Token usage, costs, and background tasks are tracked externally
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* @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.
|
||||
* Supports custom templates via settings.json override.
|
||||
*
|
||||
|
||||
+23
-20
@@ -10,7 +10,7 @@
|
||||
* - **Reliable input**: `send-keys -l` sends literal text in a single command
|
||||
* - **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:
|
||||
* - `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
|
||||
* - Send input to a tmux session
|
||||
* - Discover/reconcile real tmux sessions
|
||||
* - Read/write ~/.claudeman/mux-sessions.json
|
||||
* - Read/write ~/.codeman/mux-sessions.json
|
||||
*/
|
||||
const IS_TEST_MODE = !!process.env.VITEST;
|
||||
|
||||
/** 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) */
|
||||
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") */
|
||||
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.
|
||||
*/
|
||||
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> {
|
||||
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)) {
|
||||
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 LC_ALL=en_US.UTF-8',
|
||||
'unset COLORTERM',
|
||||
'export CLAUDEMAN_MUX=1',
|
||||
`export CLAUDEMAN_SESSION_ID=${sessionId}`,
|
||||
`export CLAUDEMAN_MUX_NAME=${muxName}`,
|
||||
`export CLAUDEMAN_API_URL=${process.env.CLAUDEMAN_API_URL || 'http://localhost:3000'}`,
|
||||
'export CODEMAN_MUX=1',
|
||||
`export CODEMAN_SESSION_ID=${sessionId}`,
|
||||
`export CODEMAN_MUX_NAME=${muxName}`,
|
||||
`export CODEMAN_API_URL=${process.env.CODEMAN_API_URL || 'http://localhost:3000'}`,
|
||||
];
|
||||
// Only unset CLAUDECODE for Claude sessions
|
||||
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.
|
||||
// It gets enabled dynamically when panes are split (agent teams).
|
||||
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 })
|
||||
.then(() => {}).catch(() => { /* Non-critical — session still works with status bar */ }),
|
||||
// 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 LC_ALL=en_US.UTF-8',
|
||||
'unset COLORTERM',
|
||||
'export CLAUDEMAN_MUX=1',
|
||||
`export CLAUDEMAN_SESSION_ID=${sessionId}`,
|
||||
`export CLAUDEMAN_MUX_NAME=${muxName}`,
|
||||
`export CLAUDEMAN_API_URL=${process.env.CLAUDEMAN_API_URL || 'http://localhost:3000'}`,
|
||||
'export CODEMAN_MUX=1',
|
||||
`export CODEMAN_SESSION_ID=${sessionId}`,
|
||||
`export CODEMAN_MUX_NAME=${muxName}`,
|
||||
`export CODEMAN_API_URL=${process.env.CODEMAN_API_URL || 'http://localhost:3000'}`,
|
||||
];
|
||||
if (mode === 'claude') envExports.splice(2, 0, 'unset CLAUDECODE');
|
||||
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
|
||||
const currentMuxName = process.env.CLAUDEMAN_MUX_NAME;
|
||||
const currentMuxName = process.env.CODEMAN_MUX_NAME;
|
||||
if (currentMuxName && session.muxName === currentMuxName) {
|
||||
console.error(`[TmuxManager] BLOCKED: Refusing to kill own tmux session: ${session.muxName}`);
|
||||
return false;
|
||||
@@ -824,7 +827,7 @@ export class TmuxManager extends EventEmitter implements TerminalMultiplexer {
|
||||
}
|
||||
}
|
||||
|
||||
// Discover unknown claudeman sessions
|
||||
// Discover unknown codeman sessions
|
||||
try {
|
||||
const output = execSync(
|
||||
"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')) {
|
||||
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
|
||||
let isKnown = false;
|
||||
@@ -846,7 +849,7 @@ export class TmuxManager extends EventEmitter implements TerminalMultiplexer {
|
||||
|
||||
if (!isKnown) {
|
||||
// Extract session ID fragment from name
|
||||
const fragment = sessionName.replace('claudeman-', '');
|
||||
const fragment = sessionName.replace(/^(?:codeman|claudeman)-/, '');
|
||||
const sessionId = `restored-${fragment}`;
|
||||
const pid = this.getPanePid(sessionName);
|
||||
|
||||
@@ -1310,7 +1313,7 @@ export class TmuxManager extends EventEmitter implements TerminalMultiplexer {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Build target: sessionName.paneId (e.g., "claudeman-abc12345.%1")
|
||||
// Build target: sessionName.paneId (e.g., "codeman-abc12345.%1")
|
||||
const target = paneTarget.startsWith('%')
|
||||
? `${muxName}.${paneTarget}`
|
||||
: `${muxName}.%${paneTarget}`;
|
||||
|
||||
+4
-4
@@ -1,8 +1,8 @@
|
||||
/**
|
||||
* @fileoverview Type definitions for Claudeman
|
||||
* @fileoverview Type definitions for Codeman
|
||||
*
|
||||
* 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
|
||||
* - Task queue operations
|
||||
* - Ralph Loop configuration
|
||||
@@ -859,7 +859,7 @@ export const DEFAULT_CONFIG: AppConfig = {
|
||||
* Types for tracking Ralph Wiggum loops and todo lists
|
||||
* 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.
|
||||
*/
|
||||
|
||||
@@ -1369,7 +1369,7 @@ export interface ActiveBashTool {
|
||||
* Used to trigger automatic image popup display in the web UI.
|
||||
*/
|
||||
export interface ImageDetectedEvent {
|
||||
/** Claudeman session ID where the image was detected */
|
||||
/** Codeman session ID where the image was detected */
|
||||
sessionId: string;
|
||||
/** Full path to the detected image file */
|
||||
filePath: string;
|
||||
|
||||
+57
-44
@@ -1,4 +1,4 @@
|
||||
// Claudeman App - Tab-based Terminal UI
|
||||
// Codeman App - Tab-based Terminal UI
|
||||
|
||||
// ============================================================================
|
||||
// Constants
|
||||
@@ -852,14 +852,14 @@ const VoiceInput = {
|
||||
|
||||
_getDeepgramConfig() {
|
||||
try {
|
||||
return JSON.parse(localStorage.getItem('claudeman-voice-settings') || '{}');
|
||||
return JSON.parse(localStorage.getItem('codeman-voice-settings') || '{}');
|
||||
} catch (_e) {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
|
||||
_saveDeepgramConfig(config) {
|
||||
localStorage.setItem('claudeman-voice-settings', JSON.stringify(config));
|
||||
localStorage.setItem('codeman-voice-settings', JSON.stringify(config));
|
||||
},
|
||||
|
||||
_shouldUseDeepgram() {
|
||||
@@ -2294,7 +2294,7 @@ class NotificationManager {
|
||||
// Get storage key for notification prefs (device-specific)
|
||||
getStorageKey() {
|
||||
const isMobile = MobileDetection.getDeviceType() === 'mobile';
|
||||
return isMobile ? 'claudeman-notification-prefs-mobile' : 'claudeman-notification-prefs';
|
||||
return isMobile ? 'codeman-notification-prefs-mobile' : 'codeman-notification-prefs';
|
||||
}
|
||||
|
||||
savePreferences() {
|
||||
@@ -2449,11 +2449,11 @@ class NotificationManager {
|
||||
this.titleFlashInterval = setInterval(() => {
|
||||
this.titleFlashState = !this.titleFlashState;
|
||||
document.title = this.titleFlashState
|
||||
? `\u26A0\uFE0F (${this.unreadCount}) Claudeman`
|
||||
? `\u26A0\uFE0F (${this.unreadCount}) Codeman`
|
||||
: this.originalTitle;
|
||||
}, TITLE_FLASH_INTERVAL_MS);
|
||||
// Set immediately
|
||||
document.title = `\u26A0\uFE0F (${this.unreadCount}) Claudeman`;
|
||||
document.title = `\u26A0\uFE0F (${this.unreadCount}) Codeman`;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2488,7 +2488,7 @@ class NotificationManager {
|
||||
if (now - this.lastBrowserNotifTime < 3000) return;
|
||||
this.lastBrowserNotifTime = now;
|
||||
|
||||
const notif = new Notification(`Claudeman: ${title}`, {
|
||||
const notif = new Notification(`Codeman: ${title}`, {
|
||||
body,
|
||||
tag, // Groups same-tag notifications
|
||||
icon: '/favicon.ico',
|
||||
@@ -2634,7 +2634,7 @@ class NotificationManager {
|
||||
}
|
||||
}
|
||||
|
||||
class ClaudemanApp {
|
||||
class CodemanApp {
|
||||
constructor() {
|
||||
this.sessions = new Map();
|
||||
this.sessionOrder = []; // Track tab order for drag-and-drop reordering
|
||||
@@ -2919,7 +2919,7 @@ class ClaudemanApp {
|
||||
|
||||
initTerminal() {
|
||||
// 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({
|
||||
theme: {
|
||||
@@ -4023,7 +4023,7 @@ class ClaudemanApp {
|
||||
this._cleanupSessionData(data.id);
|
||||
if (this.activeSessionId === data.id) {
|
||||
this.activeSessionId = null;
|
||||
try { localStorage.removeItem('claudeman-active-session'); } catch {}
|
||||
try { localStorage.removeItem('codeman-active-session'); } catch {}
|
||||
this.terminal.clear();
|
||||
this.showWelcome();
|
||||
}
|
||||
@@ -4709,11 +4709,11 @@ class ClaudemanApp {
|
||||
}
|
||||
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);
|
||||
|
||||
// Auto-open window for new active agents — but ONLY if they belong to a Claudeman session tab.
|
||||
// Agents from external Claude sessions (not managed by Claudeman) should not pop up.
|
||||
// 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 Codeman) should not pop up.
|
||||
if (data.status === 'active') {
|
||||
const agentForCheck = this.subagents.get(data.agentId);
|
||||
const hasMatchingTab = agentForCheck?.sessionId &&
|
||||
@@ -5106,11 +5106,11 @@ class ClaudemanApp {
|
||||
const headerVersionEl = this.$('headerVersion');
|
||||
if (versionEl) {
|
||||
versionEl.textContent = `v${data.version}`;
|
||||
versionEl.title = `Claudeman v${data.version}`;
|
||||
versionEl.title = `Codeman v${data.version}`;
|
||||
}
|
||||
if (headerVersionEl) {
|
||||
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
|
||||
let restoreId = previousActiveId;
|
||||
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)) {
|
||||
this.selectSession(restoreId);
|
||||
@@ -5605,7 +5605,7 @@ class ClaudemanApp {
|
||||
// Load session order from localStorage
|
||||
loadSessionOrder() {
|
||||
try {
|
||||
const saved = localStorage.getItem('claudeman-session-order');
|
||||
const saved = localStorage.getItem('codeman-session-order');
|
||||
return saved ? JSON.parse(saved) : [];
|
||||
} catch {
|
||||
return [];
|
||||
@@ -5615,7 +5615,7 @@ class ClaudemanApp {
|
||||
// Save session order to localStorage
|
||||
saveSessionOrder() {
|
||||
try {
|
||||
localStorage.setItem('claudeman-session-order', JSON.stringify(this.sessionOrder));
|
||||
localStorage.setItem('codeman-session-order', JSON.stringify(this.sessionOrder));
|
||||
} catch {
|
||||
// Ignore storage errors
|
||||
}
|
||||
@@ -5924,7 +5924,7 @@ class ClaudemanApp {
|
||||
this._localEchoOverlay.suppressBufferDetection();
|
||||
}
|
||||
this.activeSessionId = sessionId;
|
||||
try { localStorage.setItem('claudeman-active-session', sessionId); } catch {}
|
||||
try { localStorage.setItem('codeman-active-session', sessionId); } catch {}
|
||||
this.hideWelcome();
|
||||
// Clear idle hooks on view, but keep action hooks until user interacts
|
||||
this.clearPendingHooks(sessionId, 'idle_prompt');
|
||||
@@ -6160,7 +6160,7 @@ class ClaudemanApp {
|
||||
|
||||
if (this.activeSessionId === sessionId) {
|
||||
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)
|
||||
if (this.sessionOrder.length > 0 && this.sessions.size > 0) {
|
||||
const nextSessionId = this.sessionOrder[0];
|
||||
@@ -6242,7 +6242,7 @@ class ClaudemanApp {
|
||||
goHome() {
|
||||
// Deselect active session and show welcome screen
|
||||
this.activeSessionId = null;
|
||||
try { localStorage.removeItem('claudeman-active-session'); } catch {}
|
||||
try { localStorage.removeItem('codeman-active-session'); } catch {}
|
||||
this.terminal.clear();
|
||||
this.showWelcome();
|
||||
this.renderSessionTabs();
|
||||
@@ -9250,7 +9250,7 @@ class ClaudemanApp {
|
||||
} else {
|
||||
// No cases exist yet - show the default case name as directory
|
||||
select.value = 'testcase';
|
||||
document.getElementById('dirDisplay').textContent = '~/claudeman-cases/testcase';
|
||||
document.getElementById('dirDisplay').textContent = '~/codeman-cases/testcase';
|
||||
this.updateMobileCaseLabel('testcase');
|
||||
}
|
||||
|
||||
@@ -9317,7 +9317,7 @@ class ClaudemanApp {
|
||||
|
||||
setRunMode(mode) {
|
||||
this._runMode = mode;
|
||||
try { localStorage.setItem('claudeman_runMode', mode); } catch {}
|
||||
try { localStorage.setItem('codeman_runMode', mode); } catch {}
|
||||
this._applyRunMode();
|
||||
// Close menu
|
||||
document.getElementById('runModeMenu')?.classList.remove('active');
|
||||
@@ -9361,7 +9361,7 @@ class ClaudemanApp {
|
||||
}
|
||||
|
||||
_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();
|
||||
}
|
||||
|
||||
@@ -10132,7 +10132,7 @@ class ClaudemanApp {
|
||||
this.terminalBuffers.clear();
|
||||
this.terminalBufferCache.clear();
|
||||
this.activeSessionId = null;
|
||||
try { localStorage.removeItem('claudeman-active-session'); } catch {}
|
||||
try { localStorage.removeItem('codeman-active-session'); } catch {}
|
||||
this.respawnStatus = {};
|
||||
this.respawnCountdownTimers = {};
|
||||
this.respawnActionLogs = {};
|
||||
@@ -10239,13 +10239,13 @@ class ClaudemanApp {
|
||||
this.terminal.options.fontSize = size;
|
||||
document.getElementById('fontSizeDisplay').textContent = size;
|
||||
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
|
||||
this._localEchoOverlay?.refreshFont();
|
||||
}
|
||||
|
||||
loadFontSize() {
|
||||
const saved = localStorage.getItem('claudeman-font-size');
|
||||
const saved = localStorage.getItem('codeman-font-size');
|
||||
if (saved) {
|
||||
const size = parseInt(saved, 10);
|
||||
if (size >= 10 && size <= 24) {
|
||||
@@ -10676,7 +10676,7 @@ class ClaudemanApp {
|
||||
// ========== Respawn Presets ==========
|
||||
|
||||
loadRespawnPresets() {
|
||||
const saved = localStorage.getItem('claudeman-respawn-presets');
|
||||
const saved = localStorage.getItem('codeman-respawn-presets');
|
||||
const custom = saved ? JSON.parse(saved) : [];
|
||||
return [...BUILTIN_RESPAWN_PRESETS, ...custom];
|
||||
}
|
||||
@@ -10684,7 +10684,7 @@ class ClaudemanApp {
|
||||
saveRespawnPresets(presets) {
|
||||
// Only save custom presets (not built-in)
|
||||
const custom = presets.filter(p => !p.builtIn);
|
||||
localStorage.setItem('claudeman-respawn-presets', JSON.stringify(custom));
|
||||
localStorage.setItem('codeman-respawn-presets', JSON.stringify(custom));
|
||||
}
|
||||
|
||||
renderPresetDropdown() {
|
||||
@@ -11842,7 +11842,7 @@ class ClaudemanApp {
|
||||
// Get the settings storage key based on device type (mobile vs desktop)
|
||||
getSettingsStorageKey() {
|
||||
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
|
||||
@@ -12138,7 +12138,7 @@ class ClaudemanApp {
|
||||
|
||||
// Sync voice settings from server (seed localStorage if no local API key)
|
||||
if (voiceSettings) {
|
||||
const localVoice = localStorage.getItem('claudeman-voice-settings');
|
||||
const localVoice = localStorage.getItem('codeman-voice-settings');
|
||||
if (!localVoice || !JSON.parse(localVoice).apiKey) {
|
||||
VoiceInput._saveDeepgramConfig(voiceSettings);
|
||||
}
|
||||
@@ -12179,7 +12179,7 @@ class ClaudemanApp {
|
||||
const windowStates = { minimized: minimizedState, open: openWindows };
|
||||
|
||||
// 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
|
||||
try {
|
||||
@@ -12206,7 +12206,7 @@ class ClaudemanApp {
|
||||
if (res.ok) {
|
||||
states = await res.json();
|
||||
// Also update localStorage
|
||||
localStorage.setItem('claudeman-subagent-window-states', JSON.stringify(states));
|
||||
localStorage.setItem('codeman-subagent-window-states', JSON.stringify(states));
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to load subagent window states from server:', err);
|
||||
@@ -12215,7 +12215,7 @@ class ClaudemanApp {
|
||||
// Fallback to localStorage
|
||||
if (!states) {
|
||||
try {
|
||||
const saved = localStorage.getItem('claudeman-subagent-window-states');
|
||||
const saved = localStorage.getItem('codeman-subagent-window-states');
|
||||
if (saved) {
|
||||
states = JSON.parse(saved);
|
||||
}
|
||||
@@ -12321,7 +12321,7 @@ class ClaudemanApp {
|
||||
|
||||
// Save to localStorage for instant recovery
|
||||
try {
|
||||
localStorage.setItem('claudeman-subagent-parents', JSON.stringify(mapData));
|
||||
localStorage.setItem('codeman-subagent-parents', JSON.stringify(mapData));
|
||||
} catch (err) {
|
||||
console.error('Failed to save subagent parents to localStorage:', err);
|
||||
}
|
||||
@@ -12351,7 +12351,7 @@ class ClaudemanApp {
|
||||
if (res.ok) {
|
||||
mapData = await res.json();
|
||||
// Update localStorage as cache
|
||||
localStorage.setItem('claudeman-subagent-parents', JSON.stringify(mapData));
|
||||
localStorage.setItem('codeman-subagent-parents', JSON.stringify(mapData));
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to load subagent parents from server:', err);
|
||||
@@ -12360,7 +12360,7 @@ class ClaudemanApp {
|
||||
// Fallback to localStorage
|
||||
if (!mapData) {
|
||||
try {
|
||||
const saved = localStorage.getItem('claudeman-subagent-parents');
|
||||
const saved = localStorage.getItem('codeman-subagent-parents');
|
||||
if (saved) {
|
||||
mapData = JSON.parse(saved);
|
||||
}
|
||||
@@ -14670,8 +14670,8 @@ class ClaudemanApp {
|
||||
const agent = this.subagents.get(agentId);
|
||||
if (!agent) return;
|
||||
|
||||
// Only open windows for agents that belong to a Claudeman-managed session tab.
|
||||
// Agents from external Claude sessions (not tracked by Claudeman) should not pop up.
|
||||
// Only open windows for agents that belong to a Codeman-managed session tab.
|
||||
// Agents from external Claude sessions (not tracked by Codeman) should not pop up.
|
||||
if (agent.sessionId) {
|
||||
const hasMatchingTab = Array.from(this.sessions.values()).some(s => s.claudeSessionId === agent.sessionId);
|
||||
if (!hasMatchingTab) return;
|
||||
@@ -15610,7 +15610,7 @@ class ClaudemanApp {
|
||||
|
||||
/** Open a standalone terminal window for a tmux-pane teammate (no subagent entry needed) */
|
||||
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;
|
||||
|
||||
// Use pane target as the unique ID for this window
|
||||
@@ -16831,7 +16831,7 @@ class ClaudemanApp {
|
||||
this.sessions.clear();
|
||||
this.muxSessions = [];
|
||||
this.activeSessionId = null;
|
||||
try { localStorage.removeItem('claudeman-active-session'); } catch {}
|
||||
try { localStorage.removeItem('codeman-active-session'); } catch {}
|
||||
this.renderSessionTabs();
|
||||
this.renderMuxSessions();
|
||||
this.terminal.clear();
|
||||
@@ -16842,7 +16842,7 @@ class ClaudemanApp {
|
||||
// Just remove tabs, keep mux sessions running
|
||||
this.sessions.clear();
|
||||
this.activeSessionId = null;
|
||||
try { localStorage.removeItem('claudeman-active-session'); } catch {}
|
||||
try { localStorage.removeItem('codeman-active-session'); } catch {}
|
||||
this.renderSessionTabs();
|
||||
this.terminal.clear();
|
||||
this.terminal.reset();
|
||||
@@ -17554,12 +17554,25 @@ class ClaudemanApp {
|
||||
|
||||
escapeHtml(text) {
|
||||
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
|
||||
const app = new ClaudemanApp();
|
||||
const app = new CodemanApp();
|
||||
|
||||
// Expose for debugging/testing
|
||||
window.app = app;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<meta name="description" content="Claude Code session manager with web interface">
|
||||
<meta name="theme-color" content="#0a0a0a">
|
||||
<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="stylesheet" href="styles.css?v=0.1631">
|
||||
<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 -->
|
||||
<div class="loading-skeleton">
|
||||
<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>
|
||||
<div class="skeleton-terminal"></div>
|
||||
@@ -49,7 +49,7 @@
|
||||
<!-- Compact Header with Session Tabs -->
|
||||
<header class="header">
|
||||
<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>
|
||||
|
||||
<!-- Session Tabs -->
|
||||
@@ -231,7 +231,7 @@
|
||||
<!-- Welcome Overlay (shown when no session active) -->
|
||||
<div class="welcome-overlay" id="welcomeOverlay">
|
||||
<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>
|
||||
<div class="welcome-actions">
|
||||
<button class="welcome-btn welcome-btn-claude" onclick="app.setRunMode('claude'); app.runClaude()">
|
||||
@@ -379,7 +379,7 @@
|
||||
</div>
|
||||
|
||||
<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 class="toolbar-right">
|
||||
@@ -1086,7 +1086,7 @@
|
||||
<span class="settings-status" id="notifPermissionStatus">?</span>
|
||||
</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 -->
|
||||
<div class="settings-section-header">Alerts</div>
|
||||
@@ -1222,7 +1222,7 @@
|
||||
|
||||
<div class="form-row" style="grid-column: 1 / -1;">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1253,7 +1253,7 @@
|
||||
<div class="form-row">
|
||||
<label>Case Name</label>
|
||||
<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 class="form-row">
|
||||
<label>Description (optional)</label>
|
||||
@@ -1265,7 +1265,7 @@
|
||||
<div class="form-row">
|
||||
<label>Case Name</label>
|
||||
<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 class="form-row">
|
||||
<label>Folder Path</label>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Claudeman - Terminal-First UI */
|
||||
/* Codeman - Terminal-First UI */
|
||||
|
||||
:root {
|
||||
--bg-dark: #0a0a0a;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<html lang="en"><head>
|
||||
<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">
|
||||
<title>Upload Screenshot - Claudeman</title>
|
||||
<title>Upload Screenshot - Codeman</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body {
|
||||
@@ -49,7 +49,7 @@
|
||||
</style>
|
||||
</head><body>
|
||||
<div class="container">
|
||||
<a class="back" href="/">← Back to Claudeman</a>
|
||||
<a class="back" href="/">← Back to Codeman</a>
|
||||
<h1>Upload Screenshot</h1>
|
||||
<div class="drop-zone" id="drop">
|
||||
<p>Tap to select image</p>
|
||||
|
||||
+1
-1
@@ -39,7 +39,7 @@ const ALLOWED_ENV_PREFIXES = ['CLAUDE_CODE_', 'OPENCODE_'];
|
||||
/** Env var keys that are always blocked (security-sensitive) */
|
||||
const BLOCKED_ENV_KEYS = new Set([
|
||||
'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
|
||||
]);
|
||||
|
||||
|
||||
+35
-35
@@ -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:
|
||||
* - 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)
|
||||
const MAX_SCREENSHOT_SIZE = 10 * 1024 * 1024;
|
||||
// Screenshots directory
|
||||
const SCREENSHOTS_DIR = join(homedir(), '.claudeman', 'screenshots');
|
||||
const SCREENSHOTS_DIR = join(homedir(), '.codeman', 'screenshots');
|
||||
// Stats collection interval (2 seconds)
|
||||
const STATS_COLLECTION_INTERVAL_MS = 2000;
|
||||
// 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.
|
||||
* 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 } {
|
||||
const certsDir = join(homedir(), '.claudeman', 'certs');
|
||||
const certsDir = join(homedir(), '.codeman', 'certs');
|
||||
const keyPath = join(certsDir, 'server.key');
|
||||
const certPath = join(certsDir, 'server.crt');
|
||||
|
||||
@@ -292,7 +292,7 @@ function getOrCreateSelfSignedCert(): { key: string; cert: string } {
|
||||
execSync(
|
||||
`openssl req -x509 -newkey rsa:2048 -nodes ` +
|
||||
`-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"`,
|
||||
{ stdio: 'pipe' }
|
||||
);
|
||||
@@ -582,10 +582,10 @@ export class WebServer extends EventEmitter {
|
||||
threshold: 1024,
|
||||
});
|
||||
|
||||
// Optional HTTP Basic Auth (set CLAUDEMAN_PASSWORD env var to enable)
|
||||
const authPassword = process.env.CLAUDEMAN_PASSWORD;
|
||||
// Optional HTTP Basic Auth (set CODEMAN_PASSWORD env var to enable)
|
||||
const authPassword = process.env.CODEMAN_PASSWORD;
|
||||
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');
|
||||
this.app.addHook('onRequest', (req, reply, done) => {
|
||||
const auth = req.headers.authorization;
|
||||
@@ -593,7 +593,7 @@ export class WebServer extends EventEmitter {
|
||||
done();
|
||||
return;
|
||||
}
|
||||
reply.header('WWW-Authenticate', 'Basic realm="Claudeman"');
|
||||
reply.header('WWW-Authenticate', 'Basic realm="Codeman"');
|
||||
reply.code(401).send('Unauthorized');
|
||||
});
|
||||
}
|
||||
@@ -2350,7 +2350,7 @@ export class WebServer extends EventEmitter {
|
||||
});
|
||||
|
||||
// Case management
|
||||
const casesDir = join(homedir(), 'claudeman-cases');
|
||||
const casesDir = join(homedir(), 'codeman-cases');
|
||||
|
||||
this.app.get('/api/cases', async (): Promise<CaseInfo[]> => {
|
||||
const cases: CaseInfo[] = [];
|
||||
@@ -2372,7 +2372,7 @@ export class WebServer extends EventEmitter {
|
||||
}
|
||||
|
||||
// Get linked cases
|
||||
const linkedCasesFile = join(homedir(), '.claudeman', 'linked-cases.json');
|
||||
const linkedCasesFile = join(homedir(), '.codeman', 'linked-cases.json');
|
||||
try {
|
||||
const linkedCases: Record<string, string> = JSON.parse(await fs.readFile(linkedCasesFile, 'utf-8'));
|
||||
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
|
||||
const casePath = join(casesDir, name);
|
||||
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
|
||||
const linkedCasesFile = join(homedir(), '.claudeman', 'linked-cases.json');
|
||||
const linkedCasesFile = join(homedir(), '.codeman', 'linked-cases.json');
|
||||
let linkedCases: Record<string, string> = {};
|
||||
try {
|
||||
linkedCases = JSON.parse(await fs.readFile(linkedCasesFile, 'utf-8'));
|
||||
@@ -2478,9 +2478,9 @@ export class WebServer extends EventEmitter {
|
||||
// Save the linked case
|
||||
linkedCases[name] = expandedPath;
|
||||
try {
|
||||
const claudemanDir = join(homedir(), '.claudeman');
|
||||
if (!existsSync(claudemanDir)) {
|
||||
mkdirSync(claudemanDir, { recursive: true });
|
||||
const codemanDir = join(homedir(), '.codeman');
|
||||
if (!existsSync(codemanDir)) {
|
||||
mkdirSync(codemanDir, { recursive: true });
|
||||
}
|
||||
await fs.writeFile(linkedCasesFile, JSON.stringify(linkedCases, null, 2));
|
||||
this.broadcast('case:linked', { name, path: expandedPath });
|
||||
@@ -2494,7 +2494,7 @@ export class WebServer extends EventEmitter {
|
||||
const { name } = req.params as { name: string };
|
||||
|
||||
// First check linked cases
|
||||
const linkedCasesFile = join(homedir(), '.claudeman', 'linked-cases.json');
|
||||
const linkedCasesFile = join(homedir(), '.codeman', 'linked-cases.json');
|
||||
try {
|
||||
const linkedCases: Record<string, string> = JSON.parse(await fs.readFile(linkedCasesFile, 'utf-8'));
|
||||
if (linkedCases[name]) {
|
||||
@@ -2531,7 +2531,7 @@ export class WebServer extends EventEmitter {
|
||||
// Get case path (check linked cases first, then casesDir)
|
||||
let casePath: string | null = null;
|
||||
|
||||
const linkedCasesFile = join(homedir(), '.claudeman', 'linked-cases.json');
|
||||
const linkedCasesFile = join(homedir(), '.codeman', 'linked-cases.json');
|
||||
try {
|
||||
const linkedCases: Record<string, string> = JSON.parse(await fs.readFile(linkedCasesFile, 'utf-8'));
|
||||
if (linkedCases[name]) {
|
||||
@@ -2737,7 +2737,7 @@ export class WebServer extends EventEmitter {
|
||||
|
||||
// Save lastUsedCase to settings for TUI/web sync
|
||||
try {
|
||||
const settingsFilePath = join(homedir(), '.claudeman', 'settings.json');
|
||||
const settingsFilePath = join(homedir(), '.codeman', 'settings.json');
|
||||
let settings: Record<string, unknown> = {};
|
||||
try {
|
||||
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
|
||||
let outputDir: string | undefined;
|
||||
if (caseName) {
|
||||
const casesDir = join(homedir(), 'claudeman-cases');
|
||||
const casesDir = join(homedir(), 'codeman-cases');
|
||||
const casePath = join(casesDir, caseName);
|
||||
// Security: Path traversal protection - use relative path check
|
||||
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)
|
||||
this.app.get('/api/cases/:caseName/ralph-wizard/files', async (req) => {
|
||||
const { caseName } = req.params as { caseName: string };
|
||||
const casesDir = join(homedir(), 'claudeman-cases');
|
||||
const casesDir = join(homedir(), 'codeman-cases');
|
||||
let casePath = join(casesDir, caseName);
|
||||
|
||||
// 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
|
||||
if (!existsSync(casePath)) {
|
||||
const linkedCasesFile = join(homedir(), '.claudeman', 'linked-cases.json');
|
||||
const linkedCasesFile = join(homedir(), '.codeman', 'linked-cases.json');
|
||||
try {
|
||||
const linkedCases: Record<string, string> = JSON.parse(await fs.readFile(linkedCasesFile, 'utf-8'));
|
||||
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
|
||||
this.app.get('/api/cases/:caseName/ralph-wizard/file/:filePath', async (req, reply) => {
|
||||
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);
|
||||
|
||||
// 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
|
||||
if (!existsSync(casePath)) {
|
||||
const linkedCasesFile = join(homedir(), '.claudeman', 'linked-cases.json');
|
||||
const linkedCasesFile = join(homedir(), '.codeman', 'linked-cases.json');
|
||||
try {
|
||||
const linkedCases: Record<string, string> = JSON.parse(await fs.readFile(linkedCasesFile, 'utf-8'));
|
||||
if (linkedCases[caseName]) {
|
||||
@@ -3369,7 +3369,7 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
|
||||
});
|
||||
|
||||
// ============ App Settings Endpoints ============
|
||||
const settingsPath = join(homedir(), '.claudeman', 'settings.json');
|
||||
const settingsPath = join(homedir(), '.codeman', 'settings.json');
|
||||
|
||||
this.app.get('/api/settings', async () => {
|
||||
try {
|
||||
@@ -3520,7 +3520,7 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
|
||||
|
||||
// ============ Subagent Window State Endpoints ============
|
||||
// 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 () => {
|
||||
try {
|
||||
@@ -3555,7 +3555,7 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
|
||||
// ============ Subagent Parent Associations ============
|
||||
// Persists which TAB each agent window connects to.
|
||||
// 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 () => {
|
||||
try {
|
||||
@@ -3836,7 +3836,7 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
|
||||
ext = map[ctMatch[1].toLowerCase()] ?? ext;
|
||||
}
|
||||
|
||||
// Save to ~/.claudeman/screenshots/
|
||||
// Save to ~/.codeman/screenshots/
|
||||
if (!existsSync(SCREENSHOTS_DIR)) {
|
||||
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
|
||||
private async getDefaultClaudeMdPath(): Promise<string | undefined> {
|
||||
const settingsPath = join(homedir(), '.claudeman', 'settings.json');
|
||||
const settingsPath = join(homedir(), '.codeman', 'settings.json');
|
||||
|
||||
try {
|
||||
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;
|
||||
}
|
||||
|
||||
// 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.
|
||||
private _settingsCache: { data: Record<string, unknown>; ts: number } | null = null;
|
||||
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) {
|
||||
return this._settingsCache.data;
|
||||
}
|
||||
const settingsPath = join(homedir(), '.claudeman', 'settings.json');
|
||||
const settingsPath = join(homedir(), '.codeman', 'settings.json');
|
||||
try {
|
||||
const content = await fs.readFile(settingsPath, 'utf-8');
|
||||
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' });
|
||||
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)
|
||||
process.env.CLAUDEMAN_API_URL = `${protocol}://localhost:${this.port}`;
|
||||
process.env.CODEMAN_API_URL = `${protocol}://localhost:${this.port}`;
|
||||
|
||||
// Start scheduled runs cleanup timer
|
||||
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)
|
||||
*/
|
||||
private async isSubagentTrackingEnabled(): Promise<boolean> {
|
||||
const settingsPath = join(homedir(), '.claudeman', 'settings.json');
|
||||
const settingsPath = join(homedir(), '.codeman', 'settings.json');
|
||||
try {
|
||||
const content = await fs.readFile(settingsPath, 'utf-8');
|
||||
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)
|
||||
*/
|
||||
private async isImageWatcherEnabled(): Promise<boolean> {
|
||||
const settingsPath = join(homedir(), '.claudeman', 'settings.json');
|
||||
const settingsPath = join(homedir(), '.codeman', 'settings.json');
|
||||
try {
|
||||
const content = await fs.readFile(settingsPath, 'utf-8');
|
||||
const settings = JSON.parse(content);
|
||||
|
||||
@@ -149,7 +149,7 @@ describe('AiIdleChecker', () => {
|
||||
// Verify tmux was spawned with correct args
|
||||
expect(mockedSpawn).toHaveBeenCalledWith(
|
||||
'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' })
|
||||
);
|
||||
});
|
||||
@@ -163,7 +163,7 @@ describe('AiIdleChecker', () => {
|
||||
|
||||
// First call should try to kill existing session
|
||||
expect(mockedExecSync).toHaveBeenCalledWith(
|
||||
expect.stringContaining('tmux kill-session -t "claudeman-aicheck-'),
|
||||
expect.stringContaining('tmux kill-session -t "codeman-aicheck-'),
|
||||
expect.any(Object)
|
||||
);
|
||||
});
|
||||
@@ -176,7 +176,7 @@ describe('AiIdleChecker', () => {
|
||||
await checkPromise;
|
||||
|
||||
expect(mockedWriteFileSync).toHaveBeenCalledWith(
|
||||
expect.stringContaining('claudeman-aicheck-'),
|
||||
expect.stringContaining('codeman-aicheck-'),
|
||||
''
|
||||
);
|
||||
});
|
||||
|
||||
@@ -537,7 +537,7 @@ describe('API Response Structures', () => {
|
||||
it('should validate case info', () => {
|
||||
const caseInfo: CaseInfo = {
|
||||
name: 'my-project',
|
||||
path: '/home/user/claudeman-cases/my-project',
|
||||
path: '/home/user/codeman-cases/my-project',
|
||||
hasClaudeMd: true,
|
||||
};
|
||||
|
||||
@@ -549,7 +549,7 @@ describe('API Response Structures', () => {
|
||||
it('should handle optional hasClaudeMd', () => {
|
||||
const caseInfo: CaseInfo = {
|
||||
name: 'new-project',
|
||||
path: '/home/user/claudeman-cases/new-project',
|
||||
path: '/home/user/codeman-cases/new-project',
|
||||
};
|
||||
|
||||
expect(caseInfo.hasClaudeMd).toBeUndefined();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* @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.
|
||||
*/
|
||||
|
||||
|
||||
@@ -318,7 +318,7 @@ describe('CLI Command Parsing', () => {
|
||||
|
||||
describe('Help Text Generation', () => {
|
||||
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`;
|
||||
if (options.length > 0) {
|
||||
help += '\nOptions:\n';
|
||||
@@ -331,7 +331,7 @@ describe('CLI Command Parsing', () => {
|
||||
|
||||
it('should generate basic help', () => {
|
||||
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');
|
||||
});
|
||||
|
||||
@@ -724,8 +724,8 @@ describe('CLI Configuration', () => {
|
||||
maxConcurrentSessions: 5,
|
||||
defaultPort: 3000,
|
||||
defaultHost: '0.0.0.0',
|
||||
casesDirectory: '~/claudeman-cases',
|
||||
stateDirectory: '~/.claudeman',
|
||||
casesDirectory: '~/codeman-cases',
|
||||
stateDirectory: '~/.codeman',
|
||||
};
|
||||
|
||||
const expandPath = (path: string): string => {
|
||||
@@ -742,8 +742,8 @@ describe('CLI Configuration', () => {
|
||||
});
|
||||
|
||||
it('should expand home directory paths', () => {
|
||||
expect(expandPath('~/claudeman-cases')).toBe('/home/user/claudeman-cases');
|
||||
expect(expandPath('~/.claudeman')).toBe('/home/user/.claudeman');
|
||||
expect(expandPath('~/codeman-cases')).toBe('/home/user/codeman-cases');
|
||||
expect(expandPath('~/.codeman')).toBe('/home/user/.codeman');
|
||||
});
|
||||
|
||||
it('should not modify absolute paths', () => {
|
||||
|
||||
@@ -5,7 +5,7 @@ import { join } from 'node:path';
|
||||
import { homedir } from 'node:os';
|
||||
|
||||
const TEST_PORT = 3110;
|
||||
const CASES_DIR = join(homedir(), 'claudeman-cases');
|
||||
const CASES_DIR = join(homedir(), 'codeman-cases');
|
||||
|
||||
describe('Edge Cases and Error Handling', () => {
|
||||
let server: WebServer;
|
||||
@@ -349,7 +349,7 @@ describe('Concurrent Session Handling', () => {
|
||||
const { join } = await import('node:path');
|
||||
const { homedir } = await import('node:os');
|
||||
for (const name of createdCases) {
|
||||
const path = join(homedir(), 'claudeman-cases', name);
|
||||
const path = join(homedir(), 'codeman-cases', name);
|
||||
if (existsSync(path)) {
|
||||
rmSync(path, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
@@ -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
|
||||
* and open the log viewer window correctly.
|
||||
@@ -88,7 +88,7 @@ describe('File Link Click Tests', () => {
|
||||
closeBrowser();
|
||||
|
||||
// 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 });
|
||||
testLogFile = join(testDir, 'test.log');
|
||||
writeFileSync(testLogFile, '=== Test Log Started ===\n');
|
||||
@@ -102,7 +102,7 @@ describe('File Link Click Tests', () => {
|
||||
browser(`open ${baseUrl}`);
|
||||
await new Promise(r => setTimeout(r, 2000));
|
||||
const title = browserJson<{ title: string }>('get title');
|
||||
browserAvailable = title.title === 'Claudeman';
|
||||
browserAvailable = title.title === 'Codeman';
|
||||
} catch (e) {
|
||||
console.warn('Browser not available, skipping browser tests:', (e as Error).message);
|
||||
browserAvailable = false;
|
||||
|
||||
@@ -54,8 +54,8 @@ describe('generateHooksConfig', () => {
|
||||
const config = generateHooksConfig();
|
||||
const notifHooks = config.hooks.Notification as Array<{ hooks: Array<{ command: string }> }>;
|
||||
const cmd = notifHooks[0].hooks[0].command;
|
||||
expect(cmd).toContain('$CLAUDEMAN_API_URL');
|
||||
expect(cmd).toContain('$CLAUDEMAN_SESSION_ID');
|
||||
expect(cmd).toContain('$CODEMAN_API_URL');
|
||||
expect(cmd).toContain('$CODEMAN_SESSION_ID');
|
||||
expect(cmd).not.toContain('localhost');
|
||||
});
|
||||
|
||||
@@ -99,7 +99,7 @@ describe('generateHooksConfig', () => {
|
||||
});
|
||||
|
||||
describe('writeHooksConfig', () => {
|
||||
const testDir = join(tmpdir(), 'claudeman-hooks-test-' + Date.now());
|
||||
const testDir = join(tmpdir(), 'codeman-hooks-test-' + Date.now());
|
||||
|
||||
beforeEach(() => {
|
||||
if (!existsSync(testDir)) {
|
||||
@@ -648,8 +648,8 @@ describe('Hook Config Generation - Extended', () => {
|
||||
const config = generateHooksConfig();
|
||||
const notifHooks = config.hooks.Notification as Array<{ hooks: Array<{ command: string }> }>;
|
||||
const cmd = notifHooks[0].hooks[0].command;
|
||||
expect(cmd).toContain('$CLAUDEMAN_API_URL');
|
||||
expect(cmd).toContain('$CLAUDEMAN_SESSION_ID');
|
||||
expect(cmd).toContain('$CODEMAN_API_URL');
|
||||
expect(cmd).toContain('$CODEMAN_SESSION_ID');
|
||||
});
|
||||
|
||||
it('should generate POST curl commands', () => {
|
||||
|
||||
@@ -96,7 +96,7 @@ async function main() {
|
||||
page.on('console', msg => consoleLogs.push(`[${msg.type()}] ${msg.text()}`));
|
||||
|
||||
try {
|
||||
console.log('Opening Claudeman...');
|
||||
console.log('Opening Codeman...');
|
||||
await page.goto(BASE);
|
||||
await page.waitForSelector('.xterm-screen', { timeout: 10000 });
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
@@ -5,7 +5,7 @@ import { join } from 'node:path';
|
||||
import { homedir } from 'node:os';
|
||||
|
||||
const TEST_PORT = 3115;
|
||||
const CASES_DIR = join(homedir(), 'claudeman-cases');
|
||||
const CASES_DIR = join(homedir(), 'codeman-cases');
|
||||
|
||||
/**
|
||||
* Integration tests for complete user flows
|
||||
|
||||
@@ -149,9 +149,9 @@ async function main() {
|
||||
|
||||
// Enable local echo
|
||||
await page.evaluate(() => {
|
||||
const s = JSON.parse(localStorage.getItem('claudeman-app-settings') || '{}');
|
||||
const s = JSON.parse(localStorage.getItem('codeman-app-settings') || '{}');
|
||||
s.localEchoEnabled = true;
|
||||
localStorage.setItem('claudeman-app-settings', JSON.stringify(s));
|
||||
localStorage.setItem('codeman-app-settings', JSON.stringify(s));
|
||||
window.app?._updateLocalEchoState?.();
|
||||
});
|
||||
|
||||
|
||||
@@ -117,9 +117,9 @@ async function main() {
|
||||
|
||||
// Enable local echo
|
||||
await page.evaluate(() => {
|
||||
const s = JSON.parse(localStorage.getItem('claudeman-app-settings') || '{}');
|
||||
const s = JSON.parse(localStorage.getItem('codeman-app-settings') || '{}');
|
||||
s.localEchoEnabled = true;
|
||||
localStorage.setItem('claudeman-app-settings', JSON.stringify(s));
|
||||
localStorage.setItem('codeman-app-settings', JSON.stringify(s));
|
||||
window.app?._updateLocalEchoState?.();
|
||||
});
|
||||
|
||||
|
||||
@@ -120,9 +120,9 @@ async function main() {
|
||||
|
||||
// Enable local echo
|
||||
await page.evaluate(() => {
|
||||
const s = JSON.parse(localStorage.getItem('claudeman-app-settings') || '{}');
|
||||
const s = JSON.parse(localStorage.getItem('codeman-app-settings') || '{}');
|
||||
s.localEchoEnabled = true;
|
||||
localStorage.setItem('claudeman-app-settings', JSON.stringify(s));
|
||||
localStorage.setItem('codeman-app-settings', JSON.stringify(s));
|
||||
window.app?._updateLocalEchoState?.();
|
||||
});
|
||||
|
||||
@@ -307,7 +307,7 @@ async function main() {
|
||||
await page.keyboard.type('will-be-lost', { delay: 20 });
|
||||
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)');
|
||||
else fail('localStorage written', `value="${lsBefore}"`);
|
||||
|
||||
|
||||
@@ -227,7 +227,7 @@ async function main() {
|
||||
|
||||
try {
|
||||
// ── SETUP ──
|
||||
console.log('Opening Claudeman...');
|
||||
console.log('Opening Codeman...');
|
||||
await page.goto(BASE, { waitUntil: 'domcontentloaded' });
|
||||
await page.waitForSelector('.xterm-screen', { timeout: 10000 });
|
||||
await page.waitForTimeout(2000);
|
||||
@@ -235,9 +235,9 @@ async function main() {
|
||||
// Enable local echo via settings
|
||||
console.log('Enabling local echo...');
|
||||
await page.evaluate(() => {
|
||||
const settings = JSON.parse(localStorage.getItem('claudeman-app-settings') || '{}');
|
||||
const settings = JSON.parse(localStorage.getItem('codeman-app-settings') || '{}');
|
||||
settings.localEchoEnabled = true;
|
||||
localStorage.setItem('claudeman-app-settings', JSON.stringify(settings));
|
||||
localStorage.setItem('codeman-app-settings', JSON.stringify(settings));
|
||||
window.app?._updateLocalEchoState?.();
|
||||
});
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
@@ -126,9 +126,9 @@ async function main() {
|
||||
|
||||
// Enable local echo
|
||||
await page.evaluate(() => {
|
||||
const s = JSON.parse(localStorage.getItem('claudeman-app-settings') || '{}');
|
||||
const s = JSON.parse(localStorage.getItem('codeman-app-settings') || '{}');
|
||||
s.localEchoEnabled = true;
|
||||
localStorage.setItem('claudeman-app-settings', JSON.stringify(s));
|
||||
localStorage.setItem('codeman-app-settings', JSON.stringify(s));
|
||||
window.app?._updateLocalEchoState?.();
|
||||
});
|
||||
|
||||
|
||||
@@ -144,7 +144,7 @@ async function main() {
|
||||
|
||||
try {
|
||||
// ── SETUP ──
|
||||
console.log('Opening Claudeman...');
|
||||
console.log('Opening Codeman...');
|
||||
await page.goto(BASE, { waitUntil: 'domcontentloaded' });
|
||||
await page.waitForSelector('.xterm-screen', { timeout: 10000 });
|
||||
await page.waitForTimeout(2500);
|
||||
@@ -152,7 +152,7 @@ async function main() {
|
||||
// Enable local echo
|
||||
console.log('Enabling local echo...');
|
||||
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) || '{}');
|
||||
settings.localEchoEnabled = true;
|
||||
localStorage.setItem(key, JSON.stringify(settings));
|
||||
|
||||
@@ -241,9 +241,9 @@ async function main() {
|
||||
|
||||
// Enable local echo
|
||||
await page.evaluate(() => {
|
||||
const s = JSON.parse(localStorage.getItem('claudeman-app-settings') || '{}');
|
||||
const s = JSON.parse(localStorage.getItem('codeman-app-settings') || '{}');
|
||||
s.localEchoEnabled = true;
|
||||
localStorage.setItem('claudeman-app-settings', JSON.stringify(s));
|
||||
localStorage.setItem('codeman-app-settings', JSON.stringify(s));
|
||||
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.waitForTimeout(2000);
|
||||
await page.evaluate(() => {
|
||||
const s = JSON.parse(localStorage.getItem('claudeman-app-settings') || '{}');
|
||||
const s = JSON.parse(localStorage.getItem('codeman-app-settings') || '{}');
|
||||
s.localEchoEnabled = true;
|
||||
localStorage.setItem('claudeman-app-settings', JSON.stringify(s));
|
||||
localStorage.setItem('codeman-app-settings', JSON.stringify(s));
|
||||
window.app?._updateLocalEchoState?.();
|
||||
});
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* @fileoverview Tests for memory leak prevention patterns
|
||||
*
|
||||
* 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)
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Performance tests: Claudeman responsiveness under subagent load
|
||||
* Performance tests: Codeman responsiveness under subagent load
|
||||
*
|
||||
* Measures event loop lag caused by:
|
||||
* 1. SubagentWatcher processing many agent files simultaneously
|
||||
@@ -112,7 +112,7 @@ describe('SubagentWatcher performance', () => {
|
||||
let projectsDir: string;
|
||||
|
||||
beforeEach(() => {
|
||||
tmpDir = mkdtempSync(join(tmpdir(), 'claudeman-perf-'));
|
||||
tmpDir = mkdtempSync(join(tmpdir(), 'codeman-perf-'));
|
||||
projectsDir = join(tmpDir, 'projects');
|
||||
mkdirSync(projectsDir, { recursive: true });
|
||||
});
|
||||
|
||||
@@ -5,7 +5,7 @@ import { join } from 'node:path';
|
||||
import { homedir } from 'node:os';
|
||||
|
||||
const TEST_PORT = 3099;
|
||||
const CASES_DIR = join(homedir(), 'claudeman-cases');
|
||||
const CASES_DIR = join(homedir(), 'codeman-cases');
|
||||
|
||||
describe('Quick Start API', () => {
|
||||
let server: WebServer;
|
||||
|
||||
@@ -18,7 +18,7 @@ import { join } from 'node:path';
|
||||
import { homedir } from 'node:os';
|
||||
|
||||
const TEST_PORT = 3125;
|
||||
const CASES_DIR = join(homedir(), 'claudeman-cases');
|
||||
const CASES_DIR = join(homedir(), 'codeman-cases');
|
||||
|
||||
describe('Ralph Integration Tests', () => {
|
||||
let server: WebServer;
|
||||
|
||||
+11
-11
@@ -2,10 +2,10 @@
|
||||
|
||||
**Date**: 2026-01-25
|
||||
**Analyzed Files**:
|
||||
- `/home/arkon/default/claudeman/src/respawn-controller.ts`
|
||||
- `/home/arkon/default/claudeman/src/ai-idle-checker.ts`
|
||||
- `/home/arkon/default/claudeman/src/ai-plan-checker.ts`
|
||||
- `/home/arkon/default/claudeman/test/respawn-controller.test.ts`
|
||||
- `/home/arkon/default/codeman/src/respawn-controller.ts`
|
||||
- `/home/arkon/default/codeman/src/ai-idle-checker.ts`
|
||||
- `/home/arkon/default/codeman/src/ai-plan-checker.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)
|
||||
|
||||
**File**: `/home/arkon/default/claudeman/src/ai-plan-checker.ts`
|
||||
**File**: `/home/arkon/default/codeman/src/ai-plan-checker.ts`
|
||||
**Lines**: 341-346
|
||||
**Severity**: HIGH
|
||||
|
||||
@@ -84,7 +84,7 @@ const claudeCmd = `cat "${this.checkPromptFile}" | claude -p ${modelArg} --outpu
|
||||
|
||||
### 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
|
||||
**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)
|
||||
|
||||
**File**: `/home/arkon/default/claudeman/src/ai-idle-checker.ts`
|
||||
**File**: `/home/arkon/default/codeman/src/ai-idle-checker.ts`
|
||||
**Lines**: 265-279
|
||||
**Severity**: MEDIUM
|
||||
|
||||
@@ -127,7 +127,7 @@ cancel(): void {
|
||||
|
||||
### 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)
|
||||
**Severity**: LOW
|
||||
|
||||
@@ -135,21 +135,21 @@ cancel(): void {
|
||||
|
||||
**Code**:
|
||||
```typescript
|
||||
this.checkScreenName = `claudeman-aicheck-${shortId}`;
|
||||
this.checkScreenName = `codeman-aicheck-${shortId}`;
|
||||
```
|
||||
|
||||
**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:
|
||||
```typescript
|
||||
this.checkScreenName = `claudeman-aicheck-${shortId}-${timestamp}`;
|
||||
this.checkScreenName = `codeman-aicheck-${shortId}-${timestamp}`;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 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
|
||||
**Severity**: LOW
|
||||
|
||||
|
||||
@@ -243,7 +243,7 @@ afterEach(() => {
|
||||
|
||||
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
|
||||
3. **Pre-existing Protection**: Never kills screens that existed before tests started
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ export class MockSession extends EventEmitter {
|
||||
constructor(id: string = 'mock-session-id') {
|
||||
super();
|
||||
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()) */
|
||||
|
||||
@@ -5,7 +5,7 @@ import { join } from 'node:path';
|
||||
import { homedir } from 'node:os';
|
||||
|
||||
const TEST_PORT = 3120;
|
||||
const CASES_DIR = join(homedir(), 'claudeman-cases');
|
||||
const CASES_DIR = join(homedir(), 'codeman-cases');
|
||||
|
||||
/**
|
||||
* Session Cleanup Tests
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user