clauderelay

package module
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 13, 2025 License: MIT Imports: 22 Imported by: 0

README

Claude Relay - WebSocket Server

A Go-based WebSocket server that provides an isolated Claude Code CLI environment with portable Bun installation.

Features

  • Isolated Claude Installation: Uses a portable Bun installation and isolated Claude instance in the project directory
  • Auto-Setup: Automatically downloads and installs Bun and Claude on first run
  • Authentication Handling: Guides you through Claude authentication when needed
  • Conversation Context: Maintains conversation history using Claude's --print mode
  • Undo/Restore: Per-message undo functionality with restore capability
  • Progress Indicators: Shows thinking indicators while Claude processes requests
  • Web UI: Clean, modern web interface with 120-character width support
  • Session Isolation: Each connection gets its own temporary directory
  • Automatic Cleanup: Processes and directories cleaned up on disconnect

Installation

go mod download
go build -o claude-relay .

First Time Setup

On first run, the application will:

  1. Download and install portable Bun in .bun/
  2. Install Claude Code CLI locally
  3. Guide you through Claude authentication

Run the setup:

./claude-relay -port 8081

When Claude starts and shows the interactive prompt, type /login and follow the authentication instructions.

Usage

After initial setup, simply run:

./claude-relay -port 8081

Then open client.html in your browser or navigate to http://localhost:8081

Directory Structure

After setup, the following directories will be created:

  • .bun/ - Portable Bun installation
  • .claude-home/ - Isolated Claude configuration and authentication
  • claude-relay-*/ - Temporary directories for Claude workspace (created at runtime)

Architecture

  • main.go: Entry point, setup initialization, and HTTP server
  • setup.go: Handles Bun and Claude installation, authentication
  • websocket.go: WebSocket connection and message handling
  • process.go: Claude process management and conversation state
  • config.go: Configuration management
  • client.html: Modern web UI with undo/restore functionality

Features in Detail

Isolated Environment
  • No system-wide installation required
  • All dependencies contained in project directory
  • Separate Claude home directory for configuration
  • Uses custom HOME environment for Claude
Conversation Management
  • Maintains conversation history across messages
  • Uses Claude's --print mode with context injection
  • Automatic history pruning (keeps last 10 exchanges)
Undo/Restore System
  • Click the ↶ button on any message to undo from that point
  • Removes the selected message and all subsequent ones
  • Restore button appears after undo operations
  • Can restore the last undone conversation
Progress Indicators
  • "Claude is thinking..." initial indicator
  • Progressive status updates during processing
  • Visual pulse animation in the UI

Configuration

Default port is 8080, customize with:

./claude-relay -port 3000

WebSocket Protocol

Client to Server
  • Plain text messages sent to Claude
  • /undo-from:N - Undo from message index N
  • /restore - Restore last undone conversation
Server to Client
  • Claude responses as plain text
  • Progress indicators (with emoji prefixes)
  • UNDO_SUCCESS:N - Undo confirmation
  • RESTORE_SUCCESS:[...] - Restore with message array
  • Error messages prefixed with ❌

Troubleshooting

Authentication Issues
rm -rf .claude-home
./claude-relay -port 8081
# Complete login when prompted
Port Already in Use
lsof -ti :8081 | xargs kill -9
Reset Everything
rm -rf .bun .claude-home
./claude-relay -port 8081
Check Installation
ls -la .bun/bin/       # Should show bun and claude
ls -la .claude-home/   # Should show .config/claude/

Development

Building
go build -o claude-relay .
Testing
# Run server
./claude-relay -port 8081

# In another terminal
python test_ws.py
Helper Scripts
  • setup.sh - Interactive setup script
  • test.sh - Run tests
  • Makefile - Build and run commands

Security Notes

  • Each session isolated in temporary directory
  • Processes killed and cleaned up on disconnect
  • Authentication stored in isolated .claude-home
  • CORS configured to accept any origin (customize as needed)
  • Uses --dangerously-skip-permissions flag for Claude

