open-plan-annotator 0.2.3 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +50 -10
- package/bin/open-plan-annotator-install-opencode.cjs +3 -0
- package/opencode-plugin/index.ts +174 -0
- package/opencode-plugin/package.json +23 -0
- package/package.json +9 -3
- package/scripts/install-opencode-plugin.cjs +81 -0
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"name": "open-plan-annotator",
|
|
13
13
|
"source": "./",
|
|
14
14
|
"description": "Interactive plan annotation UI: review, strikethrough, and comment on Claude's plans before approving. Fully local, no external services.",
|
|
15
|
-
"version": "0.2.
|
|
15
|
+
"version": "0.2.4",
|
|
16
16
|
"author": {
|
|
17
17
|
"name": "ndom91"
|
|
18
18
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "open-plan-annotator",
|
|
3
3
|
"description": "Interactive plan annotation UI: review, strikethrough, and comment on Claude's plans before approving. Fully local, no external services.",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.4",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "ndom91"
|
|
7
7
|
},
|
package/README.md
CHANGED
|
@@ -4,22 +4,21 @@
|
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
[]()
|
|
6
6
|
|
|
7
|
-
A fully local agentic coding plugin
|
|
7
|
+
A fully local agentic coding plugin that intercepts plan mode and opens an annotation UI in your browser. Mark up the plan, send structured feedback to the agent, and receive a revised version — iterate as many times as you need until you're ready to accept.
|
|
8
8
|
|
|
9
9
|
Select text to <code style="color: purple">strikethrough</code>, <code style="color: orange">replace</code>, <code style="color: blue">insert</code>, or <code style="color: red">comment</code> — then approve the plan or request changes.
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-

