heedvane 0.3.3 → 0.3.4
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 +41 -9
- package/dist/backend/credentials.d.ts +2 -0
- package/dist/backend/credentials.js +13 -0
- package/dist/backend/repo-remote.js +2 -1
- package/dist/backend/sign-in-change.js +3 -2
- package/dist/cli.d.ts +2 -1
- package/dist/cli.js +325 -118
- package/dist/clients/definitions.d.ts +7 -3
- package/dist/clients/definitions.js +27 -7
- package/dist/clients/hermes-plugin.d.ts +5 -20
- package/dist/clients/hermes-plugin.js +97 -71
- package/dist/clients/hook-files.d.ts +22 -0
- package/dist/clients/hook-files.js +39 -0
- package/dist/clients/pi-extension.d.ts +9 -0
- package/dist/clients/pi-extension.js +41 -0
- package/dist/commands/heedvane.js +18 -3
- package/dist/commands/mcp-stub.d.ts +1 -0
- package/dist/commands/mcp-stub.js +44 -0
- package/dist/dispatchers/posix.d.ts +16 -19
- package/dist/dispatchers/posix.js +71 -91
- package/dist/doctor.d.ts +2 -0
- package/dist/doctor.js +96 -28
- package/dist/guard.d.ts +73 -0
- package/dist/guard.js +302 -0
- package/dist/heed-file.d.ts +5 -2
- package/dist/heed-file.js +7 -7
- package/dist/hook-fallback.d.ts +73 -0
- package/dist/hook-fallback.js +61 -0
- package/dist/installer.d.ts +5 -5
- package/dist/installer.js +13 -12
- package/dist/listener/background.d.ts +48 -4
- package/dist/listener/background.js +153 -19
- package/dist/listener/endpoint.d.ts +26 -3
- package/dist/listener/endpoint.js +55 -10
- package/dist/listener/forward.d.ts +7 -1
- package/dist/listener/forward.js +40 -12
- package/dist/listener/serve.d.ts +37 -0
- package/dist/listener/serve.js +196 -32
- package/dist/listener/server.d.ts +9 -0
- package/dist/listener/server.js +59 -22
- package/dist/listener/spool.d.ts +2 -17
- package/dist/listener/spool.js +6 -3
- package/dist/mcp.d.ts +23 -4
- package/dist/mcp.js +124 -22
- package/dist/plan.d.ts +35 -6
- package/dist/plan.js +125 -40
- package/dist/render.js +2 -1
- package/dist/reply/harness-output.d.ts +9 -0
- package/dist/reply/harness-output.js +13 -0
- package/dist/skill/content.d.ts +2 -2
- package/dist/skill/content.js +6 -3
- package/dist/status.js +1 -1
- package/dist/steering/rules.js +4 -2
- package/dist/uninstall.d.ts +7 -2
- package/dist/uninstall.js +49 -44
- package/package.json +10 -3
- package/skills/heedvane-hooks/SKILL.md +3 -3
- package/skills/heedvane-hooks/references/events.md +3 -0
- package/dist/dispatchers/windows.d.ts +0 -15
- package/dist/dispatchers/windows.js +0 -50
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# heedvane
|
|
2
2
|
|
|
3
3
|
Connect the coding agents on your machine to your Heedvane organization: guardrails, approved-skill
|
|
4
|
-
checks and team memory for Claude Code, Codex, Cursor, Gemini CLI, Hermes, OpenCode and more.
|
|
4
|
+
checks and team memory for Claude Code, Codex, Cursor, Gemini CLI, Hermes, OpenCode, Pi and more.
|
|
5
5
|
|
|
6
6
|
```sh
|
|
7
7
|
npx heedvane
|
|
@@ -9,7 +9,7 @@ npx heedvane
|
|
|
9
9
|
|
|
10
10
|
The installer shows the agents it found on your machine. Tick the ones you want, choose how much leaves
|
|
11
11
|
your machine, choose what Heedvane adds and does, confirm. It installs, starts Heedvane in the background and signs you in through the
|
|
12
|
-
browser. It needs Node.js 22 or newer.
|
|
12
|
+
browser. It needs Node.js 22.5 or newer.
|
|
13
13
|
|
|
14
14
|
Your dashboard's Agents page has the same command with your server and organization filled in:
|
|
15
15
|
|
|
@@ -38,8 +38,14 @@ Scripts choose with `--mode look-but-dont-keep` or `--mode metrics-only`. The ch
|
|
|
38
38
|
removing that Node version). The agent signs in
|
|
39
39
|
through the browser the first time it uses it. It replaces the `heedvane` and
|
|
40
40
|
`heedvane-findings` entries an earlier version added.
|
|
41
|
-
- Starts Heedvane's local listener, which answers the hooks.
|
|
42
|
-
|
|
41
|
+
- Starts Heedvane's local listener, which answers the hooks. Every command makes sure it runs your current settings
|
|
42
|
+
and sign-in: it replaces one from an older version and keeps a newer one. After you edit `heed.yaml` by hand, run
|
|
43
|
+
`npx heedvane listener restart`. After a reboot the hooks start it again by themselves, at most once a minute (on
|
|
44
|
+
Windows they run in Git Bash, from Git for Windows). Set `HEED_LISTENER_AUTOSTART=0` to turn that off. While the listener cannot answer, Claude Code,
|
|
45
|
+
Codex, Gemini CLI and Pi tell you once per session that actions are not being checked. The listener trusts its
|
|
46
|
+
machine: run your agents under one OS user per machine, and give each user on a shared server its own container or VM.
|
|
47
|
+
If you pin `HEED_AGENT_HOOK_PORT` and `HEED_AGENT_HOOK_TOKEN` in an agent's environment, pin `HEED_AGENT_HOOK_PROOF`
|
|
48
|
+
from `~/.heed/endpoint.env` too; without it every hook runs Node and waits a quarter second before it is answered.
|
|
43
49
|
|
|
44
50
|
Run `npx heedvane connect --dry-run` to see every file it would touch without writing anything.
|
|
45
51
|
|
|
@@ -49,22 +55,47 @@ Agents that run unattended (a factory looping on a server, a CI job, a container
|
|
|
49
55
|
package without the installer:
|
|
50
56
|
|
|
51
57
|
```sh
|
|
52
|
-
#
|
|
58
|
+
# Install the hooks and Heedvane's MCP connection (also fine while the image is built)
|
|
53
59
|
npx -y heedvane connect --server https://app.heedvane.com --org <organization id> --agent claude --yes
|
|
54
60
|
|
|
55
|
-
#
|
|
56
|
-
|
|
61
|
+
# Where the agent runs, with HEEDVANE_ORG_KEY set from your secret store: sign in with the key.
|
|
62
|
+
# This also switches Heedvane's MCP connection to sending that key.
|
|
63
|
+
printf '%s' "$HEEDVANE_ORG_KEY" | npx -y heedvane auth org
|
|
57
64
|
|
|
58
65
|
# Every agent run: Heedvane answers the hooks for exactly this run
|
|
59
|
-
npx heedvane run -- claude -p "fix the failing tests"
|
|
66
|
+
npx -y heedvane run -- claude -p "fix the failing tests"
|
|
60
67
|
```
|
|
61
68
|
|
|
69
|
+
Once an organization key is saved with `auth org` (or `HEEDVANE_ORG_KEY` is set), Heedvane's MCP connection
|
|
70
|
+
(named `heedvane`) sends the key from `HEEDVANE_ORG_KEY` as `Authorization: Bearer`, through the agent's own way
|
|
71
|
+
to read an environment variable: the key is never written to a config file, and no browser sign-in is needed.
|
|
72
|
+
The commands work in either order: `auth org` switches a connection that `connect` installed earlier. Keep
|
|
73
|
+
`HEEDVANE_ORG_KEY` set while the agent runs, because the agent reads it when it starts; `doctor` says when it is
|
|
74
|
+
not set. Claude Code, Codex, Cursor, Gemini CLI, Qwen Code, OpenCode, Droid, Kimi Code, Copilot CLI, Windsurf and Pi
|
|
75
|
+
support this; VS Code, Warp, Antigravity and Claude Desktop keep the browser sign-in.
|
|
76
|
+
|
|
62
77
|
`run` keeps the agent's terminal output and exit code, so your pipeline reads them as before. Heedvane's
|
|
63
78
|
own lines go to stderr and `~/.heed/listener.log`.
|
|
64
79
|
|
|
65
80
|
- Claude Code runs the hooks in print mode (`claude -p`), but not with `--bare`.
|
|
66
81
|
- Codex runs hooks only after they are trusted.
|
|
67
|
-
-
|
|
82
|
+
- Pi runs Heedvane's guard as an extension: `npx -y heedvane run -- pi -p "<your task>"`.
|
|
83
|
+
|
|
84
|
+
## Agents you build on Pi
|
|
85
|
+
|
|
86
|
+
Code built on Pi's core (`@earendil-works/pi-agent-core`) or on Pi Durable (`@earendil-works/pi-durable`) asks
|
|
87
|
+
Heedvane before each tool runs with the guard this package exports. Install `heedvane` beside your agent and add
|
|
88
|
+
one line:
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
import { beforeToolCall, beforeTool } from "heedvane/guard";
|
|
92
|
+
|
|
93
|
+
new Agent({ ...options, beforeToolCall }); // Pi's core
|
|
94
|
+
registry.install(defineExtension({ name: "heedvane", hooks: [hook(ToolTask, { beforeTool })] })); // Pi Durable
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Run the agent with `npx -y heedvane run -- <command>`, or on a machine where Heedvane's listener runs. When the listener cannot
|
|
98
|
+
answer, the guard lets the tool run, keeps the call for the listener and starts one, as the agents' hooks do.
|
|
68
99
|
|
|
69
100
|
## Everyday commands
|
|
70
101
|
|
|
@@ -73,6 +104,7 @@ own lines go to stderr and `~/.heed/listener.log`.
|
|
|
73
104
|
| `npx heedvane` | Opens the installer: change agents and features any time. |
|
|
74
105
|
| `npx heedvane doctor` | Checks the install and prints a fix for each problem. |
|
|
75
106
|
| `npx heedvane listener start` | Runs the listener in the foreground. |
|
|
107
|
+
| `npx heedvane listener restart` | Makes sure the background listener runs your current settings, after a hand edit of `heed.yaml`. |
|
|
76
108
|
| `npx heedvane run -- <command>` | Runs one agent with Heedvane, for CI and containers. |
|
|
77
109
|
| `npx heedvane uninstall` | Removes only the entries Heedvane added. `heed.yaml` stays. |
|
|
78
110
|
| `npx heedvane --help` | Lists every command and flag. |
|
|
@@ -3,6 +3,8 @@ import { type LoginTokenOptions } from "./login.js";
|
|
|
3
3
|
export type CredentialKind = "org";
|
|
4
4
|
export type Credentials = Partial<Record<CredentialKind, string>>;
|
|
5
5
|
export declare function credentialsPath(root: string): string;
|
|
6
|
+
/** True when this machine signs in with a saved organization key: a software factory (`auth org`), with no login, which would win. */
|
|
7
|
+
export declare function signsInWithOrgKey(root: string): boolean;
|
|
6
8
|
/** Store one key beside the others, 0600. */
|
|
7
9
|
export declare function saveCredential(root: string, kind: CredentialKind, key: string): Promise<void>;
|
|
8
10
|
/** Heedvane at `url` when this machine has a sign-in or a key now, else null. The credential is read again on every call. */
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* `npx heedvane auth org` in `<root>/credentials.json`, mode 0600, kept apart from heed.yaml (a file
|
|
5
5
|
* people share). A machine uses one of them: the login when there is one, else the key.
|
|
6
6
|
*/
|
|
7
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
7
8
|
import { readFile } from "node:fs/promises";
|
|
8
9
|
import { join } from "node:path";
|
|
9
10
|
import writeFileAtomic from "write-file-atomic";
|
|
@@ -33,6 +34,18 @@ async function readCredentials(root) {
|
|
|
33
34
|
}
|
|
34
35
|
return out;
|
|
35
36
|
}
|
|
37
|
+
/** True when this machine signs in with a saved organization key: a software factory (`auth org`), with no login, which would win. */
|
|
38
|
+
export function signsInWithOrgKey(root) {
|
|
39
|
+
if (existsSync(join(root, "login.json")))
|
|
40
|
+
return false;
|
|
41
|
+
try {
|
|
42
|
+
const value = JSON.parse(readFileSync(credentialsPath(root), "utf8"));
|
|
43
|
+
return isRecord(value) && typeof value["org"] === "string" && value["org"] !== "";
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return false; // no key saved, or a file that does not parse: no key to send
|
|
47
|
+
}
|
|
48
|
+
}
|
|
36
49
|
/** Store one key beside the others, 0600. */
|
|
37
50
|
export async function saveCredential(root, kind, key) {
|
|
38
51
|
const next = { ...(await readCredentials(root)), [kind]: key };
|
|
@@ -55,7 +55,8 @@ export function withoutCredentials(remote) {
|
|
|
55
55
|
export function repositoryName(remote) {
|
|
56
56
|
const trimmed = remote.trim();
|
|
57
57
|
const colon = trimmed.indexOf(":");
|
|
58
|
-
const
|
|
58
|
+
const origin = trimmed.includes("://") || colon <= 0 ? trimmed : `ssh://${trimmed.slice(0, colon)}/${trimmed.slice(colon + 1)}`;
|
|
59
|
+
const url = URL.canParse(origin) ? new URL(origin) : null; // not URL.parse: Node 22.0 has none
|
|
59
60
|
const path = url?.pathname.replace(/\/+$/, "").replace(/\.git$/i, "").replace(/^\/+/, "") ?? "";
|
|
60
61
|
return path.includes("/") ? path : "";
|
|
61
62
|
}
|
|
@@ -22,6 +22,7 @@ import { mkdir, readdir, readFile, rename, unlink } from "node:fs/promises";
|
|
|
22
22
|
import { join } from "node:path";
|
|
23
23
|
import writeFileAtomic from "write-file-atomic";
|
|
24
24
|
import { signInIdentity } from "./credentials.js";
|
|
25
|
+
import { OTHER_SIGN_IN_DIR } from "../hook-fallback.js";
|
|
25
26
|
import { isPaneSpoolFile, spoolDir } from "../listener/spool.js";
|
|
26
27
|
import { steeringPolicyPath } from "../steering/rules.js";
|
|
27
28
|
/** The file that records whose sign-in the spool, instructions and caches on this machine belong to. */
|
|
@@ -58,9 +59,9 @@ async function setAsideSignInLeftovers(root) {
|
|
|
58
59
|
const spool = spoolDir(root);
|
|
59
60
|
const names = (await readdir(spool).catch(() => [])).filter(isPaneSpoolFile);
|
|
60
61
|
if (names.length > 0)
|
|
61
|
-
await mkdir(join(spool,
|
|
62
|
+
await mkdir(join(spool, OTHER_SIGN_IN_DIR), { recursive: true, mode: 0o700 });
|
|
62
63
|
for (const name of names)
|
|
63
|
-
await rename(join(spool, name), join(spool,
|
|
64
|
+
await rename(join(spool, name), join(spool, OTHER_SIGN_IN_DIR, `${Date.now()}-${name}`));
|
|
64
65
|
await discardSteeringCaches(root);
|
|
65
66
|
}
|
|
66
67
|
/** The pre-rendered instructions and the cached policy of an earlier sign-in; the next sync writes the current one's. */
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { type ListenerOutcome } from "./listener/background.js";
|
|
2
3
|
import { type PromptStreams } from "./installer.js";
|
|
3
4
|
export interface MainOptions {
|
|
4
5
|
/** Env override (tests inject HEED_HOME fixtures). Defaults to process.env. */
|
|
@@ -16,7 +17,7 @@ export interface MainOptions {
|
|
|
16
17
|
/** Where the interactive installer reads keys and draws; defaults to the terminal (tests inject streams). */
|
|
17
18
|
prompts?: PromptStreams;
|
|
18
19
|
/** Starts the listener in the background for the installer; defaults to a detached `listener start` (tests pass a stand-in). */
|
|
19
|
-
|
|
20
|
+
ensureListener?: (home: string) => Promise<ListenerOutcome>;
|
|
20
21
|
}
|
|
21
22
|
declare function main(argv: string[], opts?: MainOptions): Promise<number>;
|
|
22
23
|
/** All of stdin as text (where `auth` reads its key). */
|