# JavaScript / TypeScript SDK

The Polyscope JavaScript SDK lets you manage servers, repositories, and workspaces programmatically. Use it to create workspaces from your own tools, CI pipelines, or Node.js applications.

## Installation

```bash
npm install @polyscope/sdk
```

## Authentication

You can manage your API tokens in the [Settings](/settings) section once logged in.

```typescript
import { Polyscope } from "@polyscope/sdk";

const polyscope = new Polyscope({ apiToken: "your-api-token" });
```

If no token is passed, the SDK automatically detects the token from your local Polyscope desktop application.

```typescript
const polyscope = new Polyscope();
```

## Servers

Retrieve all servers connected to your account:

```typescript
const servers = await polyscope.servers();

for (const server of servers) {
    console.log(server.name); // "My Mac"
    console.log(server.platform); // "darwin"
    console.log(server.online); // true
    console.log(server.version); // "0.11.0"
}
```

## Repositories

Retrieve all repositories, optionally filtered by server:

```typescript
// All repositories
const repositories = await polyscope.repositories();

// Repositories on a specific server
const repositories = await polyscope.repositories({ serverId: "srv-1" });
```

Each repository exposes properties like `name`, `path`, `baseBranch`, `hasRemote`, and `serverId`.

## Workspaces

### Listing Workspaces

```typescript
// All workspaces
const workspaces = await polyscope.workspaces();

// Filter by server, repository, or status
const workspaces = await polyscope.workspaces({
    serverId: "srv-1",
    repositoryId: "repo-1",
    status: "running",
});
```

### Creating a Workspace

```typescript
const workspace = await polyscope.createWorkspace({
    repositoryId: "repo-1",
    prompt: "Fix the login flow and add tests",
    serverId: "srv-1", // optional
});
```

You can also create a workspace directly from a repository resource — either with an options object or a prompt string shorthand:

```typescript
const repository = (await polyscope.repositories())[0];

const workspace = await repository.createWorkspace({
    prompt: "Add rate limiting to the API",
});

// Or use the shorthand
const workspace = await repository.createWorkspace(
    "Add rate limiting to the API"
);
```

### Creating from a GitHub Issue

```typescript
const workspace = await polyscope.createWorkspace({
    repositoryId: "repo-1",
    issueUrl: "https://github.com/org/repo/issues/42",
    serverId: "srv-1", // optional
});
```

### Creating from a Pull Request

```typescript
const workspace = await polyscope.createWorkspace({
    repositoryId: "repo-1",
    pullRequestUrl: "https://github.com/org/repo/pull/99",
    serverId: "srv-1", // optional
});
```

### Getting a Single Workspace

```typescript
const workspace = await polyscope.workspace("ws-1");

console.log(workspace.status); // "running", "idle", etc.
console.log(workspace.branch); // "brave-bunny"
console.log(workspace.previewUrl);
console.log(workspace.prUrl); // GitHub PR URL, if created
```

### Refreshing Workspace Data

```typescript
const workspace = await workspace.refresh();
```

### Deleting a Workspace

```typescript
await polyscope.deleteWorkspace("ws-1");

// Or from the resource directly
await workspace.delete();
```

## Workspace Messages

### Reading Messages

Messages support cursor-based pagination:

```typescript
const result = await workspace.messages();

for (const message of result.messages) {
    console.log(message.role); // "user" or "assistant"
    console.log(message.content);
}

// Paginate
if (result.hasMore) {
    const next = await workspace.messages(result.cursor);
}
```

### Sending a Message

```typescript
await workspace.prompt("Also add unit tests");

// Optionally specify a model
await workspace.prompt("Refactor the auth middleware", "claude-sonnet-4-6");
```

## Workspace Actions

### Viewing the Diff

```typescript
const diff = await workspace.diff();

console.log(diff.commitsAhead); // 3

// Local (uncommitted) changes
console.log(diff.local.diff);
console.log(diff.local.stats.filesChanged);
console.log(diff.local.stats.additions);
console.log(diff.local.stats.deletions);

for (const file of diff.local.files) {
    console.log(file.path); // "app/Http/Controllers/LoginController.php"
    console.log(file.additions); // 12
    console.log(file.deletions); // 3
}

// Base (committed) changes vs base branch
console.log(diff.base.diff);
```

### Committing Changes

```typescript
await workspace.commit();
```

### Creating a Pull Request

```typescript
await workspace.pullRequest({
    title: "Fix login flow",
    body: "Resolves #42",
    draft: false,
});
```

### Stopping a Workspace

```typescript
await workspace.stop();
```

## Error Handling

The SDK throws typed errors for common API responses:

| Error | Status | Description |
|---|---|---|
| `ValidationError` | 422 | Invalid input data |
| `AuthenticationError` | 401 | Invalid or missing API token |
| `ForbiddenError` | 403 | Insufficient permissions |
| `NotFoundError` | 404 | Resource not found |
| `RateLimitError` | 429 | Too many requests |
| `ServerOfflineError` | 503 | Target server is offline |
| `ServerTimeoutError` | 504 | Server did not respond in time |
| `ApiError` | — | Base class for all API errors |

All errors can be imported directly from `@polyscope/sdk`.

```typescript
import { ValidationError, ServerOfflineError } from "@polyscope/sdk";

try {
    const workspace = await polyscope.createWorkspace({
        repositoryId: "",
    });
} catch (error) {
    if (error instanceof ValidationError) {
        console.log(error.errors);
        // { repository_id: ['The repository id field is required.'] }
    } else if (error instanceof ServerOfflineError) {
        // The target server is not connected
    }
}
```

The `RateLimitError` includes a `rateLimitResetsAt` property (Unix timestamp) so you can wait and retry:

```typescript
import { RateLimitError } from "@polyscope/sdk";

try {
    await polyscope.createWorkspace(data);
} catch (error) {
    if (error instanceof RateLimitError) {
        const retryAfter = error.rateLimitResetsAt - Math.floor(Date.now() / 1000);
    }
}
```

## Full Example

Here's a complete example that creates a workspace from a GitHub issue:

```typescript
import { Polyscope, ServerOfflineError } from "@polyscope/sdk";

try {
    const polyscope = new Polyscope({ apiToken: "your-api-token" });

    // Find the repository
    const repositories = await polyscope.repositories();
    const repo = repositories.find((r) => r.name === "my-app");

    // Create a workspace from a GitHub issue
    const workspace = await repo.createWorkspace({
        prompt: "Fix the issue described below",
        issueUrl: "https://github.com/org/my-app/issues/42",
    });

    console.log(`Workspace created: ${workspace.id}`);
    console.log(`Branch: ${workspace.branch}`);
    console.log(`Preview: ${workspace.previewUrl}`);
} catch (error) {
    if (error instanceof ServerOfflineError) {
        console.log("Server is offline. Please start it and try again.");
    }
}
```
