# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Project Overview This is a modular Node.js webhook server for Gogs (Git service) that receives webhooks and executes configurable shell commands based on events. The main server uses **zero external dependencies** - only built-in Node.js modules. **NEW:** The project now includes a TypeScript-based Claude Agent SDK client (`client/`) for AI-powered issue automation, replacing the previous bash-based implementation. ## Architecture ### Module Structure The codebase follows a clean separation of concerns with seven main modules: ``` index.js (Entry Point) ├── server.js (HTTP Server) │ └── webhookHandler.js (Event Management) ├── jobQueue.js (Job Queue & Persistence) │ └── commandExecutor.js (Command Execution) ├── emailNotifier.js (Email Notifications) │ └── emailSender.js (SMTP Email Sender) ├── config.js (Configuration) └── logger.js (Logging) ``` ### Key Components **WebhookServer (src/server.js)** - HTTP server using Node.js `http` and `crypto` modules - Routes: `POST /webhook`, `GET /health` - Provides `getHandler()` to access WebhookHandler for callback registration - Binds to configurable host (0.0.0.0 by default) and port (3000 by default) - Implements HMAC-SHA256 signature verification for webhook security - Uses timing-safe comparison to prevent timing attacks **WebhookHandler (src/webhookHandler.js)** - Event-driven callback system - Two callback types: - `handler.on(eventType, callback)` - Event-specific callbacks - `handler.onAny(callback)` - Global callbacks for all events - Callbacks receive: `(payload, headers)` or `(eventType, payload, headers)` for global **JobQueue (src/jobQueue.js)** - Persistent job queue with FIFO (first-in-first-out) processing - **Two operating modes:** - **Single-queue mode (default)**: One global queue, jobs execute sequentially - **Multi-queue mode**: Separate FIFO queue per repository, enables parallel execution across repositories - Webhooks are accepted immediately and added to queue - Queue state persisted to `queue.json` for crash recovery - Automatically resumes processing pending jobs after restart - Jobs that were running during shutdown are reset to pending state - Mode is configurable via `QUEUE_PARALLEL_PER_REPOSITORY` environment variable **CommandExecutor (src/commandExecutor.js)** - Variable substitution engine using `{{variable}}` syntax - Executes shell commands via `/bin/bash` with timeout protection - Extracts 16+ variables from webhook payloads: `{{repo}}`, `{{branch}}`, `{{pusher}}`, `{{commit}}`, `{{pr_number}}`, `{{issue_number}}`, `{{issue_title}}`, etc. - Default timeout: 300000ms (5 minutes), configurable via `COMMAND_TIMEOUT` **Config (src/config.js)** - Loads `.env` file synchronously on startup - Merges with `process.env` (env vars take precedence) - Provides type-safe getters: `get()`, `getBoolean()`, `getNumber()` - Exported as singleton **Logger (src/logger.js)** - Console and file-based logger with ANSI colors and timestamps - Methods: `info()`, `error()`, `webhook()`, `warn()`, `debug()`, `jobLifecycle()` - **File logging support** with automatic rotation: - Enabled by default when running as systemd service - Configurable via `FILE_LOGGING_ENABLED` environment variable - Default log file: `/var/log/agent-manager.log` - Automatic rotation when file reaches `MAX_LOG_SIZE` (default: 10MB) - Keeps up to `MAX_LOG_FILES` rotated files (default: 5) - ANSI color codes stripped from file output - Exported as singleton **EmailNotifier (src/emailNotifier.js)** - Email notification callback system - Loads `.smtp-config.json` for SMTP server configuration - Loads `email-rules.json` for notification rules - Registers callbacks for events defined in rules - Evaluates conditions (mention, branch, action, user, repository) - Substitutes variables in email subjects and bodies - Resolves recipient variables ({{mentioned_user}}, {{sender}}, {{assignee}}) - Exported as singleton **EmailSender (src/emailSender.js)** - SMTP email sender using Node.js built-in `net` and `tls` modules - Zero external dependencies - Supports STARTTLS and direct TLS connections - Implements SMTP authentication (AUTH LOGIN) - Builds RFC 5322 compliant email messages - Supports both plain text and HTML emails - Exported as class (instantiated by EmailNotifier) ### Application Flow 1. `index.js` loads config and creates WebhookServer 2. JobQueue loads persisted queue state from `queue.json` (if exists) 3. EmailNotifier loads SMTP config and email rules 4. Gets enabled events from config (`WEBHOOK_PUSH_ENABLED=true`, etc.) 5. Registers callbacks for enabled events (webhooks → enqueue jobs) 6. Registers email notifier callbacks for events in rules 7. Starts server on configured host:port 8. On webhook: parses body → triggers callbacks → enqueues job + sends emails → responds immediately 9. JobQueue processes jobs: - **Single-queue mode**: All jobs execute sequentially (one at a time, FIFO) - **Multi-queue mode**: Jobs from different repositories execute in parallel, each repository maintains FIFO order 10. Each job: extracts variables → substitutes in command → executes → logs results 11. Email notifier evaluates conditions and sends emails for matching rules 12. Graceful shutdown on SIGTERM/SIGINT: saves queue state and stops server ## Development Commands ```bash npm start # Start production server npm run dev # Start with auto-reload (--watch flag) node examples/with-callbacks.js # Run callback example ``` ## System Management (Production) The agent-manager service runs as a systemd service on the production system. **Restart the service:** ```bash sudo /bin/systemctl restart agent-manager ``` **Check service status:** ```bash sudo /bin/systemctl status agent-manager ``` **View service logs:** ```bash sudo journalctl -u agent-manager -f ``` **View application log file:** ```bash tail -f /var/log/agent-manager.log ``` **Debugging**: When running as a systemd service, file logging is enabled by default. All application logs (INFO, WARN, ERROR, DEBUG, webhook events, job lifecycle) are written to `/var/log/agent-manager.log`. The log file is automatically rotated when it reaches 10MB (default), keeping up to 5 rotated files. For troubleshooting issues, check this file for detailed execution logs. **Note**: The `claude` user has passwordless sudo access to restart the agent-manager service via `/etc/sudoers.d/50-agent-manager` configuration. **Testing endpoints:** ```bash curl http://localhost:3000/health curl -X POST http://localhost:3000/webhook \ -H "Content-Type: application/json" \ -H "X-Gogs-Event: push" \ -H "X-Gogs-Delivery: 12345" \ -d '{"ref":"refs/heads/main","repository":{"name":"test-repo","full_name":"user/test-repo"},"pusher":{"username":"testuser"},"commits":[{"id":"abc123","message":"Test commit"}]}' ``` ## Configuration Configuration is split between two files: ### 1. Environment Configuration (`.env`) Copy from `.env.example` and configure: **Server Configuration:** ```env HOST=0.0.0.0 # Network interface (0.0.0.0=all, 127.0.0.1=localhost only) PORT=3000 # Port number WEBHOOK_PATH=/webhook # Webhook endpoint path WEBHOOK_SECRET= # Optional webhook verification secret ``` **Event Enable/Disable:** ```env WEBHOOK_{EVENT_TYPE}_ENABLED=true|false ``` **Supported Event Types:** push, pull_request, create, delete, release, issues, issue_comment, global **File Logging Configuration:** ```env FILE_LOGGING_ENABLED=true|false # Enable/disable file logging (default: false, auto-enabled for systemd) LOG_FILE_PATH=/var/log/agent-manager.log # Path to log file (default: /var/log/agent-manager.log) MAX_LOG_SIZE=10485760 # Max size in bytes before rotation (default: 10MB) MAX_LOG_FILES=5 # Number of rotated files to keep (default: 5) ``` **Note**: File logging is **automatically enabled** when running as a systemd service. The logger detects systemd environment and enables file output by default. For debugging, check `/var/log/agent-manager.log`. **MCP Server Configuration:** ```env GOGS_MCP_PATH=/data/gogs-mcp/dist/index.js # Path to gogs-mcp server for headless Claude Code ``` The `GOGS_MCP_PATH` environment variable configures the location of the gogs-mcp server used by Claude Code in headless mode for issue automation. The handle-issue.sh script dynamically generates the MCP configuration file (.mcp-gogs.json) using this path at runtime, making it environment-specific. ### 2. Command Configuration (`commands.json`) Copy from `commands.json.example` and configure commands for each event type. **Command Structure:** ```json { "commands": { "event_type": [ { "name": "command-identifier", "description": "Human-readable description", "type": "shell" | "node", "command": "path/to/command", "args": ["arg1", "{{variable}}", "arg3"], "cwd": "/working/directory" | null, "timeout": 300000, "filterBranch": "branch-name" | null, "filterActions": ["action1", "action2"] | null, "filterAssignee": "username" | null } ] } } ``` **Command Types:** - `shell` - Execute bash shell command/script - `node` - Execute Node.js script (automatically prepends `node` command) **Per-Command Settings:** - `name` - Unique identifier for the command - `description` - Human-readable description - `type` - Execution type (shell or node) - `command` - Command or script path - `args` - Array of arguments (supports variable substitution) - `cwd` - Working directory (null = project root) - `timeout` - Timeout in milliseconds - `filterBranch` - Only execute for specific branch (null = all branches) - `filterActions` - Only execute for specific actions (array, optional). Examples: `["assigned", "opened"]`, `["synchronize"]`. If omitted or empty, execute for all actions - `filterAssignee` - Only execute when issue/PR is assigned to specific user (string, optional). If omitted, execute regardless of assignee ## Important Patterns 1. **ES6 Modules**: All files use `import/export` syntax (`"type": "module"` in package.json) 2. **Singleton Exports**: Config, Logger, CommandExecutor, EmailNotifier are singletons (created on import) 3. **Zero Dependencies**: Pure Node.js - no npm packages required beyond Node.js built-ins (including SMTP implementation) 4. **Graceful Shutdown**: Both SIGTERM and SIGINT trigger `server.stop()` before exit 5. **Configuration Files**: - `.env` for enable/disable flags - `commands.json` for command definitions - `.smtp-config.json` for SMTP server configuration - `email-rules.json` for email notification rules 6. **Backward Compatibility**: Legacy .env command format still supported (deprecated) 7. **Error Handling**: Try-catch blocks with logging; errors don't stop server 8. **Command Filtering**: Optional per-command filtering via: - `filterBranch` - Execute only for specific branches - `filterActions` - Execute only for specific actions (e.g., `["assigned", "opened"]`) - `filterAssignee` - Execute only when assigned to specific user (e.g., `"claude"`) 9. **Multiple Commands**: Each event type can have multiple commands that execute sequentially 10. **Shell & Node Execution**: Commands can be shell scripts or Node.js scripts 11. **Email Notifications**: Optional callback-based email notifications with condition matching 12. **File Logging**: Automatic file logging with rotation (enabled by default for systemd services) ## Extending with Callbacks The server supports custom callback registration beyond .env command execution: ```javascript import { WebhookServer } from './src/server.js'; const server = new WebhookServer({ port: 3000 }); const handler = server.getHandler(); // Event-specific callback handler.on('push', async (payload, headers) => { console.log('Push to:', payload.repository.name); // Custom logic here }); // Global callback (all events) handler.onAny(async (eventType, payload, headers) => { console.log('Event:', eventType); // Send to analytics, logging, etc. }); server.start(); ``` See `examples/with-callbacks.js` for complete example. ## Variable Substitution System Commands in `commands.json` and legacy `.env` commands support variable substitution using `{{variable}}` syntax. **Available variables:** - `{{event}}` - Event type (push, create, delete, etc.) - `{{repo}}` - Repository name only - `{{full_repo}}` - User/repository format - `{{repo_owner}}` - Repository owner username - `{{branch}}` - Branch name (extracted from refs) - `{{pusher}}` - Username of person who triggered event - `{{commit}}` - Short commit hash (push events) - `{{commit_msg}}` - Last commit message (push events) - `{{pr_number}}` - Pull request number (PR events) - `{{pr_action}}` - PR action (opened, closed, etc.) - `{{tag}}` - Tag name (create/delete tag events) - `{{ref_type}}` - "branch" or "tag" - `{{issue_number}}` - Issue number (issue events) - `{{issue_title}}` - Issue title (issue events) - `{{issue_action}}` - Issue action (opened, closed, reopened, etc.) - `{{issue_body}}` - Issue description/body (issue events) - `{{issue_assignee}}` - Username of person assigned to issue (issue events) - `{{issue_labels}}` - Comma-separated list of issue label names (issue/issue_comment events) - `{{comment_body}}` - Comment text (issue_comment events) Variables are extracted in `commandExecutor.js` via `extractVariables()` and substituted via `substituteVariables()`. ## Job Queue System The webhook server uses a persistent job queue to ensure reliable command execution. The queue system supports two operating modes: ### Operating Modes **Single-Queue Mode (Default)** - One global FIFO queue for all repositories - Jobs execute sequentially, one at a time - Prevents conflicts and resource contention - Recommended for single-repository setups or when strict sequential execution is required **Multi-Queue Mode (Parallel Per Repository)** - Separate FIFO queue for each Gogs repository - Jobs from different repositories execute in parallel - Each repository maintains FIFO order internally - Recommended for multi-repository setups where independent execution is desired - Enable with: `QUEUE_PARALLEL_PER_REPOSITORY=true` ### Features - **FIFO Processing**: Jobs are executed in the order they are received (per repository in multi-queue mode) - **Immediate Response**: Webhooks are accepted and queued instantly, ensuring no webhook delivery timeouts - **Persistence**: Queue state is saved to `queue.json` after every change - **Crash Recovery**: Pending jobs are automatically restored and processed after server restart - **Safe Restart**: Jobs marked as "running" during shutdown are reset to "pending" on restart - **Automatic Cleanup**: Empty repository queues are automatically removed (multi-queue mode) ### Queue File Structure **Single-Queue Mode:** ```json { "queue": [ { "id": 1, "eventType": "push", "status": "pending", "createdAt": "2025-10-28T12:00:00.000Z", "config": { "command": "...", "enabled": true } } ], "jobIdCounter": 5, "lastSaved": "2025-10-28T12:05:00.000Z" } ``` **Multi-Queue Mode:** ```json { "queues": { "owner/repo1": { "queue": [ { "id": 1, "eventType": "push", "status": "pending", "createdAt": "2025-10-28T12:00:00.000Z", "config": { "command": "...", "enabled": true } } ] }, "owner/repo2": { "queue": [ { "id": 2, "eventType": "issues", "status": "running", "createdAt": "2025-10-28T12:01:00.000Z", "config": { "command": "...", "enabled": true } } ] } }, "jobIdCounter": 5, "lastSaved": "2025-10-28T12:05:00.000Z" } ``` ### Job Lifecycle 1. **Created**: Webhook received → Job created with status "pending" 2. **Queued**: Job added to queue and persisted to disk 3. **Running**: Job status updated to "running" before execution starts 4. **Completed/Failed**: Job finishes → Status updated → Job removed from queue 5. **Next**: Queue processes next pending job (if any) ### Benefits **Single-Queue Mode:** - **No Lost Jobs**: Server crashes won't lose pending webhook events - **No Concurrent Execution**: Prevents race conditions in deployment scripts - **High Availability**: Can accept webhooks even while executing long-running commands - **Predictable Order**: Strict sequential execution across all repositories **Multi-Queue Mode:** - **All benefits of single-queue mode, plus:** - **Parallel Execution**: Jobs from different repositories run independently - **Better Resource Usage**: Multiple repositories can be processed simultaneously - **Reduced Wait Time**: One slow repository won't block others - **Repository Isolation**: Issues in one repository don't affect others ## Email Notification System The webhook server includes an email notification system that sends emails based on webhook events and configurable conditions. The system follows the same callback pattern as the Claude agent client. ### Architecture **EmailNotifier (src/emailNotifier.js)** - Callback-based notification system (similar to Claude agent callback) - Loads configuration from `.smtp-config.json` and `email-rules.json` - Registers callbacks with WebhookHandler for events defined in rules - Evaluates conditions and sends emails for matching events - Supports variable substitution in email subjects and bodies - Resolves recipient variables to email addresses **EmailSender (src/emailSender.js)** - Pure Node.js SMTP implementation using `net` and `tls` modules - Zero external dependencies (maintains project philosophy) - Supports STARTTLS (port 587) and direct TLS (port 465) - Implements SMTP AUTH LOGIN authentication - Builds RFC 5322 compliant email messages - Supports both plain text and HTML emails ### Configuration Files **`.smtp-config.json`** - SMTP server configuration (excluded from git): ```json { "host": "mail.example.com", "port": 1025, "secure": false, "user": "your-email@example.com", "pass": "your-password", "from": "noreply@example.com" } ``` **`email-rules.json`** - Email notification rules (excluded from git): ```json { "rules": [ { "name": "mention-notification", "event": "issue_comment", "condition": { "type": "mention", "value": "username" }, "recipients": ["{{mentioned_user}}"], "subject": "You were mentioned in {{full_repo}}#{{issue_number}}", "body": "Comment by {{sender}}:\n{{comment_body}}" } ] } ``` ### Condition Types - **mention** - Check if a user is mentioned in the payload (issue, PR, or comment body) - **branch** - Check if event is for a specific branch - **action** - Check if webhook action matches (e.g., "opened", "assigned") - **user** - Check if sender/pusher matches - **repository** - Check if repository full name matches ### Recipient Variables - `{{mentioned_user}}` - User mentioned in comment/issue (extracts email from payload) - `{{sender}}` - Event sender email - `{{assignee}}` - Issue/PR assignee email - Direct email addresses (e.g., `"admin@example.com"`) ### Email Variable Substitution All standard webhook variables are available in email subjects and bodies: - Repository: `{{repo}}`, `{{full_repo}}`, `{{repo_owner}}` - Git: `{{branch}}`, `{{commit}}`, `{{commit_msg}}` - User: `{{sender}}`, `{{mentioned_user}}` - Issue/PR: `{{issue_number}}`, `{{issue_title}}`, `{{issue_action}}`, `{{pr_number}}`, `{{pr_action}}` - Content: `{{comment_body}}`, `{{issue_body}}` ### Integration The email notifier is automatically integrated in `src/index.js`: ```javascript import emailNotifier from './emailNotifier.js'; // ... after handler setup ... emailNotifier.register(handler); ``` If `.smtp-config.json` is missing, email notifications are disabled automatically. ### How It Works 1. **Startup**: EmailNotifier loads SMTP config and email rules 2. **Registration**: Registers callbacks for all events defined in rules 3. **Event Trigger**: When a matching webhook arrives, callback is executed 4. **Condition Check**: Rule condition is evaluated against payload 5. **Variable Substitution**: Variables in subject/body are replaced with actual values 6. **Recipient Resolution**: Recipient variables are converted to email addresses 7. **Email Send**: Email is sent via SMTP using EmailSender 8. **Error Handling**: Errors are logged but don't stop webhook processing ### Security - Configuration files are excluded from git (`.gitignore`) - Credentials stored in local files only - File permissions should be restricted (600) - No external dependencies (no npm package vulnerabilities) ## TypeScript Client for Issue Automation The `client/` directory contains a TypeScript-based Claude Agent SDK client that provides AI-powered issue automation: **Location**: `client/` **Key Files**: - `src/claude-client.ts` - Main Claude Agent SDK client - `src/config.ts` - Configuration management - `src/repository-manager.ts` - Git repository operations (clone, pull, environment setup) - `src/gogs-helper.ts` - Gogs API helper functions - `src/types.ts` - TypeScript type definitions - `src/index.ts` - CLI entry point - `dist/` - Compiled JavaScript (auto-generated by `npm run build`) **Usage**: ```bash cd client npm install npm run build ``` **Integration**: The TypeScript client is called directly from `commands.json` for `issues` and `issue_comment` events. No shell script wrapper is needed. **Features**: - Integrates with gogs-mcp MCP server for Gogs API access - Processes issues assigned to Claude - Responds to issue comments automatically - Environment-specific configuration via `.env` - **Label-aware behavior** - Adjusts behavior based on issue labels (question, documentation, bug, enhancement) - **Per-repository MCP configuration support** - Different repositories can use different MCP servers - **Dynamic tool discovery** - Automatically discovers available tools from all configured MCP servers - **Automatic repository management** - Clones repositories on demand and pulls latest changes - **Environment setup** - Handles HOME, PATH, and XDG directory configuration - **Git URL from Gogs API** - Fetches SSH URL dynamically from Gogs instead of hardcoding ### Label-Aware Behavior The Claude agent automatically adjusts its behavior based on issue labels: **🔴 "question" label:** - DO NOT implement any code changes - Provide information, guidance, and answers only - May read code to understand context, but won't modify files - Focus on helpful explanations and clarifications **📚 "documentation" label:** - Focus on creating/updating documentation files - Acceptable changes: README.md, CLAUDE.md, other .md files, code comments - Avoid implementation changes unless needed for documentation examples **🐛 "bug" label:** - Focus on identifying and fixing the root cause - Implement minimal changes necessary to resolve the bug - Add tests when appropriate to prevent regression **✨ "enhancement" label:** - May implement code changes as requested - Follow existing code patterns and architecture - Consider backward compatibility Labels are automatically extracted from webhook payloads and passed to the client as the `{{issue_labels}}` variable. See `client/README.md` for detailed documentation. ### MCP Configuration System The client supports flexible MCP server configuration with three priority levels: 1. **Repository-specific config**: `~/.config/agent-manager/mcp/{owner}/{repo}.json` 2. **Global default config**: `~/.config/agent-manager/mcp/default.json` 3. **Hardcoded fallback**: Built-in gogs-mcp configuration (automatic) **Key Features**: - Multiple MCP servers per repository - Environment variable substitution using `${VAR_NAME}` syntax - Secure storage with restricted file permissions (600) - **Automatic tool discovery** - Tools from custom MCP servers are automatically available to Claude - No need to manually configure allowed tools **Configuration Structure**: ```json { "mcpServers": { "gogs": { "type": "stdio", "command": "node", "args": ["/data/gogs-mcp/dist/index.js"], "env": {} }, "custom-mcp": { "type": "stdio", "command": "node", "args": ["/path/to/custom-mcp/dist/index.js"], "env": { "API_KEY": "${MY_API_KEY}" } } } } ``` **Security**: Configuration files are stored outside repositories in `~/.config/agent-manager/mcp/` with restricted permissions to protect secrets like API keys and JWT tokens. See `examples/MCP-CONFIG.md` for detailed configuration guide and examples. ### Dynamic Tool Discovery The client automatically discovers available tools from all configured MCP servers at runtime: **How It Works**: 1. When the client starts, it loads the MCP configuration for the repository 2. The `ToolDiscovery` class connects to each configured MCP server 3. It queries each server using the MCP `tools/list` method 4. Tool names are formatted as `mcp__{server-name}__{tool-name}` 5. Built-in Claude Code tools (Read, Write, Edit, Bash, etc.) are added automatically 6. The complete list is passed to the Claude Agent SDK as `allowedTools` **Benefits**: - **No manual configuration** - Add a custom MCP server and its tools are immediately available - **Repository-specific tools** - Different repos can have different tool sets based on their MCP config - **Always up-to-date** - Tool list reflects the actual capabilities of configured servers **Built-in Tools** (always available): - `Read`, `Write`, `Edit` - File operations - `Bash`, `Grep`, `Glob` - Shell operations - `TodoWrite`, `Task` - Task management - `WebFetch`, `WebSearch` - Web access - `NotebookEdit`, `BashOutput`, `KillShell` - Advanced operations - `Skill`, `SlashCommand` - Extension points **Example**: If you configure a custom MCP server named "github" with tools "create_pr" and "list_repos", they will automatically be available as `mcp__github__create_pr` and `mcp__github__list_repos`. ### Environment Variable Configuration The client supports a two-tier environment variable configuration system to eliminate the need for duplicating `.env` files in every cloned repository: **Configuration Priority**: 1. **Global configuration**: `~/.config/agent-manager/.env` (shared across all repositories) 2. **Repository-specific configuration**: `.env` in repository root (overrides global) **How It Works**: 1. When the client starts, it first loads `Config.loadGlobal()` to read `~/.config/agent-manager/.env` 2. Then it loads `Config.load(repoPath)` to read the repository-specific `.env` (if it exists) 3. Repository-specific values override global values 4. If a repository doesn't have a `.env` file, it uses only global values **Required Environment Variables**: - `GOGS_ACCESS_TOKEN` or `GOGS_TOKEN` - Gogs API authentication token - `GOGS_BASE_URL` - Gogs server URL (e.g., `https://git.smartbotics.ai`) - `GOGS_MCP_PATH` - Path to gogs-mcp server (defaults to `/data/gogs-mcp/dist/index.js`) **Setup Global Configuration**: ```bash mkdir -p ~/.config/agent-manager cp examples/global-env.example ~/.config/agent-manager/.env chmod 600 ~/.config/agent-manager/.env # Edit the file and add your actual values ``` **Benefits**: - **No duplication** - Set `GOGS_ACCESS_TOKEN` once in global config instead of in every repository - **Flexibility** - Repositories can override global values when needed - **Backward compatible** - Existing repository `.env` files continue to work - **Secure** - Credentials stored in `~/.config/agent-manager/` with restricted permissions See `examples/global-env.example` for a complete example configuration file. ## Code Modification Guidelines **Main Server (src/)**: - Maintain zero-dependency philosophy (use only Node.js built-ins) - Follow singleton pattern for utilities (Config, Logger, CommandExecutor) - Use async/await for all async operations - Add error handling with try-catch and logger.error() - Update `.env.example` when adding new configuration options - Update README.md when adding new features - Follow existing naming conventions: camelCase for variables/functions, PascalCase for classes - Export singletons with `export default new ClassName()` - Keep modules focused on single responsibility ## Testing No formal test suite currently. Test manually using: 1. Start server with `npm run dev` 2. Use curl to send webhook payloads 3. Check console logs for execution results 4. Verify commands execute with expected variable substitution You always have to push the changes into the git reposiroty. Before pushing changes, you have to test the project with building and running it. Take care when running the project to use an unused port if the project already running. Never keep running instance from the project ## Issue handling - always update the issue which you works on - always add label to the issue if no label added - always write comment with the current status of the work - when you create commit message, mention the issue if exists - close the issue only when: - the user explicitly requests it, OR - all work is completed and no further user interaction is needed - never close an issue if you (Claude) need to ask the user for more information or if work is incomplete