Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
315 changes: 4 additions & 311 deletions EXAMPLE_SKILL.md
Original file line number Diff line number Diff line change
@@ -1,317 +1,10 @@
---
name: coplan
description: "Upload, edit, and comment on plans via the CoPlan API. Use when asked to create plans, edit plan documents, manage edit leases, or leave review comments on plans."
description: "Create, edit, and review engineering design docs via the CoPlan API."
---

# CoPlan API
# CoPlan

Interact with the CoPlan app to create, read, edit, and comment on plan documents.
Read the full API instructions at: BASE_URL/agent-instructions

## Setup

Credentials are stored at `~/.config/planning-department/credentials.json`:

```json
{
"base_url": "http://localhost:3000",
"token": "your-token-here"
}
```

On first use:

1. Read `~/.config/planning-department/credentials.json` to get `token` and `base_url`.
2. If the file does not exist, tell the user: "Go to **Settings → API Tokens** in the CoPlan web UI to create a token." Ask for the token and base URL, then save to `~/.config/planning-department/credentials.json` with `chmod 600`.
3. If any API call returns 401, the token is invalid or revoked. Prompt the user to create a new token in Settings and update the credentials file.

Use the values from the credentials file in all API calls below. Read the file once at the start of the session:

```bash
PLANNING_BASE_URL=$(cat ~/.config/planning-department/credentials.json | jq -r '.base_url')
PLANNING_API_TOKEN=$(cat ~/.config/planning-department/credentials.json | jq -r '.token')
```

## API Reference

All requests use `Authorization: Bearer $PLANNING_API_TOKEN` header.

### List Plans

```bash
curl -s -H "Authorization: Bearer $PLANNING_API_TOKEN" \
"$PLANNING_BASE_URL/api/v1/plans" | jq .
```

Optional query param: `?status=considering`

### Get Plan

```bash
curl -s -H "Authorization: Bearer $PLANNING_API_TOKEN" \
"$PLANNING_BASE_URL/api/v1/plans/$PLAN_ID" | jq .
```

Returns: `id`, `title`, `status`, `current_content` (markdown), `current_revision`.

### Create Plan

```bash
curl -s -X POST \
-H "Authorization: Bearer $PLANNING_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"title": "My Plan", "content": "# My Plan\n\nContent here."}' \
"$PLANNING_BASE_URL/api/v1/plans" | jq .
```

### Update Plan

Update plan metadata (title, status, tags). Only fields included in the request body are changed.

```bash
curl -s -X PATCH \
-H "Authorization: Bearer $PLANNING_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"status": "considering"}' \
"$PLANNING_BASE_URL/api/v1/plans/$PLAN_ID" | jq .
```

Allowed fields: `title` (string), `status` (string — one of `brainstorm`, `considering`, `developing`, `live`, `abandoned`), `tags` (array of strings).

### Get Versions

```bash
curl -s -H "Authorization: Bearer $PLANNING_API_TOKEN" \
"$PLANNING_BASE_URL/api/v1/plans/$PLAN_ID/versions" | jq .
```

### Get Comments

```bash
curl -s -H "Authorization: Bearer $PLANNING_API_TOKEN" \
"$PLANNING_BASE_URL/api/v1/plans/$PLAN_ID/comments" | jq .
```

## Editing Plans (Lease + Operations)

Editing requires three steps: acquire lease → apply operations → release lease.

### 1. Acquire Edit Lease

```bash
LEASE_RESPONSE=$(curl -s -X POST \
-H "Authorization: Bearer $PLANNING_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"lease_token": "'$(openssl rand -hex 32)'"}' \
"$PLANNING_BASE_URL/api/v1/plans/$PLAN_ID/lease")
echo "$LEASE_RESPONSE" | jq .
LEASE_TOKEN=$(echo "$LEASE_RESPONSE" | jq -r '.lease_token')
```

Leases expire after 5 minutes. Renew with PATCH, release with DELETE.

### 2. Apply Operations

