From 177b4450ef57c94097b038e48f9eaa5d69ee4e47 Mon Sep 17 00:00:00 2001 From: Eyejoker Date: Mon, 23 Mar 2026 07:51:00 +0900 Subject: [PATCH] rebrand to EJClaw, update README and browser skill - Rename project to EJClaw, update README with verified features - Update agent-browser SKILL.md for gstack browse commands (goto, snapshot -D/-a/-C, tabs, dialogs, etc.) --- README.md | 277 ++++++-------------------- runners/skills/agent-browser/SKILL.md | 122 +++++++----- 2 files changed, 130 insertions(+), 269 deletions(-) diff --git a/README.md b/README.md index 76d012b..3ae9b04 100644 --- a/README.md +++ b/README.md @@ -1,223 +1,105 @@ -

- NanoClaw -

+# EJClaw -

- Dual-agent AI assistant running Claude Code + Codex as parallel services over Discord. -

+Dual-agent AI assistant (Claude Code + Codex) over Discord. -

- Based on qwibitai/nanoclaw -

+Forked from [qwibitai/nanoclaw](https://github.com/qwibitai/nanoclaw), heavily customized for production use. ## Overview -Two AI agents running as parallel systemd services, communicating over Discord: +Two AI agents running as parallel systemd services on a single host: -- **Claude Code** — powered by Claude Agent SDK, trigger `@claude` -- **Codex** — powered by Codex app-server (JSON-RPC), trigger `@codex` +- **Claude Code** (`@claude`) — Anthropic Agent SDK, adaptive thinking, Opus/Sonnet +- **Codex** (`@codex`) — OpenAI Codex app-server (JSON-RPC), GPT-5.4, xhigh reasoning -Each agent has its own store, data, and groups directories. Discord channels can be registered with either agent, or both (`both` agent type for shared channels). +Both share the same codebase (`dist/index.js`), differentiated by environment variables. No containers — direct host processes for zero overhead. -### Key Features +## Features -- **Direct host processes** — no container overhead, agents run natively -- **Bidirectional image support** — receive images as multimodal input, send as Discord attachments -- **Skill sync** — single source of truth (`~/.claude/skills/`), auto-synced to all sessions -- **OAuth auto-refresh** — token lifecycle managed automatically for headless environments -- **Priority queue** — per-group serialization, global concurrency limit, idle preemption -- **Auto-continue** — Codex text-only turns automatically retried to enforce task execution +- **Dual-agent architecture** — Claude Code + Codex as parallel services, shared SQLite (WAL mode) +- **Browser automation** — [gstack browse](https://github.com/garrytan/gstack) skill, headless Chromium daemon, ~100ms/command +- **Voice transcription** — Groq Whisper (primary) / OpenAI Whisper (fallback), shared file cache with dedup +- **Bidirectional images** — receive Discord attachments as multimodal input, send screenshots back +- **MCP integration** — Memento (persistent cross-session memory) +- **Skill sync** — single source of truth, auto-synced to all agent sessions +- **Priority queue** — per-group serialization, global concurrency limit +- **Session persistence** — resume conversations across restarts +- **Scheduled tasks** — cron/interval/once via MCP tool +- **Mid-turn steering** — inject follow-up messages while agent is working (both agents) ## Architecture ``` -Discord ──► SQLite ──► GroupQueue ──┬──► Claude Agent SDK (host process) - └──► Codex App-Server (JSON-RPC stdio) - ├── thread/start, thread/resume - ├── turn/start (streaming, multimodal) - ├── turn/steer (mid-turn injection) - ├── Auto-approval (bypass sandbox) - └── Auto-continue (text-only turn retry) +Discord ──► SQLite (WAL) ──► GroupQueue ──┬──► Claude Agent SDK + └──► Codex App-Server (JSON-RPC) + ├── thread/start, thread/resume + ├── turn/start (streaming, multimodal) + └── turn/steer (mid-turn injection) + +Agent ──► Bash ──► agent-browser (gstack browse) + ├── goto, snapshot, click, fill + ├── screenshot, pdf, responsive + └── persistent Chromium daemon (~100ms/cmd) ``` -### Directory Layout +## Directory Layout ``` -nanoclaw/ +ejclaw/ ├── src/ -│ ├── index.ts # Orchestrator: state, message loop, agent invocation -│ ├── agent-runner.ts # Spawns agent processes, manages env/sessions/skills -│ ├── group-queue.ts # Per-group concurrency, priority queue, idle preemption -│ ├── group-folder.ts # Group directory resolution and management -│ ├── router.ts # Outbound message formatting and routing -│ ├── sender-allowlist.ts # Security: sender-based access control -│ ├── session-commands.ts # Session commands (/compact) -│ ├── token-refresh.ts # OAuth auto-refresh + session directory sync -│ ├── task-scheduler.ts # Scheduled tasks (cron/interval/once) -│ ├── ipc.ts # IPC watcher and task processing -│ ├── db.ts # SQLite operations -│ ├── config.ts # Paths, intervals, trigger patterns +│ ├── index.ts # Orchestrator: state, message loop, agent invocation +│ ├── agent-runner.ts # Spawns agent processes, manages env/sessions/skills +│ ├── group-queue.ts # Per-group concurrency, priority queue +│ ├── router.ts # Outbound message formatting and routing +│ ├── db.ts # SQLite operations (WAL mode, shared access) +│ ├── config.ts # Paths, intervals, trigger patterns │ └── channels/ -│ ├── registry.ts # Channel self-registration system -│ └── discord.ts # Discord: mentions, images, typing, file attachments +│ └── discord.ts # Discord: mentions, images, typing, voice transcription ├── runners/ -│ ├── agent-runner/ # Claude Code runner (Agent SDK, multimodal input) -│ ├── codex-runner/ # Codex runner (app-server JSON-RPC, auto-continue) -│ └── skills/ # Shared agent skills (browser, etc.) -├── store/ # Claude Code service DB -├── store-codex/ # Codex service DB -├── data/ -│ ├── sessions/ # Per-group Claude sessions (.claude/) -│ └── attachments/ # Downloaded Discord image attachments -├── data-codex/sessions/ # Per-group Codex sessions (.codex/) -├── groups/ # Per-group memory and workspace (Claude Code) -├── groups-codex/ # Per-group memory and workspace (Codex) -└── logs/ # Service logs +│ ├── agent-runner/ # Claude Code runner (Agent SDK) +│ ├── codex-runner/ # Codex runner (app-server JSON-RPC) +│ └── skills/ +│ └── agent-browser/ # Browser automation (gstack browse wrapper) +├── groups/{name}/ # Per-group memory (CLAUDE.md) +├── data/sessions/ # Per-group agent sessions +├── store/ # SQLite database +└── prompts/ # Platform-level system prompts ``` -### Codex App-Server Integration - -The Codex runner (`runners/codex-runner/`) communicates with `codex app-server` via JSON-RPC over stdio: - -- **Session persistence**: Thread IDs stored in DB, sessions saved as JSONL on disk -- **Streaming**: `item/agentMessage/delta` notifications for real-time text -- **Mid-turn steering**: IPC messages injected via `turn/steer` during execution -- **Auto-approval**: `approvalPolicy: "never"` + `sandbox: "danger-full-access"` -- **Auto-continue**: Detects text-only turns (no tool execution) and automatically retries up to 5 times to nudge the agent into actually executing tasks -- **Multimodal input**: Image attachments converted to `localImage` input blocks in `turn/start` -- **Per-group config**: Model, effort, MCP servers configured per channel - -### Image Handling - -Bidirectional image support through Discord: - -- **Receiving** (user → agent): Discord image attachments are downloaded to `data/attachments/`, then passed as base64 `ImageBlockParam` content blocks (Claude Code) or `localImage` input blocks (Codex) -- **Sending** (agent → user): Markdown image links `[name.png](/path)` in agent responses are automatically parsed and sent as Discord file attachments. Non-image file links are converted to readable filenames (`BuildPanel.tsx:320`) - -### Skill Sync - -Skills are managed from a single source of truth (`~/.claude/skills/` on the server) and automatically synced to all agent session directories at process start: - -- Claude Code sessions: `~/.claude/skills/` + project `runners/skills/` -- Codex sessions: Same sources, synced to per-group `.codex/` directories -- Skills auto-register as slash commands (`/name`) in Claude Code and `$name` in Codex - -### GroupQueue - -`src/group-queue.ts` manages agent execution with: - -- **Per-group serialization**: Only one agent process per group at a time -- **Global concurrency limit**: Configurable max concurrent agents across all groups -- **Task priority**: Scheduled tasks drain before message processing -- **Idle preemption**: Idle agents are terminated when higher-priority tasks arrive -- **Exponential backoff**: Retries with backoff on processing failure - ## Setup ### Prerequisites - Linux (Ubuntu 22.04+) or macOS -- Node.js 20+ +- Node.js 20+ (fnm recommended) - [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code) - [Codex CLI](https://github.com/openai/codex) (`npm install -g @openai/codex`) -- Two Discord bot tokens (one per agent) — create at [Discord Developer Portal](https://discord.com/developers/applications) +- Bun 1.0+ (for browser automation) +- Discord bot token -### 1. Clone and Install +### Install ```bash -git clone https://github.com/phj1081/nanoclaw.git -cd nanoclaw +git clone https://github.com/phj1081/EJClaw.git +cd EJClaw npm install -npm run build:runners # installs + builds both agent-runner and codex-runner -npm run build # builds main project +npm run build:runners +npm run build ``` -### 2. Authenticate CLIs +### Environment ```bash -# Claude Code — opens browser for OAuth login -claude login - -# Codex — set API key -export OPENAI_API_KEY=sk-... - -# Groq — for fast voice transcription (free at console.groq.com) -export GROQ_API_KEY=gsk_... -``` - -### 3. Environment Variables - -Create `.env` in the project root: - -```bash -# .env — shared config (read by both services) -DISCORD_BOT_TOKEN= # Claude Code Discord bot token +# .env +DISCORD_BOT_TOKEN= # Discord bot token ASSISTANT_NAME=claude # Bot trigger name (@claude) -ANTHROPIC_API_KEY= # Or use OAuth (claude login) +CLAUDE_CODE_OAUTH_TOKEN= # Claude Code OAuth token OPENAI_API_KEY= # For Codex -GROQ_API_KEY= # For voice transcription (Groq Whisper, fast + free) +GROQ_API_KEY= # Voice transcription (Groq Whisper) ``` -For dual-service setup, create `.env.codex` for Codex-specific overrides: +### Systemd Services (Linux) ```bash -# .env.codex — Codex service secrets (loaded via systemd EnvironmentFile) -DISCORD_BOT_TOKEN= # Codex Discord bot token (different from above) -``` - -> **Security**: Never put tokens in systemd service files or commit them to git. Use `.env` files with restricted permissions (`chmod 600`). - -### 4. Systemd Services (Linux) - -Create `~/.config/systemd/user/nanoclaw.service`: - -```ini -[Unit] -Description=NanoClaw Claude Code -After=network.target - -[Service] -Type=simple -ExecStart=/path/to/node /path/to/nanoclaw/dist/index.js -WorkingDirectory=/path/to/nanoclaw -Restart=always -RestartSec=5 -Environment=HOME=/home/youruser -Environment=PATH=/path/to/node/bin:/usr/local/bin:/usr/bin:/bin - -[Install] -WantedBy=default.target -``` - -Create `~/.config/systemd/user/nanoclaw-codex.service`: - -```ini -[Unit] -Description=NanoClaw Codex -After=network.target - -[Service] -EnvironmentFile=/path/to/nanoclaw/.env.codex -Type=simple -ExecStart=/path/to/node /path/to/nanoclaw/dist/index.js -WorkingDirectory=/path/to/nanoclaw -Restart=always -RestartSec=5 -Environment=HOME=/home/youruser -Environment=PATH=/path/to/node/bin:/usr/local/bin:/usr/bin:/bin -Environment=ASSISTANT_NAME=codex -Environment=NANOCLAW_STORE_DIR=/path/to/nanoclaw/store-codex -Environment=NANOCLAW_DATA_DIR=/path/to/nanoclaw/data-codex -Environment=NANOCLAW_GROUPS_DIR=/path/to/nanoclaw/groups-codex - -[Install] -WantedBy=default.target -``` - -Then enable and start: - -```bash -systemctl --user daemon-reload systemctl --user enable nanoclaw nanoclaw-codex systemctl --user start nanoclaw nanoclaw-codex @@ -226,46 +108,13 @@ journalctl --user -u nanoclaw -f journalctl --user -u nanoclaw-codex -f ``` -### 5. Register Discord Channels - -Channels are stored in each service's SQLite database (`registered_groups` table). Use the IPC auth endpoint or insert directly: - -```bash -# Example: register a channel for Claude Code -sqlite3 store/nanoclaw.db "INSERT INTO registered_groups (jid, name, folder, agent_type, trigger_pattern) VALUES ('dc:CHANNEL_ID', 'my-channel', 'my-channel', 'claude-code', '@claude');" - -# Example: register a channel for Codex -sqlite3 store-codex/nanoclaw.db "INSERT INTO registered_groups (jid, name, folder, agent_type, trigger_pattern) VALUES ('dc:CHANNEL_ID', 'my-channel', 'my-channel', 'codex', '@codex');" -``` - -Fields: - -| Field | Description | -|-------|-------------| -| `jid` | `dc:` | -| `name` | Display name | -| `folder` | Group folder name (workspace directory) | -| `agent_type` | `claude-code`, `codex`, or `both` | -| `trigger_pattern` | Regex for activation (e.g., `@claude`) | -| `work_dir` | Optional working directory override | -| `agent_config` | Optional JSON (e.g., `{"codexEffort":"high"}`) | - -### macOS (launchd) - -```bash -launchctl load ~/Library/LaunchAgents/com.nanoclaw.plist -launchctl load ~/Library/LaunchAgents/com.nanoclaw-codex.plist -launchctl kickstart -k gui/$(id -u)/com.nanoclaw -``` - ## Development ```bash -npm install # Install dependencies -npm run build # Build main project -npm run build:runners # Install + build both runners -npm run dev # Dev mode with hot reload -npm test # Run tests +npm run build # Build main project +npm run build:runners # Install + build both runners +npm run dev # Dev mode with hot reload +npm test # Run tests ``` ## License diff --git a/runners/skills/agent-browser/SKILL.md b/runners/skills/agent-browser/SKILL.md index dd6c6bf..3f3eaa5 100644 --- a/runners/skills/agent-browser/SKILL.md +++ b/runners/skills/agent-browser/SKILL.md @@ -6,19 +6,20 @@ allowed-tools: Bash(agent-browser:*) # Browser Automation with agent-browser +Powered by gstack browse — persistent headless Chromium with ~100ms per command. + ## Quick start ```bash -agent-browser open # Navigate to page +agent-browser goto # Navigate to page agent-browser snapshot -i # Get interactive elements with refs agent-browser click @e1 # Click element by ref agent-browser fill @e2 "text" # Fill input by ref -agent-browser close # Close browser ``` ## Core workflow -1. Navigate: `agent-browser open ` +1. Navigate: `agent-browser goto ` 2. Snapshot: `agent-browser snapshot -i` (returns elements with refs like `@e1`, `@e2`) 3. Interact using refs from the snapshot 4. Re-snapshot after navigation or significant DOM changes @@ -28,11 +29,11 @@ agent-browser close # Close browser ### Navigation ```bash -agent-browser open # Navigate to URL +agent-browser goto # Navigate to URL agent-browser back # Go back agent-browser forward # Go forward agent-browser reload # Reload page -agent-browser close # Close browser +agent-browser url # Get current URL ``` ### Snapshot (page analysis) @@ -43,43 +44,45 @@ agent-browser snapshot -i # Interactive elements only (recommended) agent-browser snapshot -c # Compact output agent-browser snapshot -d 3 # Limit depth to 3 agent-browser snapshot -s "#main" # Scope to CSS selector +agent-browser snapshot -D # Diff against previous snapshot +agent-browser snapshot -a # Annotated screenshot with ref labels +agent-browser snapshot -C # Find non-ARIA clickable elements (@c refs) ``` ### Interactions (use @refs from snapshot) ```bash agent-browser click @e1 # Click -agent-browser dblclick @e1 # Double-click agent-browser fill @e2 "text" # Clear and type agent-browser type @e2 "text" # Type without clearing agent-browser press Enter # Press key agent-browser hover @e1 # Hover -agent-browser check @e1 # Check checkbox -agent-browser uncheck @e1 # Uncheck checkbox agent-browser select @e1 "value" # Select dropdown option agent-browser scroll down 500 # Scroll page agent-browser upload @e1 file.pdf # Upload files +agent-browser viewport 1280x720 # Set viewport size ``` -### Get information +### Content extraction ```bash -agent-browser get text @e1 # Get element text -agent-browser get html @e1 # Get innerHTML -agent-browser get value @e1 # Get input value -agent-browser get attr @e1 href # Get attribute -agent-browser get title # Get page title -agent-browser get url # Get current URL -agent-browser get count ".item" # Count matching elements +agent-browser text # Get all page text +agent-browser text @e1 # Get element text +agent-browser html @e1 # Get innerHTML +agent-browser links # Get all links +agent-browser forms # Get all forms +agent-browser accessibility # Full accessibility tree ``` ### Screenshots & PDF ```bash -agent-browser screenshot # Save to temp directory -agent-browser screenshot path.png # Save to specific path -agent-browser screenshot --full # Full page -agent-browser pdf output.pdf # Save as PDF +agent-browser screenshot # Full page screenshot +agent-browser screenshot --viewport # Viewport only +agent-browser screenshot @e1 # Element screenshot +agent-browser screenshot --clip x,y,w,h # Region screenshot +agent-browser pdf output.pdf # Save as PDF +agent-browser responsive # Multi-viewport screenshots ``` ### Wait @@ -92,54 +95,57 @@ agent-browser wait --url "**/dashboard" # Wait for URL pattern agent-browser wait --load networkidle # Wait for network idle ``` -### Semantic locators (alternative to refs) +### Inspection & Debugging ```bash -agent-browser find role button click --name "Submit" -agent-browser find text "Sign In" click -agent-browser find label "Email" fill "user@test.com" -agent-browser find placeholder "Search" type "query" -``` - -### Authentication with saved state - -```bash -# Login once -agent-browser open https://app.example.com/login -agent-browser snapshot -i -agent-browser fill @e1 "username" -agent-browser fill @e2 "password" -agent-browser click @e3 -agent-browser wait --url "**/dashboard" -agent-browser state save auth.json - -# Later: load saved state -agent-browser state load auth.json -agent-browser open https://app.example.com/dashboard +agent-browser js "document.title" # Run JavaScript expression +agent-browser eval script.js # Run JavaScript file +agent-browser css @e1 color # Get CSS property +agent-browser attrs @e1 # Get all attributes +agent-browser is visible @e1 # Check element state +agent-browser console # View console logs +agent-browser console --errors # Console errors only +agent-browser network # View network requests +agent-browser perf # Performance metrics ``` ### Cookies & Storage ```bash -agent-browser cookies # Get all cookies -agent-browser cookies set name value # Set cookie -agent-browser cookies clear # Clear cookies -agent-browser storage local # Get localStorage -agent-browser storage local set k v # Set value +agent-browser cookies # Get all cookies +agent-browser storage # Get localStorage +agent-browser storage set key value # Set localStorage value ``` -### JavaScript +### Tabs ```bash -agent-browser eval "document.title" # Run JavaScript +agent-browser tabs # List open tabs +agent-browser tab # Switch to tab +agent-browser newtab # Open new tab +agent-browser closetab # Close current tab +``` + +### Dialog handling + +```bash +agent-browser dialog-accept # Accept dialog (default behavior) +agent-browser dialog-accept "text" # Accept prompt with text +agent-browser dialog-dismiss # Dismiss dialog +``` + +### Compare + +```bash +agent-browser diff # Compare two pages ``` ## Example: Form submission ```bash -agent-browser open https://example.com/form +agent-browser goto https://example.com/form agent-browser snapshot -i -# Output shows: textbox "Email" [ref=e1], textbox "Password" [ref=e2], button "Submit" [ref=e3] +# Output: @e1 [textbox] "Email", @e2 [textbox] "Password", @e3 [button] "Submit" agent-browser fill @e1 "user@example.com" agent-browser fill @e2 "password123" @@ -151,9 +157,15 @@ agent-browser snapshot -i # Check result ## Example: Data extraction ```bash -agent-browser open https://example.com/products +agent-browser goto https://example.com/products agent-browser snapshot -i -agent-browser get text @e1 # Get product title -agent-browser get attr @e2 href # Get link URL -agent-browser screenshot products.png +agent-browser text @e1 # Get product title +agent-browser screenshot items.png ``` + +## Notes + +- Server auto-starts on first command, auto-shuts after 30min idle +- ~100ms per command after initial startup (~3s first time) +- Refs (`@e1`, `@e2`) are assigned from accessibility tree — re-snapshot after DOM changes +- `@c` refs from `-C` flag target non-ARIA clickable elements (divs with onclick, cursor:pointer)