Skip to content

Commit 976ae3c

Browse files
committed
Move plugins to single binary runtime
OpenCode and Pi now discover and call the plannotator binary instead of bundling their own server implementations. Adds plugin protocol, binary discovery, capability checks, and auto-install bridge.
1 parent 82636e1 commit 976ae3c

83 files changed

Lines changed: 3944 additions & 6764 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ jobs:
3434
- name: Install dependencies
3535
run: bun install
3636

37-
- name: Generate Pi extension shared copies
37+
- name: Generate Pi extension shared helpers
3838
run: bash apps/pi-extension/vendor.sh
3939

4040
- name: Type check

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222
- name: Install dependencies
2323
run: bun install
2424

25-
- name: Generate Pi extension shared copies
25+
- name: Generate Pi extension shared helpers
2626
run: bash apps/pi-extension/vendor.sh
2727

2828
- name: Type check

.gitignore

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,4 @@ opencode.json
5252
plannotator-local
5353
# Local research/reference docs (not for repo)
5454
/reference/
55-
# Local goal setup packages generated by the setup-goal skill.
56-
/goals/
5755
*.bun-build

AGENTS.md

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,10 @@ plannotator/
7373
│ ├── shared/ # Shared types, utilities, and cross-runtime logic
7474
│ │ ├── storage.ts # Plan saving, version history, archive listing (node:fs only)
7575
│ │ ├── draft.ts # Annotation draft persistence (node:fs only)
76-
│ │ └── project.ts # Pure string helpers (sanitizeTag, extractRepoName, extractDirName)
76+
│ │ ├── project.ts # Pure string helpers (sanitizeTag, extractRepoName, extractDirName)
77+
│ │ ├── plugin-protocol.ts # JSON protocol for binary-owned plugin commands
78+
│ │ ├── plugin-client.ts # Shared OpenCode/Pi subprocess client for plannotator plugin commands
79+
│ │ └── plugin-binary.ts # Binary discovery, compatibility checks, and installer bridge
7780
│ ├── editor/ # Plan review app
7881
│ │ ├── App.tsx # Main plan review app
7982
│ │ └── shortcuts.ts # planReviewSurface + annotateSurface — composes plan-review scopes into per-surface registries
@@ -90,12 +93,11 @@ plannotator/
9093

9194
## Server Runtimes
9295

93-
There are two separate server implementations with the same API surface:
96+
Plannotator has one server implementation:
9497

95-
- **Bun server** (`packages/server/`) — used by both Claude Code (`apps/hook/`) and OpenCode (`apps/opencode-plugin/`). These plugins import directly from `@plannotator/server`.
96-
- **Pi server** (`apps/pi-extension/server/`) — a standalone Node.js server for the Pi extension. It mirrors the Bun server's API but uses `node:http` primitives instead of Bun's `Request`/`Response` APIs.
98+
- **Bun server** (`packages/server/`) — owns plan review, code review, annotate, archive, and shared browser APIs.
9799

98-
When adding or modifying server endpoints, both implementations must be updated. Runtime-agnostic logic (store, validation, types) lives in `packages/shared/` and is imported by both.
100+
Claude Code runs this server through the released `plannotator` binary entrypoint. OpenCode and Pi do not package their own server implementations; they call the same binary through the plugin protocol in `packages/shared/plugin-protocol.ts`. Runtime-agnostic logic (store, validation, types) lives in `packages/shared/`.
99101

100102
## Installation
101103

@@ -118,6 +120,8 @@ claude --plugin-dir ./apps/hook
118120
| `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. |
119121
| `PLANNOTATOR_PORT` | Fixed port to use. Default: random locally, `19432` for remote sessions. |
120122
| `PLANNOTATOR_BROWSER` | Custom browser to open plans in. macOS: app name or path. Linux/Windows: executable path. |
123+
| `PLANNOTATOR_BIN` | Explicit `plannotator` binary path for OpenCode/Pi plugin clients. Overrides PATH and standard install locations. |
124+
| `PLANNOTATOR_DISABLE_AUTO_INSTALL` | Set to `1` / `true` to make OpenCode/Pi fail clearly instead of running the official installer when no compatible binary is found. |
121125
| `PLANNOTATOR_SHARE` | Set to `disabled` to turn off URL sharing entirely. Default: enabled. |
122126
| `PLANNOTATOR_SHARE_URL` | Custom base URL for share links (self-hosted portal). Default: `https://share.plannotator.ai`. |
123127
| `PLANNOTATOR_PASTE_URL` | Base URL of the paste service API for short URL sharing. Default: `https://plannotator-paste.plannotator.workers.dev`. |