```bash
curl -s -X POST \
-H "Authorization: Bearer $PLANNING_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"lease_token": "'$LEASE_TOKEN'",
"base_revision": 1,
"change_summary": "Updated goals section",
"operations": [
{
"op": "replace_exact",
"old_text": "old text here",
"new_text": "new text here",
"count": 1
}
]
}' \
"$PLANNING_BASE_URL/api/v1/plans/$PLAN_ID/operations" | jq .
```

**Important:** Set `base_revision` to the plan's `current_revision`. Returns 409 if stale.

### 3. Release Edit Lease

```bash
curl -s -X DELETE \
-H "Authorization: Bearer $PLANNING_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"lease_token": "'$LEASE_TOKEN'"}' \
"$PLANNING_BASE_URL/api/v1/plans/$PLAN_ID/lease"
```

### Renew Lease (if needed for long edits)

```bash
curl -s -X PATCH \
-H "Authorization: Bearer $PLANNING_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"lease_token": "'$LEASE_TOKEN'"}' \
"$PLANNING_BASE_URL/api/v1/plans/$PLAN_ID/lease" | jq .
```

## Available Operations

### replace_exact

Find and replace exact text. Fails if text not found or count exceeded.

```json
{
"op": "replace_exact",
"old_text": "We should use MySQL",
"new_text": "We should use PostgreSQL",
"count": 1
}
```

### insert_under_heading

Insert content after a markdown heading. Fails if heading not found or ambiguous.

```json
{
"op": "insert_under_heading",
"heading": "## Testing Strategy",
"content": "- Add integration tests\n- Mock external providers"
}
```

### delete_paragraph_containing

Delete the paragraph containing a needle string. Fails if 0 or >1 paragraphs match.

```json
{
"op": "delete_paragraph_containing",
"needle": "This approach is deprecated"
}
```

## Commenting

### Create Comment Thread

```bash
curl -s -X POST \
-H "Authorization: Bearer $PLANNING_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"body_markdown": "This section needs more detail.",
"anchor_text": "the exact text you are commenting on",
"agent_name": "Amp"
}' \
"$PLANNING_BASE_URL/api/v1/plans/$PLAN_ID/comments" | jq .
```

- `anchor_text`: the exact text from the plan content that this comment is about. It will be highlighted in the UI and the comment will be anchored to it.
- Omit `anchor_text` for a general (non-anchored) comment.
- `agent_name`: optional identifier for the agent (e.g., `"Amp"`, `"Claude"`). Displayed in the UI as "User Name (Agent Name)".

### Reply to Thread

```bash
curl -s -X POST \
-H "Authorization: Bearer $PLANNING_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"body_markdown": "Good point, I will address this.", "agent_name": "Amp"}' \
"$PLANNING_BASE_URL/api/v1/plans/$PLAN_ID/comments/$THREAD_ID/reply" | jq .
```

### Resolve Thread

Mark a comment thread as resolved (addressed by the plan author or thread creator).

```bash
curl -s -X PATCH \
-H "Authorization: Bearer $PLANNING_API_TOKEN" \
"$PLANNING_BASE_URL/api/v1/plans/$PLAN_ID/comments/$THREAD_ID/resolve" | jq .
```

### Dismiss Thread

Dismiss a comment thread (plan author only — for comments that are out of scope or not applicable).

```bash
curl -s -X PATCH \
-H "Authorization: Bearer $PLANNING_API_TOKEN" \
"$PLANNING_BASE_URL/api/v1/plans/$PLAN_ID/comments/$THREAD_ID/dismiss" | jq .
```

## Reviewing a Plan

When asked to review a plan (given a plan URL or ID), follow this workflow:

### 1. Read the Plan and Comments

Fetch the plan content and all comment threads:

```bash
curl -s -H "Authorization: Bearer $PLANNING_API_TOKEN" \
"$PLANNING_BASE_URL/api/v1/plans/$PLAN_ID" | jq .

curl -s -H "Authorization: Bearer $PLANNING_API_TOKEN" \
"$PLANNING_BASE_URL/api/v1/plans/$PLAN_ID/comments" | jq .
```