Requirements

  • Go 1.19 or higher
  • Internet connection for initial setup
  • macOS or Linux (Windows not tested)

License

[Your License Here]

Documentation

Overview

Package clauderelay provides a WebSocket relay server for Claude Code CLI with isolated, portable installation support.

This package enables you to programmatically create and manage multiple isolated Claude instances, each with its own configuration and authentication, without requiring system-wide installation.

Features

• Isolated Installation: Each instance has its own Bun and Claude installation • Multiple Instances: Run multiple Claude relays on different ports • Auto-Setup: Automatically downloads and installs dependencies • Authentication Management: Handle Claude authentication programmatically • WebSocket Interface: Connect to Claude via WebSocket protocol • Conversation State: Maintains context across messages • Undo/Restore: Support for undoing and restoring conversations

Basic Usage

Create a simple relay server:

import "github.com/rizrmd/claude-relay"

func main() {
	// Create relay with automatic setup
	relay, err := clauderelay.New(clauderelay.Options{
		Port:          "8081",
		BaseDir:       "./claude-instance",
		AutoSetup:     true,
		EnableLogging: true,
	})
	if err != nil {
		log.Fatal(err)
	}
	defer relay.Close()

	// Check authentication
	if authenticated, _ := relay.IsAuthenticated(); !authenticated {
		if err := relay.Authenticate(); err != nil {
			log.Fatal(err)
		}
	}

	// Start the server
	if err := relay.Start(); err != nil {
		log.Fatal(err)
	}

	// Wait for shutdown
	select {}
}

Multiple Instances

Run multiple isolated Claude instances:

// Create instance 1
relay1, _ := clauderelay.New(clauderelay.Options{
	Port:    "8081",
	BaseDir: "./claude-1",
})
relay1.Start()

// Create instance 2
relay2, _ := clauderelay.New(clauderelay.Options{
	Port:    "8082",
	BaseDir: "./claude-2",
})
relay2.Start()

Embedding in Existing Server

Integrate Claude relay into your existing HTTP server:

relay, _ := clauderelay.New(clauderelay.Options{
	Port: "8081",
})
relay.Start()

// Use relay.GetWebSocketURL() to get the WebSocket endpoint
// Integrate with your existing routes

WebSocket Protocol

The relay server exposes a WebSocket endpoint at /ws that accepts text messages and returns Claude's responses.

Client to Server: • Plain text messages to send to Claude • /undo-from:N - Undo from message index N • /restore - Restore last undone conversation

Server to Client: • Claude's responses as plain text • Progress indicators during processing • Error messages prefixed with ❌

Directory Structure

Each Claude instance creates the following structure in its BaseDir:

BaseDir/
├── .bun/                  // Portable Bun installation
│   └── bin/
│       ├── bun            // Bun executable
│       └── claude         // Claude CLI
├── .claude-home/          // Claude configuration
│   └── .config/
│       └── claude/
│           └── auth.json  // Authentication token
└── claude-relay-*/        // Temporary workspaces (runtime)

Authentication

Claude requires authentication on first use. The library provides multiple authentication methods to suit different deployment scenarios:

## Authentication Methods

### 1. SetAuthToken() - Non-Interactive/Programmatic

Use when you already have an API key. Perfect for servers, containers, and headless environments.

apiKey := os.Getenv("CLAUDE_API_KEY")
err := relay.SetAuthToken(apiKey)

### 2. Authenticate() - Interactive/Terminal-Based

Use when running in a terminal with user interaction. Launches Claude's interactive login.

err := relay.Authenticate()
// User will see Claude interface, choose theme, type /login

### 3. Options.APIKey - Automatic Authentication

Provide API key during initialization for automatic setup.

relay, _ := clauderelay.New(clauderelay.Options{
	APIKey: "sk-ant-...",  // Automatically authenticates
})

