npm package: @magnet-ai/magnet-agent-server
Description: A WebSocket server companion to Magnet AI (https://www.magnet.run) that proxies requests to Claude Code API
Author: Nicolae Rusan
Latest version: 0.1.17

# Magnet Agent Server

A WebSocket server that proxies requests to the Claude Code API, enabling real-time communication with Claude's code assistance capabilities.

## Features

- 🔌 WebSocket-based communication with Claude Code API
- 🚀 Built with Bun for high performance
- ⚡ Real-time streaming of Claude responses
- 🌳 Git worktree isolation for safe code changes
- 💾 Saved Sessions - persistent session storage and retrieval
- 🛑 Session management with abort capabilities
- 📊 Git diff functionality for worktree changes
- 🌿 Automatic branch creation from worktree changes
- 🔍 Issue-based session tracking
- 📈 Session continuation support
- 🏥 Health check endpoint
- 📦 Easy CLI installation and usage

## Requirements

- [Bun](https://bun.sh/) >= 1.0.0
- Anthropic API key
- TypeScript >= 5.0

## Installation

### Global Installation

```bash
bun install -g @magnet-ai/magnet-agent-server
```

### Development Installation

```bash
git clone <repository-url>
cd magnet-agent-server
bun install
```

## Usage

### Starting the Server

```bash
# Using global installation
magnet-agent-server

# With custom port
magnet-agent-server --port 8080
magnet-agent-server -p 3000

# Show help
magnet-agent-server --help

# Show version
magnet-agent-server --version

# Using npm scripts
bun run dev          # Development mode
bun run start        # Production mode

# Custom port via environment variable
MAGNET_SERVER_PORT=8080 bun run dev
```

The server will start on `http://localhost:3002` by default.

### CLI Options

- `-p, --port <number>` - Port to run the server on (default: 3002)
- `-h, --help` - Show help message
- `-v, --version` - Show version number

### Environment Variables

- `MAGNET_SERVER_PORT` - Server port (default: 3002, overridden by CLI --port option)
- `ANTHROPIC_API_KEY` - Can be set globally or passed per request

## Git Worktree Isolation

By default, magnet-agent-server creates an isolated git worktree for each Claude Code session. This provides several benefits:

### Benefits

- **🛡️ Safety**: Changes are made in an isolated copy, protecting your main working directory
- **🔬 Experimentation**: Try different approaches without affecting your current work
- **📋 Review**: Examine all changes before deciding to integrate them
- **🚫 No Conflicts**: Avoid merge conflicts with your ongoing work
- **🌿 Branch Creation**: Automatically create branches from completed changes

### How It Works

1. When a Claude Code session starts, the server checks if your `projectRoot` is a git repository
2. If yes, it creates a new worktree in the system temp directory with pattern `magnet-{issueId}--{timestamp}`
3. The worktree contains all your current code and uses the same branch as a detached HEAD
4. Claude Code operates in this isolated environment
5. When the session ends, you can optionally create a branch from the changes
6. The worktree is automatically cleaned up after session completion

### Configuration

**Enable/Disable Options:**
- **Default**: Enabled for all git repositories
- **Per-Session**: Include `"useWorktree": false` in WebSocket prompt message

### Example Worktree Structure
```
System Temp Directory:
├── magnet-abc123--1703123456789/  # Session 1 worktree
│   ├── src/                       # Copy of your code
│   └── package.json               # Changes made here
└── magnet-def456--1703123567890/  # Session 2 worktree (if overlapping)
```

## Session Management

### Issue-Based Tracking

Each Claude Code session is tracked using an `issueId` parameter, which provides:

- **🔍 Unique Identification**: Each session has a distinct identifier
- **📊 Better Tracking**: Monitor multiple concurrent sessions
- **🔄 Session Continuation**: Continue previous sessions with the same issueId
- **🌿 Branch Management**: Create branches tied to specific issues

### Session Continuation

The server supports continuing previous Claude Code sessions within the same WebSocket connection:

- **First Prompt**: Creates a new session with the given issueId
- **Subsequent Prompts**: Continues the existing session, maintaining context and history
- **Max Turns**: Configurable limit to prevent infinite loops (default: 50)
- **Connection-Based**: Sessions only persist within the same WebSocket connection
- **Memory-Based**: Sessions are lost when the connection closes or server restarts

## Saved Sessions

The server now supports persistent session storage, allowing you to retrieve session history even after the connection is closed or the server restarts.

### How It Works

1. **Automatic Logging**: All Claude Code messages are automatically saved to disk during the session
2. **File Storage**: Sessions are stored as JSON files in a `sessions/` directory
3. **Persistent**: Sessions persist across server restarts and connection drops
4. **Metadata**: Each session includes metadata like issueId, branchName, creation time, and status

### Session Lifecycle

- **Created**: When a new session starts, a session file is created with metadata
- **Active**: Messages are logged in real-time as they stream through the WebSocket
- **Completed**: Session is marked as completed when the Claude Code session ends normally
- **Aborted**: Session is marked as aborted when manually terminated or on error

### Storage Format

Sessions are stored in `sessions/{issueId}.json` with the following structure:

```json
{
  "metadata": {
    "issueId": "issue-123",
    "branchName": "feature/fix-bug",
    "createdAt": "2024-01-01T00:00:00Z",
    "lastUpdatedAt": "2024-01-01T00:30:00Z",
    "status": "completed"
  },
  "messages": [
    {
      "timestamp": "2024-01-01T00:00:00Z",
      "type": "claude_message",
      "data": { /* Full Claude Code message */ }
    }
  ]
}
```

### Benefits

- **📚 Session History**: Review past interactions with Claude Code
- **🔄 Resume Context**: Understand what was discussed in previous sessions
- **🐛 Debugging**: Trace issues across multiple sessions
- **📊 Analytics**: Track usage patterns and session outcomes
- **🔗 Integration**: Build workflows that depend on session history

## Git Diff Functionality

When using git worktree isolation, you can request diffs to see what changes Claude has made to your code. This is useful for:

- **🔍 Reviewing Changes**: See exactly what Claude modified before integrating changes
- **📋 Code Review**: Get a clear diff view of all modifications
- **🧪 Testing**: Understand the scope of changes before applying them
- **📊 Progress Tracking**: Monitor changes as Claude works on your project

### How to Request Diffs

Send a `get_diff` message to the WebSocket connection:

```json
{
  "type": "get_diff",
  "issueId": "your-issue-id",
  "includeUntracked": true
}
```

**Parameters:**
- `issueId` (required): The issue ID of the active session
- `includeUntracked` (optional, default: `true`): Whether to include untracked files in the response

### Diff Response Format

```json
{
  "type": "diff",
  "diff": "git diff output...",
  "stats": {
    "filesChanged": 3,
    "insertions": 45,
    "deletions": 12
  },
  "hasChanges": true,
  "untrackedFiles": ["new-file.js", "temp.txt"],
  "worktreePath": "/tmp/magnet-abc123--1703123456789",
  "originalBranch": "main",
  "issueId": "abc123"
}
```

**Response Fields:**
- `diff`: The complete git diff output showing all changes
- `stats`: Summary statistics of the changes
- `hasChanges`: Boolean indicating if any changes were detected
- `untrackedFiles`: Array of newly created files not yet tracked by git
- `worktreePath`: Path to the isolated worktree directory
- `originalBranch`: The original branch the worktree was created from
- `issueId`: The issue ID for the session

### Branch Creation

When a session completes with changes, the server can automatically create a branch:

- **Automatic Detection**: Checks for changes in the worktree
- **Smart Naming**: Creates branches with pattern `magnet-{issueId}--{timestamp}`
- **Commit Creation**: Creates a commit with all changes in the worktree
- **Branch Creation**: Creates the branch in the original repository
- **Optional**: Only creates branches if there are actual changes

## API Reference

### WebSocket Endpoint

Connect to: `ws://localhost:3002/claude-code`

### Message Formats

#### Sending a Prompt

```json
{
  "type": "prompt",
  "content": "Your prompt here",
  "projectRoot": "/path/to/your/project",
  "anthropicApiKey": "your-api-key-here",
  "issueId": "unique-issue-identifier",
  "useWorktree": true,
  "maxTurns": 50,
  "promptToAppend": "Additional system prompt"
}
```

**Parameters:**
- `content` (required): Your prompt/question for Claude
- `projectRoot` (required): Path to your project directory
- `anthropicApiKey` (required): Your Anthropic API key
- `issueId` (required): Unique identifier for this session/issue
- `useWorktree` (optional): Override server default for worktree usage
- `maxTurns` (optional): Maximum number of turns for this session (default: 50)
- `promptToAppend` (optional): Additional system prompt to append (first turn only)

#### Aborting a Session

```json
{
  "type": "abort",
  "issueId": "your-issue-id"
}
```

#### Requesting a Diff

```json
{
  "type": "get_diff",
  "issueId": "your-issue-id",
  "includeUntracked": true
}
```

**Parameters:**
- `issueId` (required): The issue ID of the active session
- `includeUntracked` (optional): Include untracked files in response (default: true)

### Response Types

#### Connection Established
```json
{
  "type": "connected",
  "message": "Connected to Claude Code server. Send your prompt to begin.",
  "issueId": "system"
}
```

#### Status Updates
```json
{
  "type": "status",
  "message": "Processing your request...",
  "issueId": "abc123"
}
```

#### Worktree Created
```json
{
  "type": "status",
  "message": "Created isolated git worktree: /tmp/magnet-abc123--1703123456789",
  "issueId": "abc123",
  "worktreePath": "/tmp/magnet-abc123--1703123456789",
  "originalProjectRoot": "/path/to/project"
}
```

#### Worktree Warning/Info
```json
{
  "type": "warning",
  "message": "Failed to create git worktree, using original project root: error details",
  "issueId": "abc123"
}
```

```json
{
  "type": "info",
  "message": "Git worktree isolation not available (not a git repository)",
  "issueId": "abc123"
}
```

#### Claude Messages
```json
{
  "type": "claude_message",
  "data": {
    "type": "assistant",
    "message": {
      "id": "msg_123",
      "content": [{"type": "text", "text": "Claude's response"}],
      "model": "claude-3-5-sonnet-20241022",
      "role": "assistant",
      "stop_reason": "end_turn",
      "usage": {
        "input_tokens": 100,
        "output_tokens": 200
      }
    },
    "session_id": "session-123"
  },
  "issueId": "abc123"
}
```

#### Session Complete
```json
{
  "type": "complete",
  "message": "Claude Code session completed",
  "issueId": "abc123",
  "worktreePath": "/tmp/magnet-abc123--1703123456789"
}
```

#### Session Aborted
```json
{
  "type": "aborted",
  "message": "Claude Code session aborted successfully",
  "issueId": "abc123"
}
```

#### Abort Failed
```json
{
  "type": "abort_failed",
  "message": "Failed to abort session: no active session found",
  "issueId": "abc123"
}
```

#### Diff Response
```json
{
  "type": "diff",
  "diff": "git diff output showing changes...",
  "stats": {
    "filesChanged": 3,
    "insertions": 45,
    "deletions": 12
  },
  "hasChanges": true,
  "untrackedFiles": ["new-file.js"],
  "worktreePath": "/tmp/magnet-abc123--1703123456789",
  "originalBranch": "main",
  "issueId": "abc123"
}
```

#### Errors
```json
{
  "type": "error",
  "message": "Error description",
  "issueId": "abc123"
}
```

### HTTP Endpoints

#### Health Check
```
GET /health
```

Response:
```json
{
  "status": "healthy",
  "timestamp": "2024-01-01T00:00:00.000Z"
}
```

#### Version
```
GET /version
```

Returns the server version and package name.

Response:
```json
{
  "version": "0.1.15",
  "name": "@magnet-ai/magnet-agent-server"
}
```

#### Root
```
GET /
```

Returns basic server information.

#### Get Saved Session
```
GET /session?issueId=<issueId>
```

Retrieves a saved session by issueId.

**Parameters:**
- `issueId` (required): The unique identifier of the session to retrieve

**Response:**
```json
{
  "metadata": {
    "issueId": "abc123",
    "branchName": "feature-branch",
    "createdAt": "2024-01-01T00:00:00.000Z",
    "lastUpdatedAt": "2024-01-01T00:30:00.000Z",
    "status": "completed"
  },
  "messages": [
    {
      "timestamp": "2024-01-01T00:00:00.000Z",
      "type": "claude_message",
      "data": {
        "type": "assistant",
        "message": {
          "content": [{"type": "text", "text": "Response from Claude"}],
          "role": "assistant"
        },
        "session_id": "session-123"
      }
    }
  ],
  "isCurrentlyActive": false
}
```

**Status Codes:**
- `200` - Session found and returned
- `404` - Session not found for the given issueId
- `400` - Missing or invalid issueId parameter
- `500` - Server error or session logger not initialized

## Client Example

### JavaScript/TypeScript Client

```typescript
const ws = new WebSocket('ws://localhost:3002/claude-code');

ws.onopen = () => {
  console.log('Connected to Magnet Agent Server');

  // Send a prompt
  ws.send(JSON.stringify({
    type: 'prompt',
    content: 'Help me write a function to calculate fibonacci numbers',
    projectRoot: '/path/to/my/project',
    anthropicApiKey: 'your-api-key',
    issueId: 'fibonacci-task-001',
    useWorktree: true,
    maxTurns: 25,
    promptToAppend: 'Please focus on performance and readability'
  }));
};

ws.onmessage = (event) => {
  const message = JSON.parse(event.data);

  switch (message.type) {
    case 'connected':
      console.log('Server ready');
      break;
    case 'claude_message':
      console.log(`[${message.issueId}] Claude response:`, message.data);
      break;
    case 'complete':
      console.log(`[${message.issueId}] Session completed`);
      if (message.worktreePath) {
        console.log(`Worktree: ${message.worktreePath}`);
      }
      break;
    case 'error':
      console.error(`[${message.issueId}] Error:`, message.message);
      break;
    case 'diff':
      console.log(`[${message.issueId}] Diff:`, message.diff);
      console.log(`Files changed: ${message.stats.filesChanged}`);
      break;
  }
};

// Request diff of current changes
const getDiff = (issueId: string) => {
  ws.send(JSON.stringify({
    type: 'get_diff',
    issueId: issueId,
    includeUntracked: true
  }));
};

// Abort current session
const abortSession = (issueId: string) => {
  ws.send(JSON.stringify({
    type: 'abort',
    issueId: issueId
  }));
};

// Continue a previous session (within the same WebSocket connection)
const continueSession = (issueId: string, newPrompt: string) => {
  ws.send(JSON.stringify({
    type: 'prompt',
    content: newPrompt,
    projectRoot: '/path/to/my/project',
    anthropicApiKey: 'your-api-key',
    issueId: issueId, // Same issueId continues the session
    useWorktree: true
  }));
};
```

### Test Client

The project includes a test client:

```bash
bun run test-client
bun run test-diff              # Test diff functionality
bun run test-worktree          # Test worktree functionality
bun run test-worktree-enabled  # Test with worktree enabled
bun run test-worktree-disabled # Test with worktree disabled
```

### Example Usage

The project includes an interactive diff example:

```bash
# Start the server
bun run dev

# In another terminal, run the diff example
ANTHROPIC_API_KEY=your_key bun examples/diff-example.ts /path/to/your/project
```

This example demonstrates:
- Starting a Claude Code session with worktree
- Making changes through Claude
- Requesting periodic diffs to see progress
- Displaying diff results in a readable format
- Creating branches from completed changes

## Session Management

- Multiple Claude Code sessions can be active simultaneously (one per issueId per connection)
- Sessions are automatically cleaned up when connections close
- Use the `abort` message type to cancel active sessions
- Each session gets a unique `issueId` for tracking and continuation
- Sessions can be continued by using the same `issueId` in subsequent prompts **within the same WebSocket connection**
- Sessions do not persist across WebSocket reconnections or server restarts
- Worktrees are isolated per session and automatically cleaned up

## Development

### Running Tests

```bash
bun test
```

### Health Check

```bash
bun run health
# or
curl http://localhost:3002/health
```

### Project Structure

```
magnet-agent-server/
├── bin/                    # CLI executable
├── lib/                    # Library modules
│   ├── git-worktree.ts    # Git worktree utilities
│   ├── ClaudeCodeSession.ts # Claude Code session management
│   └── types.ts           # Type definitions and schemas
├── index.ts               # Main server file
├── test-client.ts         # Test client
├── test-worktree.ts       # Worktree test client
├── package.json
└── README.md
```

## Error Handling

The server handles various error scenarios:

- **Invalid JSON**: Malformed message handling
- **Missing required fields**: Validation for prompt, API key, project root, and issueId
- **Session conflicts**: Manages multiple concurrent sessions per issueId per connection
- **API errors**: Forwards Claude Code API errors to clients
- **Connection drops**: Automatic cleanup of resources and worktrees
- **Worktree failures**: Graceful fallback to original project root
- **Branch creation errors**: Non-blocking failures with proper logging

## Security Considerations

- API keys are passed per request and not stored
- Sessions are isolated per issueId within each WebSocket connection
- Git worktrees provide additional isolation for code changes in system temp directory
- Automatic cleanup prevents resource leaks (including worktrees)
- Input validation for all incoming messages using Zod schemas
- Worktrees are created with restricted permissions
- Session continuation only works within the same WebSocket connection

## Performance Considerations

- **Worktree Cleanup**: Automatic cleanup of old worktrees older than 24 hours
- **Session Limits**: Configurable `maxTurns` to prevent infinite loops
- **Memory Management**: Proper cleanup of AbortControllers and sessions
- **Concurrent Sessions**: Support for multiple simultaneous sessions
- **Resource Isolation**: Each session operates in its own worktree

## Contributing

1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Add tests if applicable
5. Submit a pull request

## License

MIT

## Support

For issues and questions, please open an issue in the repository.