open-plan-annotator 0.1.1
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/README.md +61 -0
- package/bin/open-plan-annotator +21 -0
- package/install.cjs +109 -0
- package/package.json +46 -0
- package/plugin/CLAUDE.md +28 -0
- package/plugin/hooks.json +16 -0
- package/plugin/plugin.json +15 -0
package/README.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# open-plan-annotator
|
|
2
|
+
|
|
3
|
+
A fully local agentic coding plugin (claude-code, opencode) that intercepts plan mode, opens an annotation UI in your browser, and feeds structured feedback back to the agent.
|
|
4
|
+
|
|
5
|
+
Select text to strikethrough, replace, insert, or comment — then approve the plan or request changes.
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+
> UI for annotating LLM Plans
|
|
9
|
+
|
|
10
|
+
## How it works
|
|
11
|
+
|
|
12
|
+
1. Claude calls `ExitPlanMode`
|
|
13
|
+
2. A `PermissionRequest` hook launches the `open-plan-annotator` binary
|
|
14
|
+
3. An ephemeral HTTP server starts and opens a React UI in your browser
|
|
15
|
+
4. You review and annotate the plan
|
|
16
|
+
5. **Approve** — Claude proceeds with the plan
|
|
17
|
+
6. **Request Changes** — annotations are serialized as structured feedback and Claude revises
|
|
18
|
+
|
|
19
|
+
The server shuts down after you decide. Everything runs locally, nothing leaves your machine.
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
bun install
|
|
25
|
+
bun run build
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
This produces a single compiled binary at `build/open-plan-annotator`. Add it to your `PATH`, then install the plugin in Claude Code:
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
claude plugin add /path/to/open-plan-annotator/plugin
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Annotations
|
|
35
|
+
|
|
36
|
+
| Type | Shortcut | Description |
|
|
37
|
+
|------|----------|-------------|
|
|
38
|
+
| Delete | `d` | Strikethrough selected text |
|
|
39
|
+
| Replace | `r` | Replace selected text with new text |
|
|
40
|
+
| Insert | `i` | Insert text after selection |
|
|
41
|
+
| Comment | `c` | Attach a comment to selected text |
|
|
42
|
+
|
|
43
|
+
Global shortcuts: `Cmd+Enter` to approve, `Cmd+Shift+Enter` to request changes.
|
|
44
|
+
|
|
45
|
+
## Development
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
bun run dev
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Starts the Bun server on port 3847 with a test plan and the Vite dev server on port 5173 with HMR.
|
|
52
|
+
|
|
53
|
+
```sh
|
|
54
|
+
bun run lint # check
|
|
55
|
+
bun run lint:fix # auto-fix
|
|
56
|
+
bun run format # format
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## License
|
|
60
|
+
|
|
61
|
+
MIT
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { execFileSync } = require("child_process");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const fs = require("fs");
|
|
6
|
+
|
|
7
|
+
const binaryPath = path.join(__dirname, "open-plan-annotator-binary");
|
|
8
|
+
|
|
9
|
+
if (!fs.existsSync(binaryPath)) {
|
|
10
|
+
console.error(
|
|
11
|
+
"open-plan-annotator: binary not found. The postinstall script may have failed.\n" +
|
|
12
|
+
"Try running: node " + path.join(__dirname, "..", "install.cjs")
|
|
13
|
+
);
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
execFileSync(binaryPath, process.argv.slice(2), { stdio: "inherit" });
|
|
19
|
+
} catch (e) {
|
|
20
|
+
process.exit(e.status || 1);
|
|
21
|
+
}
|
package/install.cjs
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// Skip postinstall during local development
|
|
4
|
+
if (process.env.OPEN_PLAN_ANNOTATOR_SKIP_INSTALL || process.env.npm_config_dev) {
|
|
5
|
+
process.exit(0);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const fs = require("fs");
|
|
9
|
+
const path = require("path");
|
|
10
|
+
const zlib = require("zlib");
|
|
11
|
+
const https = require("https");
|
|
12
|
+
|
|
13
|
+
const VERSION = require("./package.json").version;
|
|
14
|
+
const REPO = "ndomino/open-plan-annotator";
|
|
15
|
+
|
|
16
|
+
const PLATFORM_MAP = {
|
|
17
|
+
"darwin-arm64": "open-plan-annotator-darwin-arm64",
|
|
18
|
+
"darwin-x64": "open-plan-annotator-darwin-x64",
|
|
19
|
+
"linux-x64": "open-plan-annotator-linux-x64",
|
|
20
|
+
"linux-arm64": "open-plan-annotator-linux-arm64",
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
function getPlatformKey() {
|
|
24
|
+
return `${process.platform}-${process.arch}`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function getDownloadUrl() {
|
|
28
|
+
const key = getPlatformKey();
|
|
29
|
+
const asset = PLATFORM_MAP[key];
|
|
30
|
+
if (!asset) {
|
|
31
|
+
console.error(`open-plan-annotator: unsupported platform ${key}`);
|
|
32
|
+
console.error(`Supported: ${Object.keys(PLATFORM_MAP).join(", ")}`);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
return `https://github.com/${REPO}/releases/download/v${VERSION}/${asset}.tar.gz`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function fetch(url, redirects) {
|
|
39
|
+
if (redirects === undefined) redirects = 5;
|
|
40
|
+
return new Promise((resolve, reject) => {
|
|
41
|
+
https
|
|
42
|
+
.get(url, { headers: { "User-Agent": "open-plan-annotator-install" } }, (res) => {
|
|
43
|
+
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
44
|
+
if (redirects <= 0) return reject(new Error("Too many redirects"));
|
|
45
|
+
return fetch(res.headers.location, redirects - 1).then(resolve, reject);
|
|
46
|
+
}
|
|
47
|
+
if (res.statusCode < 200 || res.statusCode >= 300) {
|
|
48
|
+
return reject(new Error(`HTTP ${res.statusCode} from ${url}`));
|
|
49
|
+
}
|
|
50
|
+
const chunks = [];
|
|
51
|
+
res.on("data", (c) => chunks.push(c));
|
|
52
|
+
res.on("end", () => resolve(Buffer.concat(chunks)));
|
|
53
|
+
})
|
|
54
|
+
.on("error", reject);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function extractBinaryFromTarGz(buffer) {
|
|
59
|
+
const tarBuffer = zlib.gunzipSync(buffer);
|
|
60
|
+
let offset = 0;
|
|
61
|
+
|
|
62
|
+
while (offset < tarBuffer.length) {
|
|
63
|
+
const header = tarBuffer.subarray(offset, offset + 512);
|
|
64
|
+
offset += 512;
|
|
65
|
+
|
|
66
|
+
const name = header.toString("utf-8", 0, 100).replace(/\0.*/g, "");
|
|
67
|
+
const sizeStr = header.toString("utf-8", 124, 136).replace(/\0.*/g, "").trim();
|
|
68
|
+
const size = parseInt(sizeStr, 8);
|
|
69
|
+
|
|
70
|
+
if (!name || isNaN(size)) break;
|
|
71
|
+
|
|
72
|
+
if (name === "open-plan-annotator" || name.endsWith("/open-plan-annotator")) {
|
|
73
|
+
return tarBuffer.subarray(offset, offset + size);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
offset += Math.ceil(size / 512) * 512;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
throw new Error("Binary 'open-plan-annotator' not found in archive");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function main() {
|
|
83
|
+
const destDir = path.join(__dirname, "bin");
|
|
84
|
+
const destPath = path.join(destDir, "open-plan-annotator-binary");
|
|
85
|
+
|
|
86
|
+
// Skip if binary already exists
|
|
87
|
+
if (fs.existsSync(destPath)) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const url = getDownloadUrl();
|
|
92
|
+
console.log(`Downloading open-plan-annotator for ${getPlatformKey()}...`);
|
|
93
|
+
|
|
94
|
+
const buffer = await fetch(url);
|
|
95
|
+
const binaryBuffer = extractBinaryFromTarGz(buffer);
|
|
96
|
+
|
|
97
|
+
if (!fs.existsSync(destDir)) {
|
|
98
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
fs.writeFileSync(destPath, binaryBuffer, { mode: 0o755 });
|
|
102
|
+
console.log(`Installed open-plan-annotator to ${destPath}`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
main().catch((err) => {
|
|
106
|
+
console.error("Failed to install open-plan-annotator binary:", err.message);
|
|
107
|
+
console.error("You can try manually running: node", path.join(__dirname, "install.cjs"));
|
|
108
|
+
process.exit(1);
|
|
109
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "open-plan-annotator",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Fully local plugin for interactive plan annotation from your Agentic assistants",
|
|
6
|
+
"author": "ndomino",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/ndomino/open-plan-annotator.git"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"claude-code",
|
|
14
|
+
"plugin",
|
|
15
|
+
"plan-mode",
|
|
16
|
+
"annotation",
|
|
17
|
+
"code-review"
|
|
18
|
+
],
|
|
19
|
+
"bin": {
|
|
20
|
+
"open-plan-annotator": "bin/open-plan-annotator"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"bin/open-plan-annotator",
|
|
24
|
+
"install.cjs",
|
|
25
|
+
"plugin/",
|
|
26
|
+
"README.md"
|
|
27
|
+
],
|
|
28
|
+
"scripts": {
|
|
29
|
+
"postinstall": "node install.cjs",
|
|
30
|
+
"build:ui": "cd ui && bun run vite build",
|
|
31
|
+
"build:platforms": "node scripts/build-platforms.cjs",
|
|
32
|
+
"build": "bun run build:ui && node scripts/build-platforms.cjs",
|
|
33
|
+
"tarball": "node scripts/tarball.cjs",
|
|
34
|
+
"release": "bun run build && node scripts/tarball.cjs",
|
|
35
|
+
"dev:ui": "cd ui && bun run vite --port 5173",
|
|
36
|
+
"dev:server": "NODE_ENV=development bun run server/index.ts",
|
|
37
|
+
"dev": "NODE_ENV=development bun run server/index.ts & cd ui && bun run vite --port 5173",
|
|
38
|
+
"lint": "biome check .",
|
|
39
|
+
"lint:fix": "biome check --write .",
|
|
40
|
+
"format": "biome format --write .",
|
|
41
|
+
"publish": "./scripts/release.sh"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@biomejs/biome": "^2.4.4"
|
|
45
|
+
}
|
|
46
|
+
}
|
package/plugin/CLAUDE.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# open-plan-annotator: Plan Review Instructions
|
|
2
|
+
|
|
3
|
+
## ALWAYS Use Plan Mode for Non-Trivial Tasks
|
|
4
|
+
|
|
5
|
+
Before beginning any implementation that involves:
|
|
6
|
+
- Creating or modifying more than 2 files
|
|
7
|
+
- Architectural or structural changes
|
|
8
|
+
- Anything the user hasn't explicitly described step-by-step
|
|
9
|
+
- Refactoring, migration, or feature additions
|
|
10
|
+
- Bug fixes that require investigation
|
|
11
|
+
|
|
12
|
+
You MUST use plan mode to present your approach first.
|
|
13
|
+
|
|
14
|
+
## Why This Matters
|
|
15
|
+
|
|
16
|
+
The user has installed the open-plan-annotator plugin specifically to review and annotate your plans before you write code. Skipping plan mode bypasses this workflow entirely and removes the user's ability to give structured feedback.
|
|
17
|
+
|
|
18
|
+
## Plan Quality Standards
|
|
19
|
+
|
|
20
|
+
When writing a plan, include:
|
|
21
|
+
- A brief summary of what you understood the task to require
|
|
22
|
+
- The specific files you intend to create or modify and why
|
|
23
|
+
- Any assumptions you are making
|
|
24
|
+
- An explicit question if anything is ambiguous
|
|
25
|
+
|
|
26
|
+
## When Plan Mode Is Optional
|
|
27
|
+
|
|
28
|
+
For truly trivial tasks (fix a typo, rename a single variable, answer a factual question), plan mode is not required. When in doubt, use it anyway — the user can always approve immediately.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "open-plan-annotator",
|
|
3
|
+
"description": "Interactive plan annotation UI: review, strikethrough, and comment on Claude's plans before approving. Fully local, no external services.",
|
|
4
|
+
"version": "0.1.1",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "ndomino"
|
|
7
|
+
},
|
|
8
|
+
"keywords": [
|
|
9
|
+
"planning",
|
|
10
|
+
"review",
|
|
11
|
+
"hooks",
|
|
12
|
+
"ExitPlanMode",
|
|
13
|
+
"annotations"
|
|
14
|
+
]
|
|
15
|
+
}
|