|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
## How it works
|
|
16
16
|
|
|
17
|
-
1.
|
|
18
|
-
2.
|
|
19
|
-
3.
|
|
20
|
-
4.
|
|
21
|
-
5.
|
|
22
|
-
6. **Request Changes** — annotations are serialized as structured feedback and Claude revises
|
|
17
|
+
1. Host submits a plan to `open-plan-annotator`
|
|
18
|
+
2. An ephemeral HTTP server starts and opens a React UI in your browser
|
|
19
|
+
3. You review and annotate the plan
|
|
20
|
+
4. **Approve** or **Request Changes**
|
|
21
|
+
5. The tool returns host-specific JSON output (Claude hook output or OpenCode plugin output)
|
|
23
22
|
|
|
24
23
|
The server shuts down after you decide. Everything runs locally, nothing leaves your machine.
|
|
25
24
|
|
|
@@ -38,7 +37,9 @@ This JS shim downloads the correct binary for your platform (macOS, Linux).
|
|
|
38
37
|
> 'open-plan-annotator' manually to trigger a download, or the first invocation
|
|
39
38
|
> by Claude will also trigger the binary download.
|
|
40
39
|
|
|
41
|
-
|
|
40
|
+
### Claude Code
|
|
41
|
+
|
|
42
|
+
Add the marketplace and install the plugin:
|
|
42
43
|
|
|
43
44
|
From within Claude Code:
|
|
44
45
|
|
|
@@ -54,7 +55,46 @@ This registers the `ExitPlanMode` hook that launches the annotation UI.
|
|
|
54
55
|
> The first run might take a few seconds if you hadn't installed the binary, as
|
|
55
56
|
> Claude will trigger the download then.
|
|
56
57
|
|
|
57
|
-
###
|
|
58
|
+
### OpenCode
|
|
59
|
+
|
|
60
|
+
The OpenCode plugin uses the `@opencode-ai/plugin` SDK to register a `submit_plan` tool and inject system prompt instructions that tell the agent to use plan mode.
|
|
61
|
+
|
|
62
|
+
**Option A: Install from npm (recommended)**
|
|
63
|
+
|
|
64
|
+
```sh
|
|
65
|
+
npm install -g open-plan-annotator
|
|
66
|
+
cd /path/to/your/project
|
|
67
|
+
open-plan-annotator-install-opencode
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
This installs the plugin to `.opencode/plugins/open-plan-annotator` and runs `bun install` / `npm install` for its dependencies.
|
|
71
|
+
|
|
72
|
+
Then register it in your project's `opencode.json`:
|
|
73
|
+
|
|
74
|
+
```json
|
|
75
|
+
{
|
|
76
|
+
"plugin": [".opencode/plugins/open-plan-annotator"]
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**Option B: From source**
|
|
81
|
+
|
|
82
|
+
```sh
|
|
83
|
+
git clone https://github.com/ndom91/open-plan-annotator.git
|
|
84
|
+
cd open-plan-annotator
|
|
85
|
+
bun install
|
|
86
|
+
bun run install:opencode-plugin # installs to .opencode/plugins/ in CWD
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Then register in `opencode.json` as above.
|
|
90
|
+
|
|
91
|
+
The plugin automatically:
|
|
92
|
+
- Injects plan-mode instructions into the agent's system prompt
|
|
93
|
+
- Registers a `submit_plan` tool that the agent calls after creating a plan
|
|
94
|
+
- Spawns the annotation UI in your browser for review
|
|
95
|
+
- Returns structured feedback to the agent on approval or rejection
|
|
96
|
+
|
|
97
|
+
### From source (Claude Code)
|
|
58
98
|
|
|
59
99
|
```sh
|
|
60
100
|
git clone https://github.com/ndom91/open-plan-annotator.git
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { type Plugin, tool } from "@opencode-ai/plugin";
|
|
5
|
+
|
|
6
|
+
function resolveRunner(): { command: string; args: string[] } {
|
|
7
|
+
const moduleDir = dirname(new URL(import.meta.url).pathname);
|
|
8
|
+
const repoRoot = join(moduleDir, "..");
|
|
9
|
+
const sourceServer = join(repoRoot, "server", "index.ts");
|
|
10
|
+
const packageBin = join(repoRoot, "bin", "open-plan-annotator.cjs");
|
|
11
|
+
const projectLocal = join(process.cwd(), "node_modules", ".bin", "open-plan-annotator");
|
|
12
|
+
|
|
13
|
+
if (existsSync(sourceServer) && process.env.OPEN_PLAN_ANNOTATOR_FORCE_BINARY !== "1") {
|
|
14
|
+
return { command: "bun", args: ["run", sourceServer] };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (existsSync(packageBin)) {
|
|
18
|
+
return { command: process.execPath, args: [packageBin] };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (existsSync(projectLocal)) {
|
|
22
|
+
return { command: projectLocal, args: [] };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return { command: "open-plan-annotator", args: [] };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function execAsync(
|
|
29
|
+
command: string,
|
|
30
|
+
args: string[],
|
|
31
|
+
stdinData: string,
|
|
32
|
+
): Promise<{ stdout: string; stderr: string; status: number | null }> {
|
|
33
|
+
return new Promise((resolve) => {
|
|
34
|
+
const child = execFile(
|
|
35
|
+
command,
|
|
36
|
+
args,
|
|
37
|
+
{ encoding: "utf8", maxBuffer: 10 * 1024 * 1024 },
|
|
38
|
+
(error, stdout, stderr) => {
|
|
39
|
+
resolve({
|
|
40
|
+
stdout: stdout ?? "",
|
|
41
|
+
stderr: stderr ?? "",
|
|
42
|
+
status: error ? 1 : 0,
|
|
43
|
+
});
|
|
44
|
+
},
|
|
45
|
+
);
|
|
46
|
+
if (child.stdin) {
|
|
47
|
+
child.stdin.write(stdinData);
|
|
48
|
+
child.stdin.end();
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function normalizeOutput(
|
|
54
|
+
stdout: string,
|
|
55
|
+
stderr: string,
|
|
56
|
+
_status: number | null,
|
|
57
|
+
): { ok: boolean; decision: string; feedback?: string; message?: string } {
|
|
58
|
+
const text = (stdout ?? "").trim();
|
|
59
|
+
if (!text) {
|
|
60
|
+
return {
|
|
61
|
+
ok: false,
|
|
62
|
+
decision: "deny",
|
|
63
|
+
feedback: stderr || "open-plan-annotator returned empty output",
|
|
64
|
+
message: "Plan changes requested.",
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
const parsed = JSON.parse(text);
|
|
70
|
+
if (parsed && typeof parsed === "object") return parsed;
|
|
71
|
+
} catch {
|
|
72
|
+
// fall through
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
ok: false,
|
|
77
|
+
decision: "deny",
|
|
78
|
+
feedback: `Unexpected response from open-plan-annotator: ${text}`,
|
|
79
|
+
message: "Plan changes requested.",
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const SYSTEM_INSTRUCTIONS = `
|
|
84
|
+
## Plan Submission
|
|
85
|
+
|
|
86
|
+
When you have completed your plan, you MUST call the \`submit_plan\` tool to submit it for user review.
|
|
87
|
+
The user will be able to:
|
|
88
|
+
- Review your plan visually in a dedicated UI
|
|
89
|
+
- Annotate specific sections with feedback
|
|
90
|
+
- Approve the plan to proceed with implementation
|
|
91
|
+
- Request changes with detailed feedback
|
|
92
|
+
|
|
93
|
+
If your plan is rejected, you will receive the user's annotated feedback. Revise your plan
|
|
94
|
+
based on their feedback and call submit_plan again.
|
|
95
|
+
|
|
96
|
+
Do NOT proceed with implementation until your plan is approved.
|
|
97
|
+
|
|
98
|
+
### When to Use Plan Mode
|
|
99
|
+
|
|
100
|
+
Use plan mode (and submit your plan for review) for any task involving:
|
|
101
|
+
- Creating or modifying more than 2 files
|
|
102
|
+
- Architectural or structural changes
|
|
103
|
+
- Anything the user hasn't explicitly described step-by-step
|
|
104
|
+
- Refactoring, migration, or feature additions
|
|
105
|
+
- Bug fixes that require investigation
|
|
106
|
+
|
|
107
|
+
For truly trivial tasks (fix a typo, rename a single variable, answer a factual question), you may skip plan submission.
|
|
108
|
+
|
|
109
|
+
### Plan Quality Standards
|
|
110
|
+
|
|
111
|
+
When writing a plan, include:
|
|
112
|
+
- A brief summary of what you understood the task to require
|
|
113
|
+
- The specific files you intend to create or modify and why
|
|
114
|
+
- Any assumptions you are making
|
|
115
|
+
- An explicit question if anything is ambiguous
|
|
116
|
+
`;
|
|
117
|
+
|
|
118
|
+
export const OpenPlanAnnotatorPlugin: Plugin = async (_ctx) => {
|
|
119
|
+
return {
|
|
120
|
+
"experimental.chat.system.transform": async (_input, output) => {
|
|
121
|
+
output.system.push(SYSTEM_INSTRUCTIONS);
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
tool: {
|
|
125
|
+
submit_plan: tool({
|
|
126
|
+
description:
|
|
127
|
+
"Submit your completed plan for interactive user review. The user can annotate, approve, or request changes. Call this when you have finished creating your implementation plan.",
|
|
128
|
+
args: {
|
|
129
|
+
plan: tool.schema.string().describe("The complete implementation plan in markdown format"),
|
|
130
|
+
summary: tool.schema
|
|
131
|
+
.string()
|
|
132
|
+
.optional()
|
|
133
|
+
.describe("A brief 1-2 sentence summary of what the plan accomplishes"),
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
async execute(args) {
|
|
137
|
+
const runner = resolveRunner();
|
|
138
|
+
const payload = JSON.stringify({
|
|
139
|
+
host: "opencode",
|
|
140
|
+
command: "submit_plan",
|
|
141
|
+
plan: args.plan,
|
|
142
|
+
cwd: process.cwd(),
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
const result = await execAsync(runner.command, runner.args, payload);
|
|
146
|
+
const output = normalizeOutput(result.stdout, result.stderr, result.status);
|
|
147
|
+
|
|
148
|
+
if (output.ok && output.decision === "approve") {
|
|
149
|
+
return [
|
|
150
|
+
"Plan approved by the user. You may now proceed with implementation.",
|
|
151
|
+
args.summary ? `\nPlan Summary: ${args.summary}` : "",
|
|
152
|
+
].join("");
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return [
|
|
156
|
+
"Plan needs revision.",
|
|
157
|
+
"",
|
|
158
|
+
"The user has requested changes to your plan. Please review their feedback below and revise your plan accordingly.",
|
|
159
|
+
"",
|
|
160
|
+
"## User Feedback",
|
|
161
|
+
"",
|
|
162
|
+
output.feedback ?? "Plan changes requested.",
|
|
163
|
+
"",
|
|
164
|
+
"---",
|
|
165
|
+
"",
|
|
166
|
+
"Please revise your plan based on this feedback and call `submit_plan` again when ready.",
|
|
167
|
+
].join("\n");
|
|
168
|
+
},
|
|
169
|
+
}),
|
|
170
|
+
},
|
|
171
|
+
};
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
export default OpenPlanAnnotatorPlugin;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "open-plan-annotator-opencode",
|
|
3
|
+
"version": "0.2.3",
|
|
4
|
+
"description": "Open Plan Annotator plugin for OpenCode - interactive plan review with visual annotation",
|
|
5
|
+
"author": "ndom91",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/ndom91/open-plan-annotator.git",
|
|
10
|
+
"directory": "opencode-plugin"
|
|
11
|
+
},
|
|
12
|
+
"main": "index.ts",
|
|
13
|
+
"files": [
|
|
14
|
+
"index.ts",
|
|
15
|
+
"package.json"
|
|
16
|
+
],
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@opencode-ai/plugin": "^1.0.218"
|
|
19
|
+
},
|
|
20
|
+
"peerDependencies": {
|
|
21
|
+
"bun": ">=1.0.0"
|
|
22
|
+
}
|
|
23
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "open-plan-annotator",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Fully local plugin for interactive plan annotation from your Agentic assistants",
|
|
6
6
|
"author": "ndom91",
|
|
@@ -11,21 +11,26 @@
|
|
|
11
11
|
},
|
|
12
12
|
"keywords": [
|
|
13
13
|
"claude-code",
|
|
14
|
+
"opencode",
|
|
14
15
|
"plugin",
|
|
15
16
|
"plan-mode",
|
|
16
17
|
"annotation",
|
|
17
18
|
"code-review"
|
|
18
19
|
],
|
|
19
20
|
"bin": {
|
|
20
|
-
"open-plan-annotator": "bin/open-plan-annotator.cjs"
|
|
21
|
+
"open-plan-annotator": "bin/open-plan-annotator.cjs",
|
|
22
|
+
"open-plan-annotator-install-opencode": "bin/open-plan-annotator-install-opencode.cjs"
|
|
21
23
|
},
|
|
22
24
|
"files": [
|
|
23
25
|
"bin/open-plan-annotator.cjs",
|
|
26
|
+
"bin/open-plan-annotator-install-opencode.cjs",
|
|
24
27
|
"install.cjs",
|
|
25
28
|
".claude-plugin/",
|
|
29
|
+
"opencode-plugin/",
|
|
26
30
|
"hooks/",
|
|
27
31
|
"CLAUDE.md",
|
|
28
|
-
"README.md"
|
|
32
|
+
"README.md",
|
|
33
|
+
"scripts/install-opencode-plugin.cjs"
|
|
29
34
|
],
|
|
30
35
|
"scripts": {
|
|
31
36
|
"postinstall": "node install.cjs",
|
|
@@ -41,6 +46,7 @@
|
|
|
41
46
|
"lint": "biome check .",
|
|
42
47
|
"lint:fix": "biome check --write .",
|
|
43
48
|
"format": "biome format --write .",
|
|
49
|
+
"install:opencode-plugin": "node scripts/install-opencode-plugin.cjs",
|
|
44
50
|
"do-release": "./scripts/release.sh",
|
|
45
51
|
"prepack": "mv CLAUDE.md CLAUDE.dev.md.bak && cp CLAUDE.plugin.md CLAUDE.md",
|
|
46
52
|
"postpack": "mv CLAUDE.dev.md.bak CLAUDE.md"
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
|
|
6
|
+
function resolveTargetRoot() {
|
|
7
|
+
const fromArg = process.argv[2];
|
|
8
|
+
if (fromArg && fromArg.trim().length > 0) {
|
|
9
|
+
return path.resolve(process.cwd(), fromArg);
|
|
10
|
+
}
|
|
11
|
+
return path.resolve(process.cwd(), ".opencode", "plugins");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function ensureParentDir(dir) {
|
|
15
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function installPlugin(sourceDir, destinationDir) {
|
|
19
|
+
if (fs.existsSync(destinationDir)) {
|
|
20
|
+
fs.rmSync(destinationDir, { recursive: true, force: true });
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
fs.symlinkSync(sourceDir, destinationDir, "dir");
|
|
25
|
+
return "symlink";
|
|
26
|
+
} catch {
|
|
27
|
+
fs.cpSync(sourceDir, destinationDir, { recursive: true });
|
|
28
|
+
return "copy";
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function main() {
|
|
33
|
+
const projectRoot = path.resolve(__dirname, "..");
|
|
34
|
+
const sourceDir = path.join(projectRoot, "opencode-plugin");
|
|
35
|
+
const targetRoot = resolveTargetRoot();
|
|
36
|
+
const destinationDir = path.join(targetRoot, "open-plan-annotator");
|
|
37
|
+
|
|
38
|
+
if (!fs.existsSync(sourceDir)) {
|
|
39
|
+
console.error(`open-plan-annotator: missing opencode-plugin at ${sourceDir}`);
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
ensureParentDir(targetRoot);
|
|
44
|
+
const mode = installPlugin(sourceDir, destinationDir);
|
|
45
|
+
|
|
46
|
+
console.log(
|
|
47
|
+
`open-plan-annotator: installed OpenCode plugin (${mode}) at ${destinationDir}`,
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
// Install plugin dependencies using the first available package manager
|
|
51
|
+
const { execFileSync } = require("child_process");
|
|
52
|
+
const packageManagers = [
|
|
53
|
+
{ cmd: "bun", args: ["install"] },
|
|
54
|
+
{ cmd: "pnpm", args: ["install"] },
|
|
55
|
+
{ cmd: "npm", args: ["install"] },
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
let installed = false;
|
|
59
|
+
for (const pm of packageManagers) {
|
|
60
|
+
try {
|
|
61
|
+
execFileSync(pm.cmd, pm.args, { cwd: destinationDir, stdio: "inherit" });
|
|
62
|
+
installed = true;
|
|
63
|
+
break;
|
|
64
|
+
} catch {
|
|
65
|
+
// Try the next package manager
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (!installed) {
|
|
70
|
+
console.log(
|
|
71
|
+
"open-plan-annotator: could not install dependencies automatically. Run `npm install` in:",
|
|
72
|
+
destinationDir,
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
console.log(
|
|
77
|
+
'\nTo activate, add to your opencode.json:\n { "plugin": [".opencode/plugins/open-plan-annotator"] }',
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
main();
|