diff --git a/.env.example b/.env.example index 8afda34..43af935 100644 --- a/.env.example +++ b/.env.example @@ -1,24 +1,6 @@ -# monkeyproof configuration -# Copy to .env and customize - -# Server port PORT=3200 - -# Auth token for API access (CHANGE THIS) -AGENT_WS_TOKEN=change-me-to-something-secret - -# Max concurrent sessions -MAX_SESSIONS=10 - -# Lines of output to buffer per session +AGENT_WS_TOKEN=monkeyproof-dev +MAX_SESSIONS=50 OUTPUT_BUFFER_SIZE=2000 - -# Auto-cleanup exited sessions after (ms) -- default 1 hour SESSION_TTL_MS=3600000 - -# If using Claude Code with Anthropic API directly: -# ANTHROPIC_API_KEY=sk-ant-... - -# If routing through LiteLLM proxy: -# ANTHROPIC_API_KEY=sk-your-litellm-key -# ANTHROPIC_BASE_URL=http://your-litellm-proxy:4000 +INTERACTIVE_SESSION_TTL_MS=7200000 diff --git a/monkeyproof.config.json b/monkeyproof.config.json new file mode 100644 index 0000000..82409d3 --- /dev/null +++ b/monkeyproof.config.json @@ -0,0 +1,42 @@ +{ + "port": 3200, + "authToken": "monkeyproof-dev", + "maxSessions": 50, + "outputBufferSize": 2000, + "sessionTtlMs": 3600000, + "interactiveSessionTtlMs": 7200000, + "presets": { + "claude": { + "command": "claude", + "args": ["--print", "--permission-mode", "bypassPermissions"] + }, + "claude-sonnet": { + "command": "claude", + "args": ["--print", "--permission-mode", "bypassPermissions", "--model", "sonnet"] + }, + "claude-opus": { + "command": "claude", + "args": ["--print", "--permission-mode", "bypassPermissions", "--model", "opus"] + }, + "claude-interactive": { + "command": "claude", + "args": ["--permission-mode", "bypassPermissions"] + }, + "claude-interactive-sonnet": { + "command": "claude", + "args": ["--permission-mode", "bypassPermissions", "--model", "sonnet"] + }, + "claude-interactive-opus": { + "command": "claude", + "args": ["--permission-mode", "bypassPermissions", "--model", "opus"] + }, + "codex": { + "command": "codex", + "args": ["exec"] + }, + "codex-auto": { + "command": "codex", + "args": ["exec", "--full-auto"] + } + } +} diff --git a/src/config.ts b/src/config.ts index 5a2ce26..2a29525 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,21 +1,62 @@ /** * @module config - * @description Server configuration from environment variables. + * @description Server configuration with file → env → defaults priority. + * + * Loading order: + * 1. monkeyproof.config.json (cwd or repo root) + * 2. Environment variables (override file values) + * 3. Hardcoded defaults (fallback) */ +import { existsSync, readFileSync } from "fs"; +import { join } from "path"; + +interface ConfigFile { + port?: number; + authToken?: string; + maxSessions?: number; + outputBufferSize?: number; + sessionTtlMs?: number; + interactiveSessionTtlMs?: number; + presets?: Record; +} + +function loadConfigFile(): ConfigFile { + const paths = [ + join(process.cwd(), "monkeyproof.config.json"), + join(__dirname, "..", "monkeyproof.config.json"), + ]; + + for (const p of paths) { + if (existsSync(p)) { + try { + const raw = readFileSync(p, "utf-8"); + return JSON.parse(raw) as ConfigFile; + } catch (e) { + console.warn(`Warning: failed to parse ${p}:`, e); + } + } + } + + return {}; +} + +const file = loadConfigFile(); + export const config = { - port: parseInt(process.env.PORT || "3200", 10), - authToken: process.env.AGENT_WS_TOKEN || "monkeyproof-dev", - maxSessions: parseInt(process.env.MAX_SESSIONS || "10", 10), - outputBufferSize: parseInt(process.env.OUTPUT_BUFFER_SIZE || "2000", 10), - sessionTtlMs: parseInt(process.env.SESSION_TTL_MS || "3600000", 10), // 1 hour + port: parseInt(process.env.PORT || String(file.port ?? 3200), 10), + authToken: process.env.AGENT_WS_TOKEN || file.authToken || "monkeyproof-dev", + maxSessions: parseInt(process.env.MAX_SESSIONS || String(file.maxSessions ?? 50), 10), + outputBufferSize: parseInt(process.env.OUTPUT_BUFFER_SIZE || String(file.outputBufferSize ?? 2000), 10), + sessionTtlMs: parseInt(process.env.SESSION_TTL_MS || String(file.sessionTtlMs ?? 3600000), 10), + interactiveSessionTtlMs: parseInt( + process.env.INTERACTIVE_SESSION_TTL_MS || String(file.interactiveSessionTtlMs ?? 7200000), + 10, + ), }; -/** - * Agent presets -- common command + args combos. - * Model names must match LiteLLM proxy aliases (10.71.1.33:4000). - */ -export const presets: Record = { +/** Default presets -- overridden by config file if present */ +const DEFAULT_PRESETS: Record = { claude: { command: "claude", args: ["--print", "--permission-mode", "bypassPermissions"], @@ -49,3 +90,26 @@ export const presets: Record = { args: ["--permission-mode", "bypassPermissions", "--model", "opus"], }, }; + +export const presets: Record = + file.presets && Object.keys(file.presets).length > 0 ? file.presets : DEFAULT_PRESETS; + +/** + * Check if a preset is interactive (no --print flag). + * Interactive sessions get a longer TTL. + */ +export function isInteractivePreset(presetName: string): boolean { + const preset = presets[presetName]; + if (!preset) return false; + return !preset.args.includes("--print"); +} + +/** + * Get the appropriate TTL for a session based on its preset. + */ +export function getSessionTtl(presetName?: string): number { + if (presetName && isInteractivePreset(presetName)) { + return config.interactiveSessionTtlMs; + } + return config.sessionTtlMs; +}