From 69912174e6f639eaff15f4d3c3224392975b9edc Mon Sep 17 00:00:00 2001 From: Michael Ramos Date: Fri, 28 Aug 2026 11:18:58 -0700 Subject: [PATCH] fix: run plugin from compiled local executable --- .gitignore | 1 + README.md | 5 ++++- herdr-plugin.toml | 15 +++++++++------ package.json | 3 ++- scripts/build-plugin.ts | 28 ++++++++++++++++++++++++++++ src/runtime.ts | 41 +++++++++++++++++++++++++++++++++++++++++ test/runtime.test.ts | 38 ++++++++++++++++++++++++++++++++++++++ 7 files changed, 123 insertions(+), 8 deletions(-) create mode 100644 scripts/build-plugin.ts create mode 100644 src/runtime.ts create mode 100644 test/runtime.test.ts diff --git a/.gitignore b/.gitignore index 2752eb9..b6fd139 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules/ .DS_Store +/bin/herdr-annotate.exe diff --git a/README.md b/README.md index eabc5a3..0934ca8 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,11 @@ Herdr Annotate adds comments to copied terminal text in [Herdr](https://github.c ## Requirements - Herdr 0.8.0 or later -- [Bun](https://bun.sh/) +- [Bun](https://bun.sh/) for installation and local development - macOS, Linux, or Windows +Installation builds a plugin-local executable. The Herdr server does not need Bun on its `PATH` after installation. + On Linux, install `wl-clipboard`, `xclip`, or `xsel` for clipboard access. On Windows, native Herdr plugin support is preview/best-effort. Bun must be on `PATH`. Clipboard access uses PowerShell; no extra clipboard package is required. The install, keybinding, configuration check, reload, and use instructions below also apply on Windows. @@ -83,6 +85,7 @@ Herdr Annotate reads text that Herdr copies to the system clipboard. The plugin ```sh bun install +bun run build:plugin bun test bun run typecheck herdr plugin link "$PWD" --enabled diff --git a/herdr-plugin.toml b/herdr-plugin.toml index 223da3a..2aa1299 100644 --- a/herdr-plugin.toml +++ b/herdr-plugin.toml @@ -1,30 +1,33 @@ id = "annotate" name = "Annotate" -version = "0.3.0" +version = "0.3.1" min_herdr_version = "0.8.0" description = "Comment on terminal selections and copy annotations as agent context." platforms = ["linux", "macos", "windows"] +[[build]] +command = ["bun", "run", "build:plugin"] + [[actions]] id = "capture" title = "Annotate selection" description = "Open a comment dialog for the current terminal selection." contexts = ["pane"] -command = ["bun", "src/capture.ts"] +command = ["./bin/herdr-annotate.exe", "capture"] [[actions]] id = "copy-context" title = "Copy annotations as context" description = "Copy all saved annotations to the clipboard as Markdown." contexts = ["global"] -command = ["bun", "src/export.ts"] +command = ["./bin/herdr-annotate.exe", "copy-context"] [[actions]] id = "manage" title = "Manage annotations" description = "Browse, copy, archive, restore, and delete annotations." contexts = ["global"] -command = ["bun", "src/open-manager.ts"] +command = ["./bin/herdr-annotate.exe", "manage"] [[panes]] id = "editor" @@ -32,7 +35,7 @@ title = "Annotate" placement = "popup" width = 88 height = 24 -command = ["bun", "src/editor.ts"] +command = ["./bin/herdr-annotate.exe", "editor"] [[panes]] id = "manager" @@ -40,4 +43,4 @@ title = "Annotations" placement = "popup" width = 100 height = 30 -command = ["bun", "src/manager.ts"] +command = ["./bin/herdr-annotate.exe", "manager"] diff --git a/package.json b/package.json index b1971bc..9585592 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,10 @@ { "name": "herdr-annotate", - "version": "0.3.0", + "version": "0.3.1", "private": true, "type": "module", "scripts": { + "build:plugin": "bun run scripts/build-plugin.ts", "test": "bun test", "typecheck": "bunx tsc --noEmit" }, diff --git a/scripts/build-plugin.ts b/scripts/build-plugin.ts new file mode 100644 index 0000000..2367906 --- /dev/null +++ b/scripts/build-plugin.ts @@ -0,0 +1,28 @@ +import fs from "node:fs"; +import path from "node:path"; + +const root = path.resolve(import.meta.dir, ".."); +const outputDirectory = path.join(root, "bin"); +const output = path.join(outputDirectory, "herdr-annotate.exe"); +const entrypoint = path.join(root, "src", "runtime.ts"); + +fs.mkdirSync(outputDirectory, { recursive: true }); + +const build = Bun.spawnSync({ + cmd: [ + process.execPath, + "build", + "--compile", + "--reject-unresolved", + "--no-compile-autoload-dotenv", + "--no-compile-autoload-bunfig", + "--outfile", + output, + entrypoint, + ], + cwd: root, + stdout: "inherit", + stderr: "inherit", +}); + +if (!build.success) process.exit(build.exitCode); diff --git a/src/runtime.ts b/src/runtime.ts new file mode 100644 index 0000000..56a0622 --- /dev/null +++ b/src/runtime.ts @@ -0,0 +1,41 @@ +type RuntimeCommand = "capture" | "copy-context" | "editor" | "manage" | "manager"; + +export {}; + +function parseRuntimeCommand(value: string | undefined): RuntimeCommand | undefined { + switch (value) { + case "capture": + case "copy-context": + case "editor": + case "manage": + case "manager": + return value; + default: + return undefined; + } +} + +const command = parseRuntimeCommand(process.argv[2]); + +switch (command) { + case "capture": + await import("./capture"); + break; + case "copy-context": + await import("./export"); + break; + case "editor": + await import("./editor"); + break; + case "manage": + await import("./open-manager"); + break; + case "manager": + await import("./manager"); + break; + default: + console.error( + "Usage: herdr-annotate.exe ", + ); + process.exit(2); +} diff --git a/test/runtime.test.ts b/test/runtime.test.ts new file mode 100644 index 0000000..778a8b8 --- /dev/null +++ b/test/runtime.test.ts @@ -0,0 +1,38 @@ +import { afterAll, describe, expect, test } from "bun:test"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import { spawnSync } from "node:child_process"; + +const root = path.resolve(import.meta.dir, ".."); +const binary = path.join(root, "bin", "herdr-annotate.exe"); +const temporaryRoot = fs.mkdtempSync(path.join(os.tmpdir(), "herdr-annotate-runtime-")); + +afterAll(() => fs.rmSync(temporaryRoot, { recursive: true, force: true })); + +describe("compiled plugin runtime", () => { + test("runs an action when Bun is absent from PATH", () => { + const built = spawnSync(process.execPath, ["run", "scripts/build-plugin.ts"], { + cwd: root, + encoding: "utf8", + }); + expect(built.status, built.stderr).toBe(0); + + const emptyPath = path.join(temporaryRoot, "empty-path"); + const state = path.join(temporaryRoot, "state"); + fs.mkdirSync(emptyPath); + + const runtime = spawnSync(binary, ["copy-context"], { + cwd: root, + encoding: "utf8", + env: { + ...process.env, + PATH: emptyPath, + HERDR_BIN_PATH: process.execPath, + HERDR_PLUGIN_STATE_DIR: state, + }, + }); + + expect(runtime.status, runtime.stderr).toBe(0); + }); +});