### 2. Triage Comments

Review each open comment thread and categorize it:

- **Just do it** — Clear, actionable feedback that can be applied directly (typos, clarifications, missing details where the fix is obvious). Apply these edits without asking.
- **Confirm first** — Substantive changes where the right fix is clear but the scope is large enough to warrant confirmation. Summarize the proposed change and ask the user before applying.
- **Discuss** — Ambiguous feedback, disagreements, or comments that require a design decision. Present these to the user for discussion — do not attempt to resolve them.

### 3. Present Summary

Before making any changes, present a summary to the user:

> **Plan Review: [Title]**
>
> **Will apply (N comments):** [list each with a one-line summary of the change]
>
> **Need confirmation (N comments):** [list each with the proposed change]
>
> **For discussion (N comments):** [list each with the question/issue]
>
> Shall I proceed with the "just do it" edits?

### 4. Apply Edits

For approved changes:

1. Acquire an edit lease
2. Apply operations (use `replace_exact`, `insert_under_heading`, or `delete_paragraph_containing`)
3. Release the lease
4. Resolve each comment thread that was addressed

### 5. Resolve Threads

After applying edits, resolve the comment threads that were addressed:

```bash
curl -s -X PATCH \
-H "Authorization: Bearer $PLANNING_API_TOKEN" \
"$PLANNING_BASE_URL/api/v1/plans/$PLAN_ID/comments/$THREAD_ID/resolve" | jq .
```

## Typical Workflow

1. **Read** the plan: `GET /api/v1/plans/:id`
2. **Acquire lease**: `POST /api/v1/plans/:id/lease`
3. **Apply operations**: `POST /api/v1/plans/:id/operations` (can call multiple times while lease is held)
4. **Release lease**: `DELETE /api/v1/plans/:id/lease`
5. **Comment** on changes: `POST /api/v1/plans/:id/comments`

## Error Codes

| Code | Meaning |
|------|---------|
| 401 | Invalid or expired API token |
| 403 | Not authorized for this action |
| 404 | Plan not found (or no access) |
| 409 | Edit lease conflict or stale revision |
| 422 | Validation error or operation failed |
Replace `BASE_URL` with the CoPlan instance URL (e.g. `https://coplan.example.com`).
6 changes: 5 additions & 1 deletion app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class ApplicationController < ActionController::Base
before_action :authenticate_user!

helper CoPlan::ApplicationHelper
helper_method :current_user, :signed_in?
helper_method :current_user, :signed_in?, :show_api_tokens?

private

Expand All @@ -20,6 +20,10 @@ def signed_in?
current_user.present?
end

def show_api_tokens?
CoPlan.configuration.show_api_tokens?
end

def authenticate_user!
unless signed_in?
redirect_to main_app.sign_in_path, alert: "Please sign in."
Expand Down
19 changes: 8 additions & 11 deletions app/views/layouts/application.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,15 @@
CoPlan
<% end %>

<ul class="site-nav__links">
<% if signed_in? %>
<li><%= link_to "Plans", coplan.plans_path %></li>
<li><%= link_to "Settings", coplan.settings_tokens_path %></li>
<% end %>
<%# TODO: Phase 8.2 — Notifications bell %>
</ul>

<% if signed_in? %>
<div class="site-nav__user">
<span><%= current_user.name %></span>
<%= link_to "Sign out", sign_out_path, data: { turbo_method: :delete } %>
<div class="site-nav__right">
<%= link_to coplan.settings_root_path, class: "site-nav__icon-link", aria: { label: "Settings" } do %>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>
<% end %>
<div class="site-nav__user">
<span><%= current_user.name %></span>
<%= link_to "Sign out", sign_out_path, data: { turbo_method: :delete } %>
</div>
</div>
<% end %>
</div>
Expand Down
Loading
Loading