apps/copilot/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "plannotator-copilot",
33
"description": "Interactive Plan & Code Review for GitHub Copilot CLI. Visual annotations, team sharing, structured feedback.",
4-
"version": "0.19.18",
4+
"version": "0.19.17",
55
"author": { "name": "backnotprop" },
66
"repository": "https://github.com/backnotprop/plannotator",
77
"license": "MIT OR Apache-2.0",

apps/hook/.claude-plugin/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "plannotator",
33
"description": "Interactive Plan Review: Mark up and refine your plans using a UI, easily share for team collaboration, automatically integrates with plan mode hooks.",
4-
"version": "0.19.18",
4+
"version": "0.19.17",
55
"author": {
66
"name": "backnotprop"
77
},

apps/hook/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ curl -fsSL https://plannotator.ai/install.cmd -o install.cmd && install.cmd && d
2323

2424
Released binaries ship with SHA256 sidecars and [SLSA build provenance](https://slsa.dev/) attestations from v0.17.2 onwards. See the [installation docs](https://plannotator.ai/docs/getting-started/installation/#verifying-your-install) for version pinning and verification commands.
2525

26+
The released binary owns Plannotator's browser server runtime for Claude Code, OpenCode, and Pi. See [Single Binary Runtime](../../docs/single-binary-runtime.md) for the plugin client boundary and daemon-next design.
27+
2628
---
2729

2830
[Plugin Installation](#plugin-installation) · [Manual Installation (Hooks)](#manual-installation-hooks) · [Obsidian Integration](#obsidian-integration)

apps/hook/dev-mock-api.ts

Lines changed: 0 additions & 159 deletions
Original file line numberDiff line numberDiff line change
@@ -552,9 +552,6 @@ This change lands in section 3 of the contributor guide alongside the updated re
552552
const USE_DIFF_DEMO =
553553
process.env.VITE_DIFF_DEMO === "1" ||
554554
process.env.VITE_DIFF_DEMO === "true";
555-
const GOAL_SETUP_DEMO = process.env.VITE_GOAL_SETUP_DEMO;
556-
const USE_GOAL_SETUP_DEMO =
557-
GOAL_SETUP_DEMO === "interview" || GOAL_SETUP_DEMO === "facts";
558555

559556
const PLAN_V1 = USE_DIFF_DEMO ? PLAN_V1_DIFF_TEST : PLAN_V1_DEFAULT;
560557
const PLAN_V2 = USE_DIFF_DEMO ? PLAN_V2_DIFF_TEST : PLAN_V2_DEFAULT;
@@ -629,153 +626,6 @@ export function devMockApi(): Plugin {
629626

630627
if (req.url === '/api/plan') {
631628
res.setHeader('Content-Type', 'application/json');
632-
if (USE_GOAL_SETUP_DEMO) {
633-
res.end(JSON.stringify({
634-
plan: '',
635-
origin: 'claude-code',
636-
mode: 'goal-setup',
637-
sharingEnabled: false,
638-
goalSetup: GOAL_SETUP_DEMO === "facts" ? {
639-
stage: "facts",
640-
title: "Interactive goal setup facts",
641-
goalSlug: "interactive-goal-setup-ui",
642-
facts: [
643-
{
644-
id: "skill-batch",
645-
text: "The setup-goal skill should package all interview questions into one Plannotator UI session.",
646-
accepted: false,
647-
removed: false,
648-
recommendedAutomatedVerification: true,
649-
automatedVerification: true,
650-
},
651-
{
652-
id: "facts-verify",
653-
text: "Each fact can be accepted, edited, removed, commented on, and marked for automated verification.",
654-
accepted: false,
655-
removed: false,
656-
recommendedAutomatedVerification: true,
657-
automatedVerification: true,
658-
},
659-
{
660-
id: "header-submit",
661-
text: "Goal setup submission should use the Plannotator app header action area instead of local form buttons.",
662-
accepted: false,
663-
removed: false,
664-
recommendedAutomatedVerification: false,
665-
automatedVerification: false,
666-
},
667-
{
668-
id: "question-modes",
669-
text: "The interview UI should cover text answers, single-select choices, multi-select choices, and custom option entry.",
670-
accepted: false,
671-
removed: false,
672-
recommendedAutomatedVerification: true,
673-
automatedVerification: true,
674-
},
675-
{
676-
id: "previous",
677-
text: "Previously accepted facts remain visible in the facts review with their accepted state preserved.",
678-
accepted: true,
679-
removed: false,
680-
recommendedAutomatedVerification: false,
681-
automatedVerification: false,
682-
},
683-
{
684-
id: "bulk-accept",
685-
text: "The facts UI provides a single action to accept every visible fact while keeping the review open for final edits.",
686-
accepted: false,
687-
removed: false,
688-
recommendedAutomatedVerification: true,
689-
automatedVerification: true,
690-
},
691-
{
692-
id: "copy-export",
693-
text: "The interview and facts UIs can copy the current state as raw JSON or markdown for provenance and debugging.",
694-
accepted: false,
695-
removed: false,
696-
recommendedAutomatedVerification: false,
697-
automatedVerification: false,
698-
},
699-
],
700-
} : {
701-
stage: "interview",
702-
title: "Interactive goal setup interview",
703-
goalSlug: "interactive-goal-setup-ui",
704-
questions: [
705-
{
706-
id: "objective",
707-
prompt: "What is the primary outcome of this goal?",
708-
description: "One sentence that captures what 'done' looks like.",
709-
answerMode: "text",
710-
recommendedAnswer: "A bundled goal setup UI where agents launch one browser session for interview Q&A and a second for facts acceptance, replacing multi-turn chat prompting.",
711-
},
712-
{
713-
id: "audience",
714-
prompt: "Which inferred audience assumption should change?",
715-
description: "The agent should not need basic confirmation here; only change this if the default is wrong.",
716-
answerMode: "single",
717-
recommendedAnswer: "Developers using Claude Code with Plannotator installed.",
718-
recommendedOptionIds: ["devs-cc"],
719-
options: [
720-
{ id: "devs-cc", label: "Developers on Claude Code" },
721-
{ id: "devs-oc", label: "Developers on OpenCode" },
722-
{ id: "devs-all", label: "All Plannotator users" },
723-
],
724-
},
725-
{
726-
id: "scope",
727-
prompt: "Which inferred scope items should stay or be added?",
728-
description: "Recommended items are based on the code paths the agent can infer. Add only missing nuance.",
729-
answerMode: "multi-custom",
730-
recommendedAnswer: "Skill text, interactive UI, server endpoints, and tests.",
731-
recommendedOptionIds: ["skill", "ui", "server", "tests"],
732-
options: [
733-
{ id: "skill", label: "Skill text" },
734-
{ id: "ui", label: "Interactive UI" },
735-
{ id: "server", label: "Server endpoints" },
736-
{ id: "tests", label: "Tests and fixtures" },
737-
],
738-
},
739-
{
740-
id: "launch",
741-
prompt: "What rollout constraint should override the default?",
742-
description: "Default is the smallest useful launch; choose a broader option only if runtime parity matters immediately.",
743-
answerMode: "single",
744-
recommendedOptionIds: ["claude-only"],
745-
options: [
746-
{ id: "claude-only", label: "Claude Code only" },
747-
{ id: "all-runtimes", label: "All runtimes (Claude Code, OpenCode, Pi)" },
748-
{ id: "prototype", label: "Prototype behind a dev flag" },
749-
],
750-
},
751-
{
752-
id: "risk",
753-
prompt: "Which risks should the plan explicitly address?",
754-
answerMode: "multi",
755-
recommendedOptionIds: ["runtime-parity", "data-loss"],
756-
options: [
757-
{ id: "runtime-parity", label: "Runtime parity", description: "Bun and Pi server endpoints stay mirrored." },
758-
{ id: "data-loss", label: "Answer data loss", description: "Edited answers survive until submission." },
759-
{ id: "header-actions", label: "Header action placement", description: "Submit/close matches existing patterns." },
760-
],
761-
},
762-
{
763-
id: "facts-ux",
764-
prompt: "How should fact review work?",
765-
answerMode: "text",
766-
recommendedAnswer: "Vertical list with per-fact accept, edit, remove, comment, and automated-verification toggle. Accepted facts hidden by default on re-review.",
767-
},
768-
{
769-
id: "out-of-scope",
770-
prompt: "Anything explicitly out of scope?",
771-
answerMode: "custom",
772-
required: false,
773-
},
774-
],
775-
},
776-
}));
777-
return;
778-
}
779629
res.end(JSON.stringify({
780630
plan: undefined, // Editor uses its own DIFF_DEMO_PLAN_CONTENT
781631
origin: 'claude-code',
@@ -786,15 +636,6 @@ export function devMockApi(): Plugin {
786636
return;
787637
}
788638

789-
if (req.url === '/api/goal-setup/submit' && req.method === 'POST') {
790-
req.on('data', () => {});
791-
req.on('end', () => {
792-
res.setHeader('Content-Type', 'application/json');
793-
res.end(JSON.stringify({ ok: true }));
794-
});
795-
return;
796-
}
797-
798639
if (req.url === '/api/plan/versions') {
799640
res.setHeader('Content-Type', 'application/json');
800641
res.end(JSON.stringify({

apps/hook/server/cli.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ describe("CLI top-level help", () => {
2323
expect(output).toContain("plannotator [--browser <name>]");
2424
expect(output).toContain("plannotator review [--git] [PR_URL]");
2525
expect(output).toContain("plannotator annotate <file.md | file.html | https://... | folder/>");
26-
expect(output).toContain("plannotator setup-goal <interview|facts>");
26+
expect(output).toContain("plannotator plugin capabilities");
2727
expect(output).toContain("running 'plannotator' without arguments is for hook integration");
2828
});
2929
});
@@ -56,8 +56,8 @@ describe("interactive no-arg invocation", () => {
5656
expect(output).toContain("usually launched automatically by Claude Code hooks");
5757
expect(output).toContain("It expects hook JSON on stdin.");
5858
expect(output).toContain("plannotator review");
59-
expect(output).toContain("plannotator setup-goal interview bundle.json --json");
6059
expect(output).toContain("plannotator sessions");
60+
expect(output).toContain("plannotator plugin capabilities");
6161
expect(output).toContain("Run 'plannotator --help' for top-level usage.");
6262
});
6363
});

apps/hook/server/cli.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,12 @@ export function formatTopLevelHelp(): string {
2727
" plannotator [--browser <name>]",
2828
" plannotator review [--git] [PR_URL]",
2929
" plannotator annotate <file.md | file.html | https://... | folder/> [--no-jina] [--gate] [--json] [--hook]",
30-
" plannotator setup-goal <interview|facts> <bundle.json | -> [--json]",
3130
" plannotator last",
3231
" plannotator archive",
32+
" plannotator setup-goal <interview|facts> <bundle.json | -> [--json]",
3333
" plannotator sessions",
3434
" plannotator improve-context",
35+
" plannotator plugin capabilities",
3536
"",
3637
"Note:",
3738
" running 'plannotator' without arguments is for hook integration and expects JSON on stdin",
@@ -46,10 +47,10 @@ export function formatInteractiveNoArgClarification(): string {
4647
"For interactive use, try:",
4748
" plannotator review",
4849
" plannotator annotate <file.md | file.html | https://...>",
49-
" plannotator setup-goal interview bundle.json --json",
5050
" plannotator last",
5151
" plannotator archive",
5252
" plannotator sessions",
53+
" plannotator plugin capabilities",
5354
"",
5455
"Run 'plannotator --help' for top-level usage.",
5556
].join("\n");

0 commit comments

Comments
 (0)