# Laravel / PHP SDK

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

## Installation

```bash
composer require beyondcode/polyscope
```

## Authentication

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

### Standalone PHP

```php
use Polyscope\Laravel\Polyscope;

$polyscope = new Polyscope('your-api-token');
```

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

### Laravel

The SDK auto-registers a service provider. Add your credentials to `config/services.php`:

```php
'polyscope' => [
    'token' => env('POLYSCOPE_TOKEN'),
],
```

Then use the facade anywhere in your application:

```php
use Polyscope\Laravel\Facades\Polyscope;

$servers = Polyscope::servers();
```

## Servers

Retrieve all servers connected to your account:

```php
$servers = $polyscope->servers();

foreach ($servers as $server) {
    echo $server->name;     // "My Mac"
    echo $server->platform; // "darwin"
    echo $server->online;   // true
    echo $server->version;  // "0.11.0"
}
```

## Repositories

Retrieve all repositories, optionally filtered by server:

```php
// All repositories
$repositories = $polyscope->repositories();

// Repositories on a specific server
$repositories = $polyscope->repositories(serverId: 'srv-1');
```

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

## Workspaces

### Listing Workspaces

```php
// All workspaces
$workspaces = $polyscope->workspaces();

// Filter by server, repository, or status
$workspaces = $polyscope->workspaces([
    'server_id' => 'srv-1',
    'repository_id' => 'repo-1',
    'status' => 'running',
]);
```

### Creating a Workspace

```php
$workspace = $polyscope->createWorkspace([
    'repository_id' => 'repo-1',
    'prompt' => 'Fix the login flow and add tests',
    'server_id' => 'srv-1', // optional
]);
```

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

```php
$repository = $polyscope->repositories()[0];

$workspace = $repository->createWorkspace([
    'prompt' => 'Add rate limiting to the API',
]);

// Or use the shorthand
$workspace = $repository->createWorkspace('Add rate limiting to the API');
```

### Creating from a GitHub Issue

```php
$workspace = $polyscope->createWorkspaceFromIssue(
    repositoryId: 'repo-1',
    issueUrl: 'https://github.com/org/repo/issues/42',
    serverId: 'srv-1', // optional
);
```

### Creating from a Pull Request

```php
$workspace = $polyscope->createWorkspaceFromPullRequest(
    repositoryId: 'repo-1',
    pullRequestUrl: 'https://github.com/org/repo/pull/99',
    serverId: 'srv-1', // optional
);
```

### Getting a Single Workspace

```php
$workspace = $polyscope->workspace('ws-1');

echo $workspace->status;    // "running", "idle", etc.
echo $workspace->branch;    // "brave-bunny"
echo $workspace->previewUrl;
echo $workspace->prUrl;     // GitHub PR URL, if created
```

### Refreshing Workspace Data

```php
$workspace = $workspace->refresh();
```

### Deleting a Workspace

```php
$polyscope->deleteWorkspace('ws-1');

// Or from the resource directly
$workspace->delete();
```

## Workspace Messages

### Reading Messages

Messages support cursor-based pagination:

```php
$result = $polyscope->workspaceMessages('ws-1');
// Or from the workspace resource
$result = $workspace->messages();

foreach ($result->messages as $message) {
    echo $message->role;    // "user" or "assistant"
    echo $message->content;
}

// Paginate
if ($result->hasMore) {
    $next = $workspace->messages(after: $result->cursor);
}
```

### Sending a Message

```php
$polyscope->sendWorkspaceMessage('ws-1', 'Also add unit tests');

// Or from the workspace resource
$workspace->prompt('Also add unit tests');

// Optionally specify a model
$workspace->prompt('Refactor the auth middleware', model: 'claude-sonnet-4-6');
```

## Workspace Actions

### Viewing the Diff

```php
$diff = $polyscope->workspaceDiff('ws-1');
// Or from the workspace resource
$diff = $workspace->diff();

echo $diff->commitsAhead; // 3

// Local (uncommitted) changes
echo $diff->local->diff;
echo $diff->local->stats->filesChanged;
echo $diff->local->stats->additions;
echo $diff->local->stats->deletions;

foreach ($diff->local->files as $file) {
    echo $file->path;       // "app/Http/Controllers/LoginController.php"
    echo $file->additions;  // 12
    echo $file->deletions;  // 3
}

// Base (committed) changes vs base branch
echo $diff->base->diff;
```

### Committing Changes

```php
$polyscope->commitWorkspace('ws-1');

// Or from the workspace resource
$workspace->commit();
```

### Creating a Pull Request

```php
$polyscope->createWorkspacePullRequest(
    workspaceId: 'ws-1',
    title: 'Fix login flow',
    body: 'Resolves #42',
    draft: false,
);

// Or from the workspace resource
$workspace->pullRequest(
    title: 'Fix login flow',
    body: 'Resolves #42',
    draft: true,
);
```

### Stopping a Workspace

```php
$polyscope->stopWorkspace('ws-1');

// Or from the workspace resource
$workspace->stop();
```

## Error Handling

The SDK throws typed exceptions for common API errors:

| Exception | Status | Description |
|---|---|---|
| `ValidationException` | 422 | Invalid input data |
| `AuthenticationException` | 401 | Invalid or missing API token |
| `ForbiddenException` | 403 | Insufficient permissions |
| `NotFoundException` | 404 | Resource not found |
| `RateLimitExceededException` | 429 | Too many requests |
| `ServerOfflineException` | 503 | Target server is offline |
| `ServerTimeoutException` | 504 | Server did not respond in time |
| `ApiException` | — | Base class for all API errors |

All exceptions live in the `Polyscope\Laravel\Exceptions` namespace.

```php
use Polyscope\Laravel\Exceptions\ValidationException;
use Polyscope\Laravel\Exceptions\ServerOfflineException;

try {
    $workspace = $polyscope->createWorkspace([
        'prompt' => 'Missing repository_id',
    ]);
} catch (ValidationException $e) {
    $errors = $e->errors(); // ['repository_id' => ['The repository id field is required.']]
} catch (ServerOfflineException $e) {
    // The target server is not connected
}
```

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

```php
use Polyscope\Laravel\Exceptions\RateLimitExceededException;

try {
    $polyscope->createWorkspace($data);
} catch (RateLimitExceededException $e) {
    $retryAfter = $e->rateLimitResetsAt - time();
}
```

## Full Example

Here's a complete example that creates a workspace from a GitHub issue and waits for it to finish:

```php
use Polyscope\Laravel\Facades\Polyscope;
use Polyscope\Laravel\Exceptions\ServerOfflineException;

try {
    // Find the repository
    $repositories = Polyscope::repositories();
    $repo = collect($repositories)->firstWhere('name', 'my-app');

    // Create a workspace from a GitHub issue
    $workspace = $repo->createWorkspace([
        'prompt' => 'Fix the issue described below',
        'issue_url' => 'https://github.com/org/my-app/issues/42',
    ]);

    echo "Workspace created: {$workspace->id}\n";
    echo "Branch: {$workspace->branch}\n";
    echo "Preview: {$workspace->previewUrl}\n";
} catch (ServerOfflineException $e) {
    echo "Server is offline. Please start it and try again.\n";
}
```
