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 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
+ ![](.github/assets/screenshot_001.png)
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
+ }
@@ -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,16 @@
1
+ {
2
+ "hooks": {
3
+ "PermissionRequest": [
4
+ {
5
+ "matcher": "ExitPlanMode",
6
+ "hooks": [
7
+ {
8
+ "type": "command",
9
+ "command": "open-plan-annotator",
10
+ "timeout": 345600
11
+ }
12
+ ]
13
+ }
14
+ ]
15
+ }
16
+ }
@@ -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
+ }