Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
node_modules/
.DS_Store
/bin/herdr-annotate.exe
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
15 changes: 9 additions & 6 deletions herdr-plugin.toml
Original file line number Diff line number Diff line change
@@ -1,43 +1,46 @@
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"
title = "Annotate"
placement = "popup"
width = 88
height = 24
command = ["bun", "src/editor.ts"]
command = ["./bin/herdr-annotate.exe", "editor"]

[[panes]]
id = "manager"
title = "Annotations"
placement = "popup"
width = 100
height = 30
command = ["bun", "src/manager.ts"]
command = ["./bin/herdr-annotate.exe", "manager"]
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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"
},
Expand Down
28 changes: 28 additions & 0 deletions scripts/build-plugin.ts
Original file line number Diff line number Diff line change
@@ -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);
41 changes: 41 additions & 0 deletions src/runtime.ts
Original file line number Diff line number Diff line change
@@ -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 <capture|copy-context|editor|manage|manager>",
);
process.exit(2);
}
38 changes: 38 additions & 0 deletions test/runtime.test.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});