### 4. Options.AuthCallback - Custom Authentication Flow

Implement your own authentication UI/flow.

relay, _ := clauderelay.New(clauderelay.Options{
	AuthCallback: func(authURL string) (string, error) {
		// Show authURL to user in your UI
		// Return API key from your source
		return getUserAPIKey(authURL), nil
	},
})

## When to Use Each Method

SetAuthToken() - Use when: • Running as a web service or API • Deploying in Docker containers • Running in CI/CD pipelines • Serverless functions (AWS Lambda, etc.) • Background jobs or workers • Any environment without terminal access

Authenticate() - Use when: • Building CLI tools • Local development with terminal • One-time setup scripts • User manages their own authentication

## Checking Authentication Status

// Simple check
authenticated, err := relay.IsAuthenticated()

// Detailed status
authenticated, message, err := relay.GetAuthStatus()
// Returns: (true, "Authenticated", nil) or (false, "No authentication file found", nil)

## Getting the Auth URL

For custom authentication flows:

url, _ := relay.GetAuthURL()
// Returns: "https://console.anthropic.com/settings/keys"

Thread Safety

The Relay type is thread-safe and can be safely accessed from multiple goroutines. Multiple relay instances can run concurrently without interference.

Package clauderelay provides a WebSocket relay server for Claude Code CLI with isolated, portable installation support.

This package allows you to create multiple isolated Claude instances, each with its own configuration and authentication, without requiring system-wide installation.

Basic usage:

import "github.com/yourusername/claude-relay"

// Create a new relay server
relay, err := clauderelay.New(clauderelay.Options{
	Port:           "8081",
	BaseDir:        "./claude-instance-1",
	AutoSetup:      true,
})
if err != nil {
	log.Fatal(err)
}
defer relay.Close()

// Start the server
if err := relay.Start(); err != nil {
	log.Fatal(err)
}

Package clauderelay provides isolated Claude Code CLI instances.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func PromptUser

func PromptUser(prompt string) string

Types

type ClaudeProcess

type ClaudeProcess struct {
	// contains filtered or unexported fields
}

func NewClaudeProcess

func NewClaudeProcess(setup *ClaudeSetup) (*ClaudeProcess, error)

func (*ClaudeProcess) CanRestore

func (p *ClaudeProcess) CanRestore() bool

func (*ClaudeProcess) CanUndo

func (p *ClaudeProcess) CanUndo() bool

func (*ClaudeProcess) Cleanup

func (p *ClaudeProcess) Cleanup() error

func (*ClaudeProcess) GetLastExchange

func (p *ClaudeProcess) GetLastExchange() (string, string, error)

func (*ClaudeProcess) GetRestoredMessagesForClient

func (p *ClaudeProcess) GetRestoredMessagesForClient() []map[string]string

func (*ClaudeProcess) GetWorkingDirectory

func (p *ClaudeProcess) GetWorkingDirectory() string

func (*ClaudeProcess) IsRunning

func (p *ClaudeProcess) IsRunning() bool

func (*ClaudeProcess) Kill

func (p *ClaudeProcess) Kill() error

func (*ClaudeProcess) ReadFile

func (p *ClaudeProcess) ReadFile(filename string) ([]byte, error)

func (*ClaudeProcess) RestoreLastUndo

func (p *ClaudeProcess) RestoreLastUndo() ([]string, error)

func (*ClaudeProcess) SaveFile

func (p *ClaudeProcess) SaveFile(filename string, content []byte) error

func (*ClaudeProcess) SaveState

func (p *ClaudeProcess) SaveState()

func (*ClaudeProcess) SendMessage

func (p *ClaudeProcess) SendMessage(message string) (string, error)

func (*ClaudeProcess) SendMessageWithProgress

func (p *ClaudeProcess) SendMessageWithProgress(message string, progressChan chan<- []byte, doneChan <-chan bool) (string, error)

