mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-05 15:09: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:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user