open-plan-annotator 1.0.17 → 1.0.20
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 +8 -3
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +19 -7
- package/bin/open-plan-annotator.mjs +46 -47
- package/opencode/bridge.js +6 -74
- package/opencode/index.js +17 -28
- package/opencode/index.test.ts +12 -34
- package/package.json +11 -5
- package/shared/cliHelp.mjs +2 -1
- package/shared/cliHelp.test.ts +2 -1
- package/shared/cliMode.mjs +2 -1
- package/shared/cliMode.test.ts +4 -0
- package/shared/runtimeResolver.mjs +63 -0
- package/shared/runtimeResolver.test.ts +36 -0
- package/shared/updateHints.mjs +14 -0
- package/install.mjs +0 -218
- package/shared/releaseAssets.d.ts +0 -12
- package/shared/releaseAssets.mjs +0 -65
|
@@ -5,14 +5,19 @@
|
|
|
5
5
|
},
|
|
6
6
|
"metadata": {
|
|
7
7
|
"description": "Interactive plan annotation plugin for Claude Code",
|
|
8
|
-
"version": "1.0.
|
|
8
|
+
"version": "1.0.20"
|
|
9
9
|
},
|
|
10
10
|
"plugins": [
|
|
11
11
|
{
|
|
12
12
|
"name": "open-plan-annotator",
|
|
13
|
-
"source":
|
|
13
|
+
"source": {
|
|
14
|
+
"npm": {
|
|
15
|
+
"package": "open-plan-annotator",
|
|
16
|
+
"version": "1.0.20"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
14
19
|
"description": "Interactive plan annotation UI: review, strikethrough, and comment on Claude's plans before approving. Fully local, no external services.",
|
|
15
|
-
"version": "1.0.
|
|
20
|
+
"version": "1.0.20",
|
|
16
21
|
"author": {
|
|
17
22
|
"name": "ndom91"
|
|
18
23
|
},
|
|
@@ -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": "1.0.
|
|
4
|
+
"version": "1.0.20",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "ndom91"
|
|
7
7
|
},
|
package/README.md
CHANGED
|
@@ -23,11 +23,9 @@ Everything runs locally. Nothing leaves your machine.
|
|
|
23
23
|
## Install
|
|
24
24
|
|
|
25
25
|
> [!NOTE]
|
|
26
|
-
>
|
|
27
|
-
>
|
|
28
|
-
>
|
|
29
|
-
> platform upon first use, i.e. when first transitioning out of plan mode.
|
|
30
|
-
> All subsequent runs will be instant.
|
|
26
|
+
> `open-plan-annotator` now ships as one package-managed install. The npm package
|
|
27
|
+
> contains the plugin glue and resolves a platform runtime package locally. There
|
|
28
|
+
> is no first-run binary download and no in-app self-update path.
|
|
31
29
|
|
|
32
30
|
### Claude Code
|
|
33
31
|
|
|
@@ -38,7 +36,7 @@ From within Claude Code, add the marketplace and install the plugin:
|
|
|
38
36
|
/plugin install open-plan-annotator@ndom91-open-plan-annotator
|
|
39
37
|
```
|
|
40
38
|
|
|
41
|
-
This registers the `ExitPlanMode` hook that launches the annotation UI.
|
|
39
|
+
This installs the npm-backed plugin and registers the `ExitPlanMode` hook that launches the annotation UI.
|
|
42
40
|
|
|
43
41
|
### OpenCode
|
|
44
42
|
|
|
@@ -57,6 +55,8 @@ OpenCode will install the package and load it automatically. The plugin:
|
|
|
57
55
|
- Returns structured feedback to the agent on approval or rejection
|
|
58
56
|
- Optionally hands off to an implementation agent after approval
|
|
59
57
|
|
|
58
|
+
To update, refresh the plugin through OpenCode and restart the app so it reloads the latest package-managed runtime.
|
|
59
|
+
|
|
60
60
|
#### Implementation Handoff
|
|
61
61
|
|
|
62
62
|
By default, after a plan is approved the plugin sends "Proceed with implementation." to a `build` agent. To customize or disable this, create `open-plan-annotator.json` in your project's `.opencode/` directory or globally in `~/.config/opencode/`:
|
|
@@ -74,7 +74,7 @@ Set `enabled` to `false` to disable auto-handoff. Project config overrides globa
|
|
|
74
74
|
|
|
75
75
|
### Manual Install
|
|
76
76
|
|
|
77
|
-
If you want to run the
|
|
77
|
+
If you want to run the CLI standalone or install the package globally:
|
|
78
78
|
|
|
79
79
|
```sh
|
|
80
80
|
npm install -g open-plan-annotator
|
|
@@ -95,6 +95,18 @@ Then load it directly in Claude Code:
|
|
|
95
95
|
claude --plugin-dir ./open-plan-annotator
|
|
96
96
|
```
|
|
97
97
|
|
|
98
|
+
## Updates
|
|
99
|
+
|
|
100
|
+
- OpenCode: update the installed npm plugin through OpenCode, then restart OpenCode.
|
|
101
|
+
- Claude Code: update the marketplace/plugin install, then restart Claude Code.
|
|
102
|
+
- Standalone/global install: update the npm package (`npm`, `pnpm`, or `bun`), then rerun `open-plan-annotator`.
|
|
103
|
+
|
|
104
|
+
The built-in `doctor` command reports the resolved runtime package and runtime path:
|
|
105
|
+
|
|
106
|
+
```sh
|
|
107
|
+
open-plan-annotator doctor
|
|
108
|
+
```
|
|
109
|
+
|
|
98
110
|
## Keyboard Shortcuts
|
|
99
111
|
|
|
100
112
|
| Action | Shortcut | Description |
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { spawn } from "node:child_process";
|
|
4
4
|
import fs from "node:fs";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
import { buildCliHelpText, buildUnknownCommandPrefix } from "../shared/cliHelp.mjs";
|
|
8
8
|
import { resolveCliMode } from "../shared/cliMode.mjs";
|
|
9
|
+
import { resolveRuntimeBinary } from "../shared/runtimeResolver.mjs";
|
|
10
|
+
import { buildUpdateInstructions } from "../shared/updateHints.mjs";
|
|
9
11
|
|
|
10
12
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
11
|
-
const binaryPath = path.join(__dirname, "open-plan-annotator-binary");
|
|
12
|
-
const installScript = path.join(__dirname, "..", "install.mjs");
|
|
13
13
|
const VERSION = JSON.parse(fs.readFileSync(path.join(__dirname, "..", "package.json"), "utf8")).version;
|
|
14
14
|
|
|
15
15
|
const arg = process.argv[2];
|
|
@@ -25,6 +25,11 @@ if (cliMode === "help") {
|
|
|
25
25
|
process.exit(0);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
if (cliMode === "doctor") {
|
|
29
|
+
printDoctor();
|
|
30
|
+
process.exit(0);
|
|
31
|
+
}
|
|
32
|
+
|
|
28
33
|
if (cliMode === "unknown") {
|
|
29
34
|
console.error(buildUnknownCommandPrefix(arg));
|
|
30
35
|
console.error("Run `open-plan-annotator --help` for usage.");
|
|
@@ -44,46 +49,8 @@ if (cliMode === "hook") {
|
|
|
44
49
|
stdinBuffer = Buffer.alloc(0);
|
|
45
50
|
}
|
|
46
51
|
|
|
47
|
-
let justInstalled = false;
|
|
48
|
-
if (!fs.existsSync(binaryPath)) {
|
|
49
|
-
// Auto-download the binary (handles pnpm blocking postinstall)
|
|
50
|
-
console.error("open-plan-annotator: binary not found, downloading...");
|
|
51
|
-
try {
|
|
52
|
-
execFileSync(process.execPath, [installScript], {
|
|
53
|
-
stdio: ["ignore", 2, "inherit"],
|
|
54
|
-
});
|
|
55
|
-
} catch (e) {
|
|
56
|
-
console.error(
|
|
57
|
-
"\nopen-plan-annotator: failed to download binary.\n" +
|
|
58
|
-
"Try running manually: node " + installScript + "\n"
|
|
59
|
-
);
|
|
60
|
-
process.exit(1);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (!fs.existsSync(binaryPath)) {
|
|
64
|
-
console.error(
|
|
65
|
-
"open-plan-annotator: binary still not found after install.\n" +
|
|
66
|
-
"Try running manually: node " + installScript + "\n"
|
|
67
|
-
);
|
|
68
|
-
process.exit(1);
|
|
69
|
-
}
|
|
70
|
-
justInstalled = true;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Handle `open-plan-annotator update|upgrade` subcommand
|
|
74
52
|
if (cliMode === "update") {
|
|
75
|
-
|
|
76
|
-
console.log("Binary installed (v" + VERSION + ")");
|
|
77
|
-
process.exit(0);
|
|
78
|
-
}
|
|
79
|
-
try {
|
|
80
|
-
execFileSync(binaryPath, ["update"], {
|
|
81
|
-
stdio: "inherit",
|
|
82
|
-
env: { ...process.env, OPEN_PLAN_PKG_MANAGER: detectPackageManager() },
|
|
83
|
-
});
|
|
84
|
-
} catch (e) {
|
|
85
|
-
process.exit(e.status || 1);
|
|
86
|
-
}
|
|
53
|
+
console.log(buildUpdateInstructions({ host: process.env.OPEN_PLAN_HOST, packageManager: detectPackageManager() }));
|
|
87
54
|
process.exit(0);
|
|
88
55
|
}
|
|
89
56
|
|
|
@@ -96,13 +63,22 @@ function detectPackageManager() {
|
|
|
96
63
|
return "npm";
|
|
97
64
|
}
|
|
98
65
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
66
|
+
let runtime;
|
|
67
|
+
try {
|
|
68
|
+
runtime = resolveRuntimeBinary({ parentUrl: import.meta.url });
|
|
69
|
+
} catch (error) {
|
|
70
|
+
console.error(`open-plan-annotator: ${error instanceof Error ? error.message : String(error)}`);
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const child = spawn(runtime.binaryPath, process.argv.slice(2), {
|
|
103
75
|
stdio: ["pipe", "pipe", "inherit"],
|
|
104
76
|
detached: true,
|
|
105
|
-
env: {
|
|
77
|
+
env: {
|
|
78
|
+
...process.env,
|
|
79
|
+
OPEN_PLAN_HOST: process.env.OPEN_PLAN_HOST || "claude-code",
|
|
80
|
+
OPEN_PLAN_PKG_MANAGER: detectPackageManager(),
|
|
81
|
+
},
|
|
106
82
|
});
|
|
107
83
|
|
|
108
84
|
child.stdin.write(stdinBuffer);
|
|
@@ -149,3 +125,26 @@ child.on("error", (err) => {
|
|
|
149
125
|
console.error("open-plan-annotator: failed to spawn binary:", err.message);
|
|
150
126
|
process.exit(1);
|
|
151
127
|
});
|
|
128
|
+
|
|
129
|
+
function printDoctor() {
|
|
130
|
+
const platformKey = `${process.platform}-${process.arch}`;
|
|
131
|
+
|
|
132
|
+
try {
|
|
133
|
+
const runtime = resolveRuntimeBinary({ parentUrl: import.meta.url });
|
|
134
|
+
console.log([
|
|
135
|
+
`open-plan-annotator v${VERSION}`,
|
|
136
|
+
`platform: ${platformKey}`,
|
|
137
|
+
`runtime package: ${runtime.packageName}`,
|
|
138
|
+
`runtime path: ${runtime.binaryPath}`,
|
|
139
|
+
`update: ${buildUpdateInstructions({ host: process.env.OPEN_PLAN_HOST, packageManager: detectPackageManager() })}`,
|
|
140
|
+
].join("\n"));
|
|
141
|
+
} catch (error) {
|
|
142
|
+
console.log([
|
|
143
|
+
`open-plan-annotator v${VERSION}`,
|
|
144
|
+
`platform: ${platformKey}`,
|
|
145
|
+
`runtime: missing`,
|
|
146
|
+
`error: ${error instanceof Error ? error.message : String(error)}`,
|
|
147
|
+
`update: ${buildUpdateInstructions({ host: process.env.OPEN_PLAN_HOST, packageManager: detectPackageManager() })}`,
|
|
148
|
+
].join("\n"));
|
|
149
|
+
}
|
|
150
|
+
}
|
package/opencode/bridge.js
CHANGED
|
@@ -1,15 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { existsSync, statSync } from "node:fs";
|
|
4
|
-
import { dirname
|
|
4
|
+
import { dirname } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { resolveRuntimeBinary } from "../shared/runtimeResolver.mjs";
|
|
6
7
|
|
|
7
8
|
const PKG_ROOT = fileURLToPath(new URL("..", import.meta.url));
|
|
8
|
-
const LOCAL_BINARY_PATH = join(PKG_ROOT, "bin", "open-plan-annotator-binary");
|
|
9
|
-
const INSTALL_SCRIPT = join(PKG_ROOT, "install.mjs");
|
|
10
|
-
|
|
11
|
-
/** Resolved path to the binary (may differ from LOCAL_BINARY_PATH if found on PATH). */
|
|
12
|
-
let BINARY_PATH = LOCAL_BINARY_PATH;
|
|
13
9
|
|
|
14
10
|
/**
|
|
15
11
|
* @typedef {{
|
|
@@ -102,27 +98,6 @@ function validateHookOutput(value) {
|
|
|
102
98
|
throw new Error("unsupported decision payload");
|
|
103
99
|
}
|
|
104
100
|
|
|
105
|
-
/**
|
|
106
|
-
* Check if `open-plan-annotator` is available on PATH.
|
|
107
|
-
* The CLI wrapper handles binary discovery/download and stdin/stdout
|
|
108
|
-
* forwarding, so we can spawn it directly as a fallback when the local
|
|
109
|
-
* binary isn't available (e.g. OpenCode loads the plugin from its own
|
|
110
|
-
* node_modules but the binary only exists in a global pnpm install).
|
|
111
|
-
* @returns {string | undefined}
|
|
112
|
-
*/
|
|
113
|
-
function findWrapperOnPath() {
|
|
114
|
-
const cmd = process.platform === "win32" ? "where" : "which";
|
|
115
|
-
try {
|
|
116
|
-
const result = execFileSync(cmd, ["open-plan-annotator"], {
|
|
117
|
-
encoding: "utf-8",
|
|
118
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
119
|
-
}).trim().split("\n")[0];
|
|
120
|
-
return result || undefined;
|
|
121
|
-
} catch {
|
|
122
|
-
return undefined;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
101
|
function detectPackageManager() {
|
|
127
102
|
const ua = process.env.npm_config_user_agent || "";
|
|
128
103
|
if (ua.startsWith("pnpm")) return "pnpm";
|
|
@@ -131,55 +106,11 @@ function detectPackageManager() {
|
|
|
131
106
|
return "npm";
|
|
132
107
|
}
|
|
133
108
|
|
|
134
|
-
/** Ensure the compiled binary exists, downloading if necessary. */
|
|
135
|
-
function ensureBinary() {
|
|
136
|
-
if (existsSync(LOCAL_BINARY_PATH)) {
|
|
137
|
-
BINARY_PATH = LOCAL_BINARY_PATH;
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Try to find node on PATH for running the install script
|
|
142
|
-
try {
|
|
143
|
-
execFileSync(process.execPath, [INSTALL_SCRIPT], {
|
|
144
|
-
cwd: PKG_ROOT,
|
|
145
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
146
|
-
});
|
|
147
|
-
} catch {
|
|
148
|
-
// Retry with "node" explicitly in case process.execPath is bun
|
|
149
|
-
try {
|
|
150
|
-
execFileSync("node", [INSTALL_SCRIPT], {
|
|
151
|
-
cwd: PKG_ROOT,
|
|
152
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
153
|
-
});
|
|
154
|
-
} catch {
|
|
155
|
-
// ignore — we'll check below
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
if (existsSync(LOCAL_BINARY_PATH)) {
|
|
160
|
-
BINARY_PATH = LOCAL_BINARY_PATH;
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// Fallback: use the CLI wrapper from PATH (e.g. global pnpm install).
|
|
165
|
-
// The wrapper handles binary discovery/download and stdio forwarding.
|
|
166
|
-
const wrapperPath = findWrapperOnPath();
|
|
167
|
-
if (wrapperPath) {
|
|
168
|
-
BINARY_PATH = wrapperPath;
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
throw new Error(
|
|
173
|
-
`open-plan-annotator: binary not found at ${LOCAL_BINARY_PATH}. ` +
|
|
174
|
-
`Try running: node ${INSTALL_SCRIPT}`,
|
|
175
|
-
);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
109
|
/**
|
|
179
110
|
* @param {{ plan: string, sessionId?: string, cwd?: string }} options
|
|
180
111
|
*/
|
|
181
112
|
export async function runPlanReview(options) {
|
|
182
|
-
|
|
113
|
+
const runtime = resolveRuntimeBinary({ parentUrl: import.meta.url });
|
|
183
114
|
|
|
184
115
|
const payload = buildHookPayload(options);
|
|
185
116
|
|
|
@@ -197,11 +128,12 @@ export async function runPlanReview(options) {
|
|
|
197
128
|
|
|
198
129
|
// Spawn detached so the binary can outlive this call — it keeps its
|
|
199
130
|
// HTTP server alive for ~10s after emitting the JSON hook response.
|
|
200
|
-
const child = spawn(
|
|
131
|
+
const child = spawn(runtime.binaryPath, [], {
|
|
201
132
|
cwd,
|
|
202
133
|
stdio: ["pipe", "pipe", "pipe"],
|
|
203
134
|
env: {
|
|
204
135
|
...process.env,
|
|
136
|
+
OPEN_PLAN_HOST: "opencode",
|
|
205
137
|
OPEN_PLAN_PKG_MANAGER: process.env.OPEN_PLAN_PKG_MANAGER || detectPackageManager(),
|
|
206
138
|
},
|
|
207
139
|
detached: true,
|
package/opencode/index.js
CHANGED
|
@@ -162,7 +162,7 @@ export const OpenPlanAnnotatorPlugin = async (ctx) => {
|
|
|
162
162
|
tool: {
|
|
163
163
|
submit_plan: tool({
|
|
164
164
|
description:
|
|
165
|
-
"Submit a markdown plan for interactive user review. Returns
|
|
165
|
+
"Submit a markdown plan for interactive user review. Returns plain-text execution or revision instructions for the agent.",
|
|
166
166
|
|
|
167
167
|
args: {
|
|
168
168
|
plan: tool.schema.string().describe("The complete implementation plan in markdown format"),
|
|
@@ -178,17 +178,8 @@ export const OpenPlanAnnotatorPlugin = async (ctx) => {
|
|
|
178
178
|
|
|
179
179
|
const feedback = result.approved ? "" : (result.feedback ?? "Plan changes requested.");
|
|
180
180
|
|
|
181
|
-
const basePayload = {
|
|
182
|
-
plan_status: result.approved ? "approved" : "rejected",
|
|
183
|
-
next_state: result.approved ? "EXECUTION" : "PLAN_DRAFT",
|
|
184
|
-
feedback,
|
|
185
|
-
};
|
|
186
|
-
|
|
187
181
|
if (result.approved) {
|
|
188
|
-
const lines = [
|
|
189
|
-
"Plan approved by the user.",
|
|
190
|
-
"Do NOT call `submit_plan` again. The planning phase is finished.",
|
|
191
|
-
];
|
|
182
|
+
const lines = ["Plan review status: plan_status=approved.", "State transition: next_state=EXECUTION."];
|
|
192
183
|
|
|
193
184
|
if (args.summary) {
|
|
194
185
|
lines.push(`Summary: ${args.summary}`);
|
|
@@ -203,25 +194,23 @@ export const OpenPlanAnnotatorPlugin = async (ctx) => {
|
|
|
203
194
|
}
|
|
204
195
|
}
|
|
205
196
|
|
|
206
|
-
lines.push("
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
197
|
+
lines.push("Replan intent: explicit_replan=false unless the user explicitly asks to revise the plan.");
|
|
198
|
+
lines.push("Execute the approved plan directly now — write code, create files, and make changes.");
|
|
199
|
+
lines.push("Do not call `submit_plan` again unless the user explicitly requests re-planning.");
|
|
200
|
+
|
|
201
|
+
return lines.join("\n\n");
|
|
211
202
|
}
|
|
212
203
|
|
|
213
|
-
return
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
].join("\n"),
|
|
224
|
-
};
|
|
204
|
+
return [
|
|
205
|
+
"Plan review status: plan_status=rejected.",
|
|
206
|
+
"State transition: next_state=PLAN_DRAFT.",
|
|
207
|
+
"",
|
|
208
|
+
"## User feedback",
|
|
209
|
+
"",
|
|
210
|
+
feedback,
|
|
211
|
+
"",
|
|
212
|
+
"Revise the plan using this feedback, then submit the revised draft once via `submit_plan`.",
|
|
213
|
+
].join("\n");
|
|
225
214
|
},
|
|
226
215
|
}),
|
|
227
216
|
},
|
package/opencode/index.test.ts
CHANGED
|
@@ -19,8 +19,8 @@ function createPluginContext() {
|
|
|
19
19
|
};
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
describe("submit_plan
|
|
23
|
-
test("
|
|
22
|
+
describe("submit_plan tool output", () => {
|
|
23
|
+
test("returns plain text execution instructions after approval", async () => {
|
|
24
24
|
mock.module("./bridge.js", () => ({
|
|
25
25
|
runPlanReview: async () => ({ approved: true }),
|
|
26
26
|
}));
|
|
@@ -30,17 +30,15 @@ describe("submit_plan return schema contract", () => {
|
|
|
30
30
|
|
|
31
31
|
const result = await plugin.tool.submit_plan.execute({ plan: "# Plan" }, { sessionID: "session-1" });
|
|
32
32
|
|
|
33
|
-
expect(result
|
|
34
|
-
expect(result
|
|
35
|
-
expect(result
|
|
36
|
-
expect(result
|
|
33
|
+
expect(typeof result).toBe("string");
|
|
34
|
+
expect(result).toContain("plan_status=approved");
|
|
35
|
+
expect(result).toContain("next_state=EXECUTION");
|
|
36
|
+
expect(result).toContain("Do not call `submit_plan` again");
|
|
37
37
|
});
|
|
38
38
|
|
|
39
|
-
test("
|
|
40
|
-
const bridgeFeedback = "Need to add rollback steps.";
|
|
41
|
-
|
|
39
|
+
test("returns plain text revision instructions after rejection", async () => {
|
|
42
40
|
mock.module("./bridge.js", () => ({
|
|
43
|
-
runPlanReview: async () => ({ approved: false, feedback:
|
|
41
|
+
runPlanReview: async () => ({ approved: false, feedback: "Need rollback steps." }),
|
|
44
42
|
}));
|
|
45
43
|
|
|
46
44
|
const { OpenPlanAnnotatorPlugin } = await import(`./index.js?rejected-${Date.now()}`);
|
|
@@ -48,29 +46,9 @@ describe("submit_plan return schema contract", () => {
|
|
|
48
46
|
|
|
49
47
|
const result = await plugin.tool.submit_plan.execute({ plan: "# Plan" }, { sessionID: "session-2" });
|
|
50
48
|
|
|
51
|
-
expect(result
|
|
52
|
-
expect(result
|
|
53
|
-
expect(result
|
|
54
|
-
expect(result
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
test("all top-level submit_plan fields are strings for display safety", async () => {
|
|
58
|
-
mock.module("./bridge.js", () => ({
|
|
59
|
-
runPlanReview: async () => ({ approved: true }),
|
|
60
|
-
}));
|
|
61
|
-
|
|
62
|
-
const { OpenPlanAnnotatorPlugin } = await import(`./index.js?display-safe-${Date.now()}`);
|
|
63
|
-
const plugin = await OpenPlanAnnotatorPlugin(createPluginContext());
|
|
64
|
-
|
|
65
|
-
const result = await plugin.tool.submit_plan.execute(
|
|
66
|
-
{ plan: "# Plan", summary: "Short summary" },
|
|
67
|
-
{ sessionID: "session-3" },
|
|
68
|
-
);
|
|
69
|
-
|
|
70
|
-
for (const value of Object.values(result)) {
|
|
71
|
-
expect(typeof value).toBe("string");
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
expect(result).not.toHaveProperty("approved");
|
|
49
|
+
expect(typeof result).toBe("string");
|
|
50
|
+
expect(result).toContain("plan_status=rejected");
|
|
51
|
+
expect(result).toContain("next_state=PLAN_DRAFT");
|
|
52
|
+
expect(result).toContain("Need rollback steps.");
|
|
75
53
|
});
|
|
76
54
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "open-plan-annotator",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.20",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Fully local plugin for interactive plan annotation from your Agentic assistants",
|
|
6
6
|
"author": "ndom91",
|
|
@@ -17,13 +17,15 @@
|
|
|
17
17
|
"annotation",
|
|
18
18
|
"code-review"
|
|
19
19
|
],
|
|
20
|
+
"workspaces": [
|
|
21
|
+
"packages/*"
|
|
22
|
+
],
|
|
20
23
|
"main": "opencode/index.js",
|
|
21
24
|
"bin": {
|
|
22
25
|
"open-plan-annotator": "bin/open-plan-annotator.mjs"
|
|
23
26
|
},
|
|
24
27
|
"files": [
|
|
25
28
|
"bin/open-plan-annotator.mjs",
|
|
26
|
-
"install.mjs",
|
|
27
29
|
"shared/",
|
|
28
30
|
".claude-plugin/",
|
|
29
31
|
"opencode/",
|
|
@@ -32,14 +34,12 @@
|
|
|
32
34
|
"README.md"
|
|
33
35
|
],
|
|
34
36
|
"scripts": {
|
|
35
|
-
"postinstall": "node install.mjs",
|
|
36
37
|
"test": "bun test",
|
|
37
38
|
"typecheck": "tsgo --noEmit --project ui/tsconfig.json && tsgo --noEmit --project server/tsconfig.json",
|
|
38
39
|
"build:ui": "cd ui && bun run vite build",
|
|
39
40
|
"build:platforms": "node scripts/build-platforms.mjs",
|
|
40
41
|
"build": "bun run build:ui && node scripts/build-platforms.mjs",
|
|
41
|
-
"
|
|
42
|
-
"release": "bun run build && node scripts/tarball.mjs",
|
|
42
|
+
"release": "bun run build",
|
|
43
43
|
"pack:check": "node scripts/check-package-files.mjs",
|
|
44
44
|
"dev:ui": "cd ui && bun run vite --port 5173",
|
|
45
45
|
"dev:server": "NODE_ENV=development bun run server/index.ts",
|
|
@@ -59,5 +59,11 @@
|
|
|
59
59
|
},
|
|
60
60
|
"dependencies": {
|
|
61
61
|
"@opencode-ai/plugin": "^1.2.14"
|
|
62
|
+
},
|
|
63
|
+
"optionalDependencies": {
|
|
64
|
+
"@open-plan-annotator/runtime-darwin-arm64": "1.0.20",
|
|
65
|
+
"@open-plan-annotator/runtime-darwin-x64": "1.0.20",
|
|
66
|
+
"@open-plan-annotator/runtime-linux-arm64": "1.0.20",
|
|
67
|
+
"@open-plan-annotator/runtime-linux-x64": "1.0.20"
|
|
62
68
|
}
|
|
63
69
|
}
|
package/shared/cliHelp.mjs
CHANGED
|
@@ -3,7 +3,8 @@ const REPOSITORY_URL = "https://github.com/ndom91/open-plan-annotator";
|
|
|
3
3
|
const HELP_USAGE_LINES = [
|
|
4
4
|
"open-plan-annotator Show this help",
|
|
5
5
|
"open-plan-annotator < event.json Run as a Claude Code hook (debug)",
|
|
6
|
-
"open-plan-annotator
|
|
6
|
+
"open-plan-annotator doctor Show resolved runtime details",
|
|
7
|
+
"open-plan-annotator update Show package-managed update guidance",
|
|
7
8
|
"open-plan-annotator upgrade Alias for update",
|
|
8
9
|
"open-plan-annotator --version Print version",
|
|
9
10
|
"open-plan-annotator --help Show this help",
|
package/shared/cliHelp.test.ts
CHANGED
|
@@ -8,7 +8,8 @@ describe("buildCliHelpText", () => {
|
|
|
8
8
|
Usage:
|
|
9
9
|
open-plan-annotator Show this help
|
|
10
10
|
open-plan-annotator < event.json Run as a Claude Code hook (debug)
|
|
11
|
-
open-plan-annotator
|
|
11
|
+
open-plan-annotator doctor Show resolved runtime details
|
|
12
|
+
open-plan-annotator update Show package-managed update guidance
|
|
12
13
|
open-plan-annotator upgrade Alias for update
|
|
13
14
|
open-plan-annotator --version Print version
|
|
14
15
|
open-plan-annotator --help Show this help
|
package/shared/cliMode.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @typedef {"hook" | "update" | "help" | "version" | "unknown"} CliMode
|
|
2
|
+
* @typedef {"hook" | "update" | "doctor" | "help" | "version" | "unknown"} CliMode
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -17,6 +17,7 @@ export function resolveCliMode(arg, options = {}) {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
if (arg === "update" || arg === "upgrade") return "update";
|
|
20
|
+
if (arg === "doctor") return "doctor";
|
|
20
21
|
if (arg === "--help" || arg === "-h") return "help";
|
|
21
22
|
if (arg === "--version" || arg === "-v") return "version";
|
|
22
23
|
return "unknown";
|
package/shared/cliMode.test.ts
CHANGED
|
@@ -18,6 +18,10 @@ describe("resolveCliMode", () => {
|
|
|
18
18
|
expect(resolveCliMode("upgrade")).toBe("update");
|
|
19
19
|
});
|
|
20
20
|
|
|
21
|
+
test("recognizes doctor subcommand", () => {
|
|
22
|
+
expect(resolveCliMode("doctor")).toBe("doctor");
|
|
23
|
+
});
|
|
24
|
+
|
|
21
25
|
test("recognizes help and version flags", () => {
|
|
22
26
|
expect(resolveCliMode("--help")).toBe("help");
|
|
23
27
|
expect(resolveCliMode("-h")).toBe("help");
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
export const RUNTIME_PACKAGE_MAP = {
|
|
7
|
+
"darwin-arm64": "@open-plan-annotator/runtime-darwin-arm64",
|
|
8
|
+
"darwin-x64": "@open-plan-annotator/runtime-darwin-x64",
|
|
9
|
+
"linux-arm64": "@open-plan-annotator/runtime-linux-arm64",
|
|
10
|
+
"linux-x64": "@open-plan-annotator/runtime-linux-x64",
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function getRuntimePlatformKey(platform = process.platform, arch = process.arch) {
|
|
14
|
+
return `${platform}-${arch}`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function getRuntimePackageName(platform = process.platform, arch = process.arch) {
|
|
18
|
+
return RUNTIME_PACKAGE_MAP[getRuntimePlatformKey(platform, arch)];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function resolveRuntimeBinary(options = {}) {
|
|
22
|
+
const platform = options.platform ?? process.platform;
|
|
23
|
+
const arch = options.arch ?? process.arch;
|
|
24
|
+
const packageName = getRuntimePackageName(platform, arch);
|
|
25
|
+
|
|
26
|
+
if (!packageName) {
|
|
27
|
+
throw new Error(`Unsupported platform ${getRuntimePlatformKey(platform, arch)}`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const requireFrom = createRequire(options.parentUrl ?? import.meta.url);
|
|
31
|
+
const workspaceRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
32
|
+
|
|
33
|
+
let packageJsonPath;
|
|
34
|
+
try {
|
|
35
|
+
packageJsonPath = requireFrom.resolve(`${packageName}/package.json`);
|
|
36
|
+
} catch {
|
|
37
|
+
const workspaceBinaryPath = path.join(workspaceRoot, "packages", packageName.split("/").at(-1) ?? "", "bin", "open-plan-annotator");
|
|
38
|
+
if (fs.existsSync(workspaceBinaryPath)) {
|
|
39
|
+
return {
|
|
40
|
+
packageName,
|
|
41
|
+
packageRoot: path.dirname(path.dirname(workspaceBinaryPath)),
|
|
42
|
+
binaryPath: workspaceBinaryPath,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
throw new Error(
|
|
47
|
+
`Missing runtime package ${packageName}. Reinstall open-plan-annotator for ${getRuntimePlatformKey(platform, arch)}.`,
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const packageRoot = path.dirname(packageJsonPath);
|
|
52
|
+
const binaryPath = path.join(packageRoot, "bin", "open-plan-annotator");
|
|
53
|
+
|
|
54
|
+
if (!fs.existsSync(binaryPath)) {
|
|
55
|
+
throw new Error(`Runtime package ${packageName} is installed but ${binaryPath} is missing. Rebuild or reinstall it.`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
packageName,
|
|
60
|
+
packageRoot,
|
|
61
|
+
binaryPath,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { pathToFileURL } from "node:url";
|
|
6
|
+
import { getRuntimePackageName, resolveRuntimeBinary } from "./runtimeResolver.mjs";
|
|
7
|
+
|
|
8
|
+
describe("runtimeResolver", () => {
|
|
9
|
+
test("maps supported platforms to runtime packages", () => {
|
|
10
|
+
expect(getRuntimePackageName("darwin", "arm64")).toBe("@open-plan-annotator/runtime-darwin-arm64");
|
|
11
|
+
expect(getRuntimePackageName("linux", "x64")).toBe("@open-plan-annotator/runtime-linux-x64");
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test("throws for unsupported platforms", () => {
|
|
15
|
+
expect(() => resolveRuntimeBinary({ platform: "win32", arch: "x64" })).toThrow("Unsupported platform win32-x64");
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("resolves binary from installed runtime package", () => {
|
|
19
|
+
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "opa-runtime-"));
|
|
20
|
+
const packageRoot = path.join(tempRoot, "node_modules", "@open-plan-annotator", "runtime-linux-x64");
|
|
21
|
+
const binaryPath = path.join(packageRoot, "bin", "open-plan-annotator");
|
|
22
|
+
|
|
23
|
+
fs.mkdirSync(path.dirname(binaryPath), { recursive: true });
|
|
24
|
+
fs.writeFileSync(path.join(packageRoot, "package.json"), '{"name":"@open-plan-annotator/runtime-linux-x64"}');
|
|
25
|
+
fs.writeFileSync(binaryPath, "binary");
|
|
26
|
+
|
|
27
|
+
const resolved = resolveRuntimeBinary({
|
|
28
|
+
platform: "linux",
|
|
29
|
+
arch: "x64",
|
|
30
|
+
parentUrl: pathToFileURL(path.join(tempRoot, "index.mjs")).href,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
expect(resolved.packageName).toBe("@open-plan-annotator/runtime-linux-x64");
|
|
34
|
+
expect(fs.realpathSync(resolved.binaryPath)).toBe(fs.realpathSync(binaryPath));
|
|
35
|
+
});
|
|
36
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function buildUpdateInstructions(options = {}) {
|
|
2
|
+
const host = options.host ?? process.env.OPEN_PLAN_HOST;
|
|
3
|
+
const packageManager = options.packageManager ?? "npm";
|
|
4
|
+
|
|
5
|
+
if (host === "opencode") {
|
|
6
|
+
return "Refresh the OpenCode plugin install, then restart OpenCode.";
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
if (host === "claude-code") {
|
|
10
|
+
return "Refresh the Claude Code plugin or marketplace install, then restart Claude Code.";
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return `Update open-plan-annotator via ${packageManager}, then rerun it.`;
|
|
14
|
+
}
|
package/install.mjs
DELETED
|
@@ -1,218 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import crypto from "node:crypto";
|
|
4
|
-
import fs from "node:fs";
|
|
5
|
-
import https from "node:https";
|
|
6
|
-
import path from "node:path";
|
|
7
|
-
import {
|
|
8
|
-
PLATFORM_ASSET_BASENAME_MAP,
|
|
9
|
-
REPO,
|
|
10
|
-
getPlatformAssetArchiveName,
|
|
11
|
-
getPlatformKey,
|
|
12
|
-
parseChecksumManifest,
|
|
13
|
-
selectChecksumAsset,
|
|
14
|
-
} from "./shared/releaseAssets.mjs";
|
|
15
|
-
import { fileURLToPath } from "node:url";
|
|
16
|
-
import zlib from "node:zlib";
|
|
17
|
-
|
|
18
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
19
|
-
const VERSION = JSON.parse(fs.readFileSync(new URL("./package.json", import.meta.url), "utf8")).version;
|
|
20
|
-
|
|
21
|
-
function getReleaseApiUrl() {
|
|
22
|
-
return `https://api.github.com/repos/${REPO}/releases/tags/v${VERSION}`;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function fetch(url, redirects) {
|
|
26
|
-
if (redirects === undefined) redirects = 5;
|
|
27
|
-
return new Promise((resolve, reject) => {
|
|
28
|
-
https
|
|
29
|
-
.get(url, { headers: { "User-Agent": "open-plan-annotator-install" } }, (res) => {
|
|
30
|
-
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
31
|
-
if (redirects <= 0) return reject(new Error("Too many redirects"));
|
|
32
|
-
return fetch(res.headers.location, redirects - 1).then(resolve, reject);
|
|
33
|
-
}
|
|
34
|
-
if (res.statusCode < 200 || res.statusCode >= 300) {
|
|
35
|
-
return reject(new Error(`HTTP ${res.statusCode} from ${url}`));
|
|
36
|
-
}
|
|
37
|
-
const chunks = [];
|
|
38
|
-
res.on("data", (c) => chunks.push(c));
|
|
39
|
-
res.on("end", () => resolve(Buffer.concat(chunks)));
|
|
40
|
-
})
|
|
41
|
-
.on("error", reject);
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
async function fetchJson(url) {
|
|
46
|
-
const buffer = await fetch(url);
|
|
47
|
-
return JSON.parse(buffer.toString("utf8"));
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function sha256Hex(buffer) {
|
|
51
|
-
return crypto.createHash("sha256").update(buffer).digest("hex");
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
async function resolveReleaseAssetAndChecksum(options) {
|
|
55
|
-
const opts = options || {};
|
|
56
|
-
const fetchJsonImpl = opts.fetchJson || fetchJson;
|
|
57
|
-
const fetchBuffer = opts.fetch || fetch;
|
|
58
|
-
const releaseApiUrl = opts.releaseApiUrl || getReleaseApiUrl();
|
|
59
|
-
const version = opts.version || VERSION;
|
|
60
|
-
|
|
61
|
-
const release = await fetchJsonImpl(releaseApiUrl);
|
|
62
|
-
const releaseAssets = Array.isArray(release.assets) ? release.assets : [];
|
|
63
|
-
const key = opts.platformKey || getPlatformKey();
|
|
64
|
-
const assetName = getPlatformAssetArchiveName(key);
|
|
65
|
-
if (!assetName) {
|
|
66
|
-
throw new Error(`Unsupported platform ${key}`);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const asset = releaseAssets.find((entry) => entry.name === assetName);
|
|
70
|
-
if (!asset) {
|
|
71
|
-
throw new Error(`Release v${version} is missing asset ${assetName}`);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const checksumAsset = selectChecksumAsset(releaseAssets);
|
|
75
|
-
if (!checksumAsset) {
|
|
76
|
-
throw new Error(`Release v${version} does not contain a checksum manifest asset`);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const checksumManifest = (await fetchBuffer(checksumAsset.browser_download_url)).toString("utf8");
|
|
80
|
-
const checksums = parseChecksumManifest(checksumManifest);
|
|
81
|
-
const expectedSha256 = checksums.get(assetName);
|
|
82
|
-
if (!expectedSha256) {
|
|
83
|
-
throw new Error(`Checksum manifest does not contain ${assetName}`);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
return {
|
|
87
|
-
assetName,
|
|
88
|
-
assetUrl: asset.browser_download_url,
|
|
89
|
-
expectedSha256,
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function errorMessage(err) {
|
|
94
|
-
return err && err.message ? err.message : String(err);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
async function downloadVerifiedArchive(options) {
|
|
98
|
-
const opts = options || {};
|
|
99
|
-
const resolveRelease = opts.resolveReleaseAssetAndChecksum || resolveReleaseAssetAndChecksum;
|
|
100
|
-
const fetchBuffer = opts.fetch || fetch;
|
|
101
|
-
const checksumRequirement =
|
|
102
|
-
"open-plan-annotator requires release checksum/sha256sum availability and will not install without verification.";
|
|
103
|
-
|
|
104
|
-
let releaseInfo;
|
|
105
|
-
|
|
106
|
-
try {
|
|
107
|
-
releaseInfo = await resolveRelease();
|
|
108
|
-
} catch (err) {
|
|
109
|
-
throw new Error(`Unable to verify release checksums: ${errorMessage(err)} ${checksumRequirement}`);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const { assetName, assetUrl, expectedSha256 } = releaseInfo;
|
|
113
|
-
const archiveBuffer = await fetchBuffer(assetUrl);
|
|
114
|
-
const actualSha256 = sha256Hex(archiveBuffer);
|
|
115
|
-
|
|
116
|
-
if (actualSha256 !== expectedSha256) {
|
|
117
|
-
throw new Error(
|
|
118
|
-
`Checksum verification failed for ${assetName} (expected ${expectedSha256}, got ${actualSha256}). ${checksumRequirement}`,
|
|
119
|
-
);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return archiveBuffer;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function extractBinaryFromTarGz(buffer) {
|
|
126
|
-
const tarBuffer = zlib.gunzipSync(buffer);
|
|
127
|
-
let offset = 0;
|
|
128
|
-
|
|
129
|
-
while (offset < tarBuffer.length) {
|
|
130
|
-
const header = tarBuffer.subarray(offset, offset + 512);
|
|
131
|
-
offset += 512;
|
|
132
|
-
|
|
133
|
-
const name = header.toString("utf-8", 0, 100).replace(/\0.*/g, "");
|
|
134
|
-
const sizeStr = header.toString("utf-8", 124, 136).replace(/\0.*/g, "").trim();
|
|
135
|
-
const size = parseInt(sizeStr, 8);
|
|
136
|
-
|
|
137
|
-
if (!name || isNaN(size)) break;
|
|
138
|
-
|
|
139
|
-
if (name === "open-plan-annotator" || name.endsWith("/open-plan-annotator")) {
|
|
140
|
-
return tarBuffer.subarray(offset, offset + size);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
offset += Math.ceil(size / 512) * 512;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
throw new Error("Binary 'open-plan-annotator' not found in archive");
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
async function main() {
|
|
150
|
-
const destDir = path.join(__dirname, "bin");
|
|
151
|
-
const destPath = path.join(destDir, "open-plan-annotator-binary");
|
|
152
|
-
const tempPath = `${destPath}.tmp-${process.pid}-${Date.now()}`;
|
|
153
|
-
|
|
154
|
-
// Skip if binary already exists
|
|
155
|
-
if (fs.existsSync(destPath)) {
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
console.error(`Downloading open-plan-annotator for ${getPlatformKey()}...`);
|
|
160
|
-
const archiveBuffer = await downloadVerifiedArchive();
|
|
161
|
-
|
|
162
|
-
try {
|
|
163
|
-
const binaryBuffer = extractBinaryFromTarGz(archiveBuffer);
|
|
164
|
-
|
|
165
|
-
if (!fs.existsSync(destDir)) {
|
|
166
|
-
fs.mkdirSync(destDir, { recursive: true });
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
fs.writeFileSync(tempPath, binaryBuffer, { mode: 0o755 });
|
|
170
|
-
fs.renameSync(tempPath, destPath);
|
|
171
|
-
fs.chmodSync(destPath, 0o755);
|
|
172
|
-
console.error(`Installed open-plan-annotator to ${destPath}`);
|
|
173
|
-
} catch (err) {
|
|
174
|
-
try {
|
|
175
|
-
fs.unlinkSync(tempPath);
|
|
176
|
-
} catch {
|
|
177
|
-
// Temp file may not exist
|
|
178
|
-
}
|
|
179
|
-
throw err;
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
function shouldSkipInstall() {
|
|
184
|
-
return Boolean(process.env.OPEN_PLAN_ANNOTATOR_SKIP_INSTALL || process.env.npm_config_dev);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
function runCli() {
|
|
188
|
-
if (shouldSkipInstall()) {
|
|
189
|
-
process.exit(0);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
main().catch((err) => {
|
|
193
|
-
console.error("Failed to install open-plan-annotator binary:", err.message);
|
|
194
|
-
console.error("You can try manually running: node", path.join(__dirname, "install.mjs"));
|
|
195
|
-
process.exit(1);
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
200
|
-
runCli();
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
export {
|
|
204
|
-
VERSION,
|
|
205
|
-
PLATFORM_ASSET_BASENAME_MAP as PLATFORM_MAP,
|
|
206
|
-
getPlatformKey,
|
|
207
|
-
getReleaseApiUrl,
|
|
208
|
-
fetch,
|
|
209
|
-
fetchJson,
|
|
210
|
-
sha256Hex,
|
|
211
|
-
parseChecksumManifest,
|
|
212
|
-
selectChecksumAsset,
|
|
213
|
-
resolveReleaseAssetAndChecksum,
|
|
214
|
-
extractBinaryFromTarGz,
|
|
215
|
-
downloadVerifiedArchive,
|
|
216
|
-
shouldSkipInstall,
|
|
217
|
-
main,
|
|
218
|
-
};
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
export interface ReleaseAsset {
|
|
2
|
-
name: string;
|
|
3
|
-
browser_download_url: string;
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
export declare const REPO: string;
|
|
7
|
-
export declare const PLATFORM_ASSET_BASENAME_MAP: Record<string, string>;
|
|
8
|
-
|
|
9
|
-
export declare function getPlatformKey(platform?: string, arch?: string): string;
|
|
10
|
-
export declare function getPlatformAssetArchiveName(platformKey?: string): string | null;
|
|
11
|
-
export declare function parseChecksumManifest(manifestText: string): Map<string, string>;
|
|
12
|
-
export declare function selectChecksumAsset(assets: ReleaseAsset[]): ReleaseAsset | null;
|
package/shared/releaseAssets.mjs
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
const REPO = "ndom91/open-plan-annotator";
|
|
2
|
-
|
|
3
|
-
const PLATFORM_ASSET_BASENAME_MAP = {
|
|
4
|
-
"darwin-arm64": "open-plan-annotator-darwin-arm64",
|
|
5
|
-
"darwin-x64": "open-plan-annotator-darwin-x64",
|
|
6
|
-
"linux-x64": "open-plan-annotator-linux-x64",
|
|
7
|
-
"linux-arm64": "open-plan-annotator-linux-arm64",
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
function getPlatformKey(platform = process.platform, arch = process.arch) {
|
|
11
|
-
return `${platform}-${arch}`;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function getPlatformAssetArchiveName(platformKey = getPlatformKey()) {
|
|
15
|
-
const assetBaseName = PLATFORM_ASSET_BASENAME_MAP[platformKey];
|
|
16
|
-
if (!assetBaseName) {
|
|
17
|
-
return null;
|
|
18
|
-
}
|
|
19
|
-
return `${assetBaseName}.tar.gz`;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function parseChecksumManifest(manifestText) {
|
|
23
|
-
const checksums = new Map();
|
|
24
|
-
|
|
25
|
-
for (const rawLine of manifestText.split(/\r?\n/)) {
|
|
26
|
-
const line = rawLine.trim();
|
|
27
|
-
if (!line || line.startsWith("#")) continue;
|
|
28
|
-
|
|
29
|
-
const bsdStyle = line.match(/^SHA256\s*\(([^)]+)\)\s*=\s*([a-fA-F0-9]{64})$/);
|
|
30
|
-
if (bsdStyle) {
|
|
31
|
-
checksums.set(bsdStyle[1].trim(), bsdStyle[2].toLowerCase());
|
|
32
|
-
continue;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const gnuStyle = line.match(/^([a-fA-F0-9]{64})\s+[* ]?(.+)$/);
|
|
36
|
-
if (gnuStyle) {
|
|
37
|
-
checksums.set(gnuStyle[2].trim(), gnuStyle[1].toLowerCase());
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
return checksums;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function selectChecksumAsset(assets) {
|
|
45
|
-
const checksumAssets = assets
|
|
46
|
-
.filter((asset) => {
|
|
47
|
-
const lower = asset.name.toLowerCase();
|
|
48
|
-
return (
|
|
49
|
-
(lower.includes("sha256") || lower.includes("checksum")) &&
|
|
50
|
-
(lower.endsWith(".txt") || lower.endsWith(".sha256") || lower.endsWith(".sha256sum") || lower.endsWith(".sha256sums"))
|
|
51
|
-
);
|
|
52
|
-
})
|
|
53
|
-
.sort((a, b) => a.name.localeCompare(b.name));
|
|
54
|
-
|
|
55
|
-
return checksumAssets[0] ?? null;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export {
|
|
59
|
-
REPO,
|
|
60
|
-
PLATFORM_ASSET_BASENAME_MAP,
|
|
61
|
-
getPlatformKey,
|
|
62
|
-
getPlatformAssetArchiveName,
|
|
63
|
-
parseChecksumManifest,
|
|
64
|
-
selectChecksumAsset,
|
|
65
|
-
};
|