func (*ClaudeProcess) UndoLastExchange

func (p *ClaudeProcess) UndoLastExchange() error

func (*ClaudeProcess) UndoToIndex

func (p *ClaudeProcess) UndoToIndex(messageIndex int) error

type ClaudeProcessV2

type ClaudeProcessV2 struct {
	// contains filtered or unexported fields
}

func NewClaudeProcessV2

func NewClaudeProcessV2() (*ClaudeProcessV2, error)

func (*ClaudeProcessV2) Cleanup

func (p *ClaudeProcessV2) Cleanup() error

func (*ClaudeProcessV2) GetWorkingDirectory

func (p *ClaudeProcessV2) GetWorkingDirectory() string

func (*ClaudeProcessV2) ReadFile

func (p *ClaudeProcessV2) ReadFile(filename string) ([]byte, error)

func (*ClaudeProcessV2) SaveFile

func (p *ClaudeProcessV2) SaveFile(filename string, content []byte) error

func (*ClaudeProcessV2) SendMessage

func (p *ClaudeProcessV2) SendMessage(message string) (string, error)

type ClaudeSetup

type ClaudeSetup struct {
	// contains filtered or unexported fields
}

func NewClaudeSetup

func NewClaudeSetup() (*ClaudeSetup, error)

func NewClaudeSetupWithBaseDir

func NewClaudeSetupWithBaseDir(baseDir string) (*ClaudeSetup, error)

NewClaudeSetupWithBaseDir creates a ClaudeSetup with a custom base directory. This allows multiple isolated Claude instances.

func (*ClaudeSetup) CheckAuthentication

func (cs *ClaudeSetup) CheckAuthentication() (bool, error)

func (*ClaudeSetup) GetAuthStatus

func (cs *ClaudeSetup) GetAuthStatus() (bool, string, error)

GetAuthStatus returns detailed authentication status

func (*ClaudeSetup) GetAuthURL

func (cs *ClaudeSetup) GetAuthURL() (string, error)

GetAuthURL returns the Claude authentication URL for non-interactive login

func (*ClaudeSetup) GetClaudeEnv

func (cs *ClaudeSetup) GetClaudeEnv() []string

func (*ClaudeSetup) GetClaudeHome

func (cs *ClaudeSetup) GetClaudeHome() string

func (*ClaudeSetup) GetClaudePath

func (cs *ClaudeSetup) GetClaudePath() string

func (*ClaudeSetup) HandleAuthenticationPrompt

func (cs *ClaudeSetup) HandleAuthenticationPrompt(process *ClaudeProcess) error

func (*ClaudeSetup) InstallBun

func (cs *ClaudeSetup) InstallBun() error

func (*ClaudeSetup) InstallClaude

func (cs *ClaudeSetup) InstallClaude() error

func (*ClaudeSetup) IsAuthenticationNeeded

func (cs *ClaudeSetup) IsAuthenticationNeeded(output string) bool

func (*ClaudeSetup) IsInstalled

func (cs *ClaudeSetup) IsInstalled() bool

func (*ClaudeSetup) RunClaudeLogin

func (cs *ClaudeSetup) RunClaudeLogin() error

func (*ClaudeSetup) SetAuthToken

func (cs *ClaudeSetup) SetAuthToken(apiKey string) error

SetAuthToken sets the authentication token programmatically

func (*ClaudeSetup) Setup

func (cs *ClaudeSetup) Setup() error

func (*ClaudeSetup) SetupClaudeHome

func (cs *ClaudeSetup) SetupClaudeHome() error

func (*ClaudeSetup) StartNonInteractiveAuth

func (cs *ClaudeSetup) StartNonInteractiveAuth() (string, chan error)

StartNonInteractiveAuth initiates non-interactive authentication It returns the auth URL and a channel that will receive the result

func (*ClaudeSetup) WaitForAuth

func (cs *ClaudeSetup) WaitForAuth(timeout time.Duration) error

