mirror of
https://github.com/backnotprop/plannotator.git
synced 2026-09-14 14:17:26 +08:00
7ee366d8a1
* fix(opencode): show the session URL on OpenCode 2's native command path
On OpenCode 2 a remote session's URL was invisible. runNativeCommand builds
its bridge client with createV2BridgeClient, which deliberately has no tui
domain, so toastPlannotatorUrl optional-chained to a no-op; both URL delivery
paths (the CLI stderr forwarder and the ready-file poller) route through it.
The V2 client's app.log is console.error, and OpenCode discards a server
plugin's stderr under both default launch modes (packages/cli/src/services/
standalone.ts uses stderr: "ignore" unless OPENCODE_PRINT_LOGS=1). Remote mode
also suppresses the browser, so /plannotator-review showed the user nothing at
all and presented as a hang.
Deliver the URL as a visible transcript notice instead. createSessionUrlNotifier
duck-types ctx.session.synthetic and exposes it to cli-bridge as notifyUrl, a
seam toastPlannotatorUrl prefers over the toast when present; OpenCode 1 clients
carry no notifyUrl and keep their real toast unchanged. The notice is posted
with resume: false, which upstream skips the wake for, so nothing starts a model
turn, and it carries the URL in both text and description because the TUI drops
a synthetic row whose description is empty and renders the description rather
than the text. Everything is guarded: a host without session.synthetic, or a
call with no session, gets no notifier and falls back to today's log-only
behavior, and a rejecting synthetic is caught and leaves the URL retryable by
the other delivery path.
The README's remedy line claimed remote sessions should read the URL from the
OpenCode log, which was never true; it now describes the transcript notice and
names OPENCODE_PRINT_LOGS=1 for older hosts.
Also fixes two bugs in the OpenCode 2 native-command smoke:
- scripts/opencode2-native-commands-smoke.sh looked for a node_modules/.bin/
opencode binary. @opencode-ai/cli publishes opencode2 on every dist-tag, so
the script failed before it started a server. It now tries both names and
reports which it looked for.
- The command-ownership check read /api/command once, immediately after
activation, racing the reclaim schedule whose last tick lands about 15.5s
later. Under PLANNOTATOR_SMOKE_EXPECT_NATIVE=1 that reported a shadowing bug
the reclaim had simply not reached yet. It now polls to a 30s deadline
(PLANNOTATOR_SMOKE_COMMAND_TIMEOUT_MS), still only after /api/plugin reports
the plugin loaded.
AI-assisted (Claude) under maintainer direction.
* fix(opencode): deliver the session URL on OpenCode 2's plan review path too
The first commit fixed only the native command path. The plan path builds its
own client (createV2Client, typed as { app: { agents, log } } with no notifier),
so a remote OpenCode 2 user who reached a review through submit_plan still never
saw the URL: no browser is opened for them and the plugin's console output is
discarded by the host.
The plan path now builds the same bridge client the command path uses, with
toolContext.sessionID, so it carries notifyUrl whenever the host exposes
session.synthetic. That covers both runtimes: the CLI runtime already prefers
notifyUrl inside toastPlannotatorUrl, and the embedded runtime's previously
empty logReady hook is now createPlanReadyNotifier.
That hook still does not log. app.log is console.error, the same stderr
handleServerReady already printed the URL to, so logging there would duplicate
the line in remote mode and add a stray one locally, which is why the hook was
empty. The transcript notice is a different surface, and it is the only one a
remote reviewer can see. Without session.synthetic the hook stays silent exactly
as before.
createV2Client is gone: it duplicated the bridge client's URL-deduped app.log
verbatim, and nothing else used it.
Three tests on the plan path (delivers the notice; stays silent and does not
re-log without synthetic; catches a rejecting notice) plus one that pins the two
wiring seams at source level, since the notifier tests all pass while the plan
path is wired to nothing, which is the shape the bug had.
Also from review: console.error is stubbed across the V2 URL delivery block, so
those tests no longer print URL lines into the suite output. The README bullet
now says the notice covers every way a session opens rather than slash commands
alone.
AI-assisted (Claude) under maintainer direction.
235 lines
11 KiB
Markdown
235 lines
11 KiB
Markdown
# @plannotator/opencode
|
|
|
|
**Annotate plans. Not in the terminal.**
|
|
|
|
Interactive Plan Review for OpenCode. Select the exact parts of the plan you want to change—mark for deletion, add a comment, or suggest a replacement. Feedback flows back to your agent automatically.
|
|
|
|
Obsidian users can auto-save approved plans to Obsidian as well. [See details](#obsidian-integration)
|
|
|
|
<table>
|
|
<tr>
|
|
<td align="center">
|
|
<strong>Watch Demo</strong><br><br>
|
|
<a href="https://youtu.be/_N7uo0EFI-U">
|
|
<img src="https://img.youtube.com/vi/_N7uo0EFI-U/maxresdefault.jpg" alt="Watch Demo" width="600" />
|
|
</a>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
|
|
## Install
|
|
|
|
### OpenCode 2 beta
|
|
|
|
Install OpenCode 2 from npm's `next` tag, then add Plannotator to the V2 `plugins` field:
|
|
|
|
```json
|
|
{
|
|
"$schema": "https://opencode.ai/config.json",
|
|
"plugins": [
|
|
{
|
|
"package": "@plannotator/opencode@latest",
|
|
"options": {
|
|
"workflow": "plan-agent",
|
|
"planningAgents": ["plan"]
|
|
}
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
Restart OpenCode 2 and verify that `plannotator` appears in `opencode2 plugin list`.
|
|
|
|
OpenCode 2 support is experimental while its plugin API is in beta. The core `submit_plan` review flow works everywhere. Two newer capabilities depend on which plugin API your OpenCode build ships with, and Plannotator detects both at runtime rather than requiring a particular channel:
|
|
|
|
- **Slash commands.** Native command execution landed upstream in `@opencode-ai/plugin` (anomalyco/opencode issue #2185, PR #44765) and currently ships on the `beta` and `dev` dist-tags; the `next` and `latest` tags still carry the older API. Capability is detected from the command draft OpenCode hands the plugin, not from the plugin API's shape: `ctx.command.transform` exists on both generations, and only the newer draft has `add`. On a host that has it, Plannotator registers `/plannotator-review`, `/plannotator-annotate`, and `/plannotator-last` itself and runs the same machinery OpenCode 1 uses, so your raw arguments reach the CLI unchanged and nothing is routed through the model. On an older host it registers nothing and the commands run from their markdown definitions, which ask the agent to run the `plannotator` CLI and relay its output; that path works but costs a model turn and depends on the agent following the instruction.
|
|
- **Command precedence.** OpenCode activates its own config-command loader after package plugins, and the last definition to claim a name wins, so the markdown stubs the installer writes to `~/.config/opencode/commands` would otherwise shadow the native definitions on every normal install. Plannotator re-registers the three names shortly after startup so its own definitions are the ones that run. If that reclaim cannot run, the stubs keep the names and the commands still work through the model-mediated fallback.
|
|
- **Agent switching.** `ctx.session.switchAgent` arrived with the same plugin API generation. On a host that exposes it, an agent switch chosen in the review UI is applied to the session. On an older host the plan is still approved and a warning is written to the server log; switch to `build` manually before implementation.
|
|
- **Abort signal.** V2 tool execution still exposes no abort signal. Cancelling a turn cannot stop a running review server or CLI child immediately.
|
|
- **Session URLs.** OpenCode 2 has a TUI plugin entry point, but it is separate from the server plugin Plannotator registers, so there is no toast to show and the plugin's own console output is discarded by the host unless you start it with `OPENCODE_PRINT_LOGS=1`. Instead, on a host whose plugin API exposes `session.synthetic`, Plannotator posts the URL into the session transcript as a `Plannotator session ready: <url>` notice, injected with `resume: false` so it appears without waking a model turn. This covers every way a session opens: the three slash commands and the `submit_plan` plan review, whether the review runs on the embedded runtime or the CLI. That is the link to open for a remote session, which gets no browser opened for it. On an older host without `session.synthetic` the URL only reaches that discarded console output, so run with `OPENCODE_PRINT_LOGS=1` there.
|
|
|
|
### OpenCode 1
|
|
|
|
Add to your `opencode.json`:
|
|
|
|
```json
|
|
{
|
|
"$schema": "https://opencode.ai/config.json",
|
|
"plugin": ["@plannotator/opencode@latest"]
|
|
}
|
|
```
|
|
|
|
Restart OpenCode. By default, the `submit_plan` tool is available to OpenCode's `plan` agent, not to `build` or other primary agents.
|
|
|
|
> **OpenCode 1 slash commands:** Run the install script to get `/plannotator-review`, `/plannotator-annotate`, and `/plannotator-last`:
|
|
> ```bash
|
|
> curl -fsSL https://plannotator.ai/install.sh | bash
|
|
> ```
|
|
> This also clears any cached plugin versions.
|
|
|
|
## Workflow Modes
|
|
|
|
The examples below use the OpenCode 1 config shape. OpenCode 2 places the same option keys under the plugin entry's `options` object shown above. In V2, `manual` registers no tool, so it leaves only the slash commands: useful on a host with native command execution, inactive on one without it.
|
|
|
|
- **`plan-agent`** (default): `submit_plan` is available to OpenCode's built-in `plan` agent plus any extra agents listed in `planningAgents`. This keeps Plannotator integrated with OpenCode plan mode without nudging `build` to call it.
|
|
- **`manual`**: `submit_plan` is not registered. Use `/plannotator-last`, `/plannotator-annotate`, and `/plannotator-review` when you want Plannotator.
|
|
- **`user-managed`**: `submit_plan` is registered but no prompts or agent permissions are modified. You manage which agents can call `submit_plan` via OpenCode's native agent configuration.
|
|
- **`all-agents`**: legacy broad behavior. Primary agents can see and call `submit_plan`.
|
|
|
|
Default config:
|
|
|
|
```json
|
|
{
|
|
"$schema": "https://opencode.ai/config.json",
|
|
"plugin": [
|
|
["@plannotator/opencode@latest", {
|
|
"workflow": "plan-agent",
|
|
"planningAgents": ["plan"]
|
|
}]
|
|
]
|
|
}
|
|
```
|
|
|
|
Runtime selection is automatic. In Bun-hosted OpenCode, Plannotator uses the embedded server bundled with the plugin. In Node-hosted or wrapped OpenCode environments, the plugin falls back to the installed `plannotator` CLI and sends the result back through OpenCode. You can force the fallback while debugging:
|
|
|
|
```json
|
|
{
|
|
"$schema": "https://opencode.ai/config.json",
|
|
"plugin": [
|
|
["@plannotator/opencode@latest", {
|
|
"runtime": "cli"
|
|
}]
|
|
]
|
|
}
|
|
```
|
|
|
|
If you use other OpenCode plugins, keep everything in one `plugin` array and attach Plannotator's options directly to the Plannotator entry:
|
|
|
|
```json
|
|
{
|
|
"$schema": "https://opencode.ai/config.json",
|
|
"plugin": [
|
|
["@plannotator/opencode@latest", {
|
|
"workflow": "plan-agent",
|
|
"planningAgents": ["plan", "sisyphus"]
|
|
}],
|
|
"@tarquinen/opencode-dcp@latest",
|
|
"octto",
|
|
"oh-my-opencode-slim"
|
|
]
|
|
}
|
|
```
|
|
|
|
Do not put `{ "workflow": "plan-agent" }` as its own item in the `plugin` array. OpenCode plugin entries must be either a plugin string or a two-item array like `[pluginName, options]`.
|
|
|
|
Restore the old broad behavior:
|
|
|
|
```json
|
|
{
|
|
"$schema": "https://opencode.ai/config.json",
|
|
"plugin": [
|
|
["@plannotator/opencode@latest", {
|
|
"workflow": "all-agents"
|
|
}]
|
|
]
|
|
}
|
|
```
|
|
|
|
Use commands only:
|
|
|
|
```json
|
|
{
|
|
"$schema": "https://opencode.ai/config.json",
|
|
"plugin": [
|
|
["@plannotator/opencode@latest", {
|
|
"workflow": "manual"
|
|
}]
|
|
]
|
|
}
|
|
```
|
|
|
|
Register the tool but manage prompts and permissions yourself:
|
|
|
|
```json
|
|
{
|
|
"$schema": "https://opencode.ai/config.json",
|
|
"plugin": [
|
|
["@plannotator/opencode@latest", {
|
|
"workflow": "user-managed"
|
|
}]
|
|
]
|
|
}
|
|
```
|
|
|
|
## How It Works
|
|
|
|
1. The configured planning agent calls `submit_plan` → Plannotator opens in your browser
|
|
2. Select text → annotate (delete, replace, comment)
|
|
3. **Approve** → Agent proceeds with implementation
|
|
4. **Request changes** → Annotations sent back as structured feedback
|
|
|
|
## Features
|
|
|
|
- **Visual annotations**: Select text, choose an action, see feedback in the sidebar
|
|
- **Local by default**: Plans, annotations, drafts, history, and configuration stay local. Every app load checks GitHub for updates without sending plan content, and there is currently no opt-out setting; URL annotation, hosted PR review, AI, sharing, and Workspaces use the network when selected.
|
|
- **Legacy link sharing**: Small markdown shares use compressed, unencrypted URL fragments. Larger and raw HTML shares can use client-encrypted short links. Workspaces is the primary direction for team sharing.
|
|
- **Plan Diff**: See what changed when the agent revises a plan after feedback
|
|
- **Annotate last message**: Run `/plannotator-last` to annotate the agent's most recent response
|
|
- **Annotate files, folders, and URLs**: Run `/plannotator-annotate` when you want manual review of an artifact
|
|
- **Obsidian integration**: Auto-save approved plans to your vault with frontmatter and tags
|
|
|
|
## Environment Variables
|
|
|
|
| Variable | Description |
|
|
|----------|-------------|
|
|
| `PLANNOTATOR_REMOTE` | Set to `1` / `true` for remote mode, `0` / `false` for local mode, or leave unset for SSH auto-detection. Uses a fixed port in remote mode; browser-opening behavior depends on the environment. |
|
|
| `PLANNOTATOR_PORT` | Fixed port to use. Default: random locally, `19432` for remote sessions. |
|
|
| `PLANNOTATOR_BROWSER` | Custom browser to open plans in. macOS: app name or path. Linux/Windows: executable path. |
|
|
| `PLANNOTATOR_SHARE_URL` | Custom share portal URL for self-hosting. Default: `https://share.plannotator.ai`. |
|
|
| `PLANNOTATOR_PASTE_URL` | Custom paste service URL for self-hosting. Default: `https://plannotator-paste.plannotator.workers.dev`. |
|
|
| `PLANNOTATOR_PLAN_TIMEOUT_SECONDS` | Timeout for `submit_plan` review wait. Default: `345600` (96h). Set `0` to disable timeout. |
|
|
| `PLANNOTATOR_BIN` | Override the CLI path used by the OpenCode plugin's CLI runtime fallback. Default: `plannotator` on `PATH`. |
|
|
|
|
## Devcontainer / Docker
|
|
|
|
Works in containerized environments. Set the env vars and forward the port:
|
|
|
|
```json
|
|
{
|
|
"containerEnv": {
|
|
"PLANNOTATOR_REMOTE": "1",
|
|
"PLANNOTATOR_PORT": "9999"
|
|
},
|
|
"forwardPorts": [9999]
|
|
}
|
|
```
|
|
|
|
If nothing opens automatically, open `http://localhost:9999` when `submit_plan` is called.
|
|
|
|
See [devcontainer.md](./devcontainer.md) for full setup details.
|
|
|
|
## Obsidian Integration
|
|
|
|
Save approved plans directly to your Obsidian vault.
|
|
|
|
1. Open Settings in Plannotator UI
|
|
2. Enable "Obsidian Integration" and select your vault
|
|
3. Approved plans save automatically with:
|
|
- Human-readable filenames: `Title - Jan 2, 2026 2-30pm.md`
|
|
- YAML frontmatter (`created`, `source`, `tags`)
|
|
- Auto-extracted tags from plan title and code languages
|
|
- Backlink to `[[Plannotator Plans]]` for graph view
|
|
|
|
<img width="1190" height="730" alt="image" src="https://github.com/user-attachments/assets/5036a3ea-e5e8-426c-882d-0a1d991c1625" />
|
|
|
|
|
|
## Links
|
|
|
|
- [Website](https://plannotator.ai)
|
|
- [GitHub](https://github.com/backnotprop/plannotator)
|
|
- [Claude Code Plugin](https://github.com/backnotprop/plannotator/tree/main/apps/hook)
|
|
|
|
## License
|
|
|
|
Copyright 2025 backnotprop Licensed under [MIT](../../LICENSE-MIT) or [Apache-2.0](../../LICENSE-APACHE).
|