WaitForAuth waits for authentication to be completed This can be used after SetAuthToken to verify authentication worked

type Config

type Config struct {
	Port         string            `json:"port"`
	ClaudePath   string            `json:"claude_path"`
	MaxProcesses int               `json:"max_processes"`
	Endpoints    map[string]string `json:"endpoints,omitempty"`
	AllowOrigins []string          `json:"allow_origins,omitempty"`
	TempDirBase  string            `json:"temp_dir_base,omitempty"`
}

func DefaultConfig

func DefaultConfig() *Config

func LoadConfig

func LoadConfig(path string) (*Config, error)

func (*Config) Save

func (c *Config) Save(path string) error

type Connection

type Connection struct {
	// contains filtered or unexported fields
}

type ConnectionV2

type ConnectionV2 struct {
	// contains filtered or unexported fields
}

type ConversationState

type ConversationState struct {
	History   []string
	Timestamp time.Time
}

type Options

type Options struct {
	// Port is the port number for the WebSocket server.
	// Default: "8080"
	Port string

	// BaseDir is the directory where Claude and Bun will be installed.
	// Each instance should have a unique BaseDir for isolation.
	// Default: current directory
	BaseDir string

	// AutoSetup automatically installs Claude and Bun if not present.
	// Default: true
	AutoSetup bool

	// MaxProcesses limits the number of concurrent Claude processes.
	// Default: 100
	MaxProcesses int

	// EnableLogging enables detailed logging output.
	// Default: true
	EnableLogging bool

	// CustomClaudePath allows specifying a custom Claude executable path.
	// If empty, uses the isolated installation.
	CustomClaudePath string

	// APIKey provides the Claude API key for non-interactive authentication.
	// If provided, the library will automatically authenticate without user interaction.
	APIKey string

	// AuthCallback is called when authentication is needed.
	// If provided, this will be used instead of interactive authentication.
	// The callback receives the auth URL and should return the API key.
	AuthCallback func(authURL string) (string, error)
}

Options configures the Claude relay server.

type Relay

type Relay struct {
	// contains filtered or unexported fields
}

Relay represents a Claude WebSocket relay server instance.

func New

func New(opts Options) (*Relay, error)

New creates a new Claude relay instance with the given options.

Example:

relay, err := clauderelay.New(clauderelay.Options{
	Port:    "8081",
	BaseDir: "./my-claude",
})

func (*Relay) Authenticate

func (r *Relay) Authenticate() error

Authenticate runs the Claude authentication process interactively. This should be called when IsAuthenticated returns false.

Example:

authenticated, _ := relay.IsAuthenticated()
if !authenticated {
	fmt.Println("Please complete Claude authentication:")
	if err := relay.Authenticate(); err != nil {
		log.Fatal(err)
	}
}

func (*Relay) AuthenticateWithCallback

func (r *Relay) AuthenticateWithCallback(callback func(authURL string) (string, error)) error

AuthenticateWithCallback performs non-interactive authentication using a callback. The callback receives the auth URL and should return the API key.

Example:

err := relay.AuthenticateWithCallback(func(authURL string) (string, error) {
	fmt.Printf("Please visit: %s\n", authURL)
	fmt.Print("Enter your API key: ")
	var apiKey string
	fmt.Scanln(&apiKey)
	return apiKey, nil
})

func (*Relay) Close

func (r *Relay) Close() error

Close stops the server and cleans up resources. It's safe to call Close multiple times.

Example:

defer relay.Close()

func (*Relay) GetAuthStatus

func (r *Relay) GetAuthStatus() (bool, string, error)

GetAuthStatus returns detailed authentication status. Returns: (isAuthenticated, statusMessage, error)

func (*Relay) GetAuthURL

func (r *Relay) GetAuthURL() (string, error)

GetAuthURL returns the URL where users can get their API key. Use this for non-interactive authentication flows.

Example:

url, _ := relay.GetAuthURL()
fmt.Printf("Get your API key at: %s\n", url)
// ... show URL to user ...
relay.SetAuthToken(apiKey)

func (*Relay) GetBaseDir

func (r *Relay) GetBaseDir() string

GetBaseDir returns the base directory for this Claude instance.

func (*Relay) GetPort

func (r *Relay) GetPort() string

GetPort returns the port the server is configured to use.

func (*Relay) GetServer

func (r *Relay) GetServer() *Server

GetServer returns the underlying Server instance for advanced usage.

func (*Relay) GetSetup

func (r *Relay) GetSetup() *ClaudeSetup

GetSetup returns the underlying ClaudeSetup instance for advanced usage.

func (*Relay) GetWebSocketURL

func (r *Relay) GetWebSocketURL() string

GetWebSocketURL returns the WebSocket URL for connecting to this relay.

func (*Relay) IsAuthenticated

func (r *Relay) IsAuthenticated() (bool, error)

IsAuthenticated checks if Claude is authenticated.

func (*Relay) IsInstalled

func (r *Relay) IsInstalled() bool

IsInstalled checks if Claude and Bun are installed.

func (*Relay) IsRunning

func (r *Relay) IsRunning() bool

IsRunning returns true if the relay server is currently running.

func (*Relay) SetAuthToken

func (r *Relay) SetAuthToken(apiKey string) error

SetAuthToken sets the API key for non-interactive authentication. This method allows programmatic authentication without terminal access.

Example:

// Get API key from environment, database, or user input
apiKey := os.Getenv("CLAUDE_API_KEY")
if err := relay.SetAuthToken(apiKey); err != nil {
	log.Fatal(err)
}

func (*Relay) Setup

func (r *Relay) Setup() error

Setup installs Claude and Bun if not already installed. This is called automatically if AutoSetup is true.

func (*Relay) Start

func (r *Relay) Start() error

Start starts the WebSocket relay server. The server runs in the background and returns immediately.

Example:

if err := relay.Start(); err != nil {
	log.Fatal(err)
}

func (*Relay) Stop

func (r *Relay) Stop() error

Stop gracefully stops the relay server.

Example:

if err := relay.Stop(); err != nil {
	log.Printf("Error stopping relay: %v", err)
}

func (*Relay) Wait

func (r *Relay) Wait()

Wait blocks until the server is stopped.

Example:

go relay.Start()
relay.Wait() // Block until stopped

type Server

type Server struct {
	// contains filtered or unexported fields
}

func NewServer

func NewServer(config *Config, setup *ClaudeSetup) *Server

func (*Server) HandleWebSocket

func (s *Server) HandleWebSocket(w http.ResponseWriter, r *http.Request)

type ServerV2

type ServerV2 struct {
	// contains filtered or unexported fields
}

func NewServerV2

func NewServerV2(config *Config) *ServerV2

func (*ServerV2) HandleWebSocketV2

func (s *ServerV2) HandleWebSocketV2(w http.ResponseWriter, r *http.Request)

Directories

Path Synopsis
cmd
claude-relay command
Command claude-relay starts a WebSocket relay server for Claude Code CLI.
Command claude-relay starts a WebSocket relay server for Claude Code CLI.
examples
auth-apikey command
Example of non-interactive authentication using API key.
Example of non-interactive authentication using API key.
auth-callback command
Example of non-interactive authentication using a callback function.
Example of non-interactive authentication using a callback function.
basic command
Basic example of using the clauderelay library.
Basic example of using the clauderelay library.
embedded command
Example of embedding Claude relay in an existing HTTP server.
Example of embedding Claude relay in an existing HTTP server.
multiple command
Example of running multiple isolated Claude instances.
Example of running multiple isolated Claude instances.
webservice command
Example of using Claude relay in a web service with non-interactive authentication.
Example of using Claude relay in a web service with non-interactive authentication.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL