heedvane 0.0.0-stage → 0.2.0
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 +62 -2
- package/dist/apply.d.ts +104 -0
- package/dist/apply.js +388 -0
- package/dist/backend/client.d.ts +40 -0
- package/dist/backend/client.js +32 -0
- package/dist/backend/credentials.d.ts +15 -0
- package/dist/backend/credentials.js +76 -0
- package/dist/backend/login.d.ts +33 -0
- package/dist/backend/login.js +265 -0
- package/dist/backend/repo-remote.d.ts +15 -0
- package/dist/backend/repo-remote.js +61 -0
- package/dist/backend/sign-in-change.d.ts +12 -0
- package/dist/backend/sign-in-change.js +72 -0
- package/dist/best-effort.d.ts +4 -0
- package/dist/best-effort.js +25 -0
- package/dist/cli.d.ts +29 -0
- package/dist/cli.js +666 -0
- package/dist/clients/definitions.d.ts +113 -0
- package/dist/clients/definitions.js +427 -0
- package/dist/clients/hermes-plugin.d.ts +33 -0
- package/dist/clients/hermes-plugin.js +247 -0
- package/dist/commands/heedvane.d.ts +15 -0
- package/dist/commands/heedvane.js +74 -0
- package/dist/commands/memory.d.ts +10 -0
- package/dist/commands/memory.js +106 -0
- package/dist/config-doc.d.ts +97 -0
- package/dist/config-doc.js +465 -0
- package/dist/dispatchers/posix.d.ts +41 -0
- package/dist/dispatchers/posix.js +208 -0
- package/dist/dispatchers/snippets.d.ts +6 -0
- package/dist/dispatchers/snippets.js +39 -0
- package/dist/dispatchers/windows.d.ts +15 -0
- package/dist/dispatchers/windows.js +50 -0
- package/dist/doctor.d.ts +37 -0
- package/dist/doctor.js +303 -0
- package/dist/events/contract.d.ts +54 -0
- package/dist/events/contract.js +87 -0
- package/dist/events/normalize.d.ts +36 -0
- package/dist/events/normalize.js +143 -0
- package/dist/guards.d.ts +18 -0
- package/dist/guards.js +28 -0
- package/dist/heed-file.d.ts +78 -0
- package/dist/heed-file.js +316 -0
- package/dist/installer.d.ts +47 -0
- package/dist/installer.js +174 -0
- package/dist/listener/background.d.ts +8 -0
- package/dist/listener/background.js +36 -0
- package/dist/listener/endpoint.d.ts +26 -0
- package/dist/listener/endpoint.js +125 -0
- package/dist/listener/forward.d.ts +36 -0
- package/dist/listener/forward.js +257 -0
- package/dist/listener/serve.d.ts +2 -0
- package/dist/listener/serve.js +155 -0
- package/dist/listener/server.d.ts +48 -0
- package/dist/listener/server.js +251 -0
- package/dist/listener/spool.d.ts +28 -0
- package/dist/listener/spool.js +60 -0
- package/dist/mcp-server.d.ts +9 -0
- package/dist/mcp-server.js +187 -0
- package/dist/mcp.d.ts +57 -0
- package/dist/mcp.js +83 -0
- package/dist/memory/private-memory.d.ts +12 -0
- package/dist/memory/private-memory.js +54 -0
- package/dist/memory/table-of-contents.d.ts +10 -0
- package/dist/memory/table-of-contents.js +80 -0
- package/dist/memory/team-memory.d.ts +60 -0
- package/dist/memory/team-memory.js +54 -0
- package/dist/plan.d.ts +77 -0
- package/dist/plan.js +288 -0
- package/dist/render.d.ts +32 -0
- package/dist/render.js +132 -0
- package/dist/reply/harness-output.d.ts +57 -0
- package/dist/reply/harness-output.js +88 -0
- package/dist/reply/reply.d.ts +65 -0
- package/dist/reply/reply.js +169 -0
- package/dist/reply/verdict.d.ts +28 -0
- package/dist/reply/verdict.js +17 -0
- package/dist/skill/bundle.d.ts +23 -0
- package/dist/skill/bundle.js +102 -0
- package/dist/skill/content.d.ts +16 -0
- package/dist/skill/content.js +182 -0
- package/dist/skills/approved.d.ts +31 -0
- package/dist/skills/approved.js +123 -0
- package/dist/skills/check.d.ts +21 -0
- package/dist/skills/check.js +83 -0
- package/dist/state.d.ts +51 -0
- package/dist/state.js +118 -0
- package/dist/steering/rules.d.ts +49 -0
- package/dist/steering/rules.js +165 -0
- package/dist/steering/steer.d.ts +29 -0
- package/dist/steering/steer.js +64 -0
- package/dist/steering/violations.d.ts +27 -0
- package/dist/steering/violations.js +24 -0
- package/dist/uninstall.d.ts +23 -0
- package/dist/uninstall.js +159 -0
- package/package.json +35 -4
- package/skills/heedvane-hooks/SKILL.md +87 -0
- package/skills/heedvane-hooks/references/events.md +69 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Declarative table of supported coding-agent clients.
|
|
3
|
+
*
|
|
4
|
+
* One source of truth for every agent `heedvane-hook` can integrate with:
|
|
5
|
+
* detection marker, config targets (path + format + purpose), capabilities,
|
|
6
|
+
* and the agent's native hook-event set. Path fragments are home-relative;
|
|
7
|
+
* {@link supportedClients} resolves them into absolute per-platform paths
|
|
8
|
+
* from a pure `{ home, platform, env }` context (no process state, so the
|
|
9
|
+
* table is trivially testable against fixture homes).
|
|
10
|
+
*
|
|
11
|
+
* This module imports nothing outside `node:path` — the package must stay
|
|
12
|
+
* standalone-publishable (no intra-monorepo imports, no Effect).
|
|
13
|
+
*/
|
|
14
|
+
/** Env passed to resolvers: { home, platform, env } — pure, testable. */
|
|
15
|
+
export type Env = Record<string, string | undefined>;
|
|
16
|
+
/** Config document dialects the package knows how to parse and patch. */
|
|
17
|
+
export type ClientFormat = "json" | "toml" | "jsonc" | "opencode" | "vscode" | "yaml";
|
|
18
|
+
/** Integration channels per client. `plugin-install` = Hermes-style native plugin. */
|
|
19
|
+
export type Capability = "hooks" | "mcp" | "skill" | "plugin-install";
|
|
20
|
+
/** What a config target is written for. Skill bundles install to the universal
|
|
21
|
+
* `~/.agents/skills/` directory and therefore carry no per-client target. */
|
|
22
|
+
export type ConfigPurpose = "hooks" | "mcp" | "plugin";
|
|
23
|
+
/** Platforms whose path semantics the resolver implements. */
|
|
24
|
+
export type Platform = NodeJS.Platform;
|
|
25
|
+
/** Pure resolution context — callers pass fixture/real values explicitly. */
|
|
26
|
+
export interface ResolveContext {
|
|
27
|
+
/** User home directory (fixture or real). */
|
|
28
|
+
readonly home: string;
|
|
29
|
+
/** Platform whose path semantics to apply. */
|
|
30
|
+
readonly platform: Platform;
|
|
31
|
+
/** Environment variables (only the documented override names are read). */
|
|
32
|
+
readonly env: Readonly<Record<string, string | undefined>>;
|
|
33
|
+
}
|
|
34
|
+
/** Root a client's directory or detection marker is resolved against. */
|
|
35
|
+
export type ClientPathBase = "home" | "xdg-config" | "platform-config";
|
|
36
|
+
/** Base a writable config target is resolved against. */
|
|
37
|
+
export type PathBase = "client" | "home" | "platform-config";
|
|
38
|
+
/** One writable config file for a client. */
|
|
39
|
+
export interface ConfigTarget {
|
|
40
|
+
/** Path segments relative to {@link PathBase}. */
|
|
41
|
+
readonly segments: readonly string[];
|
|
42
|
+
readonly base: PathBase;
|
|
43
|
+
readonly format: ClientFormat;
|
|
44
|
+
readonly purpose: ConfigPurpose;
|
|
45
|
+
/** Honest caveat surfaced by plan/doctor instead of a fake write. */
|
|
46
|
+
readonly limitation?: string;
|
|
47
|
+
}
|
|
48
|
+
/** A resolved, absolute-path config target. */
|
|
49
|
+
export interface ResolvedConfigTarget {
|
|
50
|
+
readonly path: string;
|
|
51
|
+
readonly format: ClientFormat;
|
|
52
|
+
readonly purpose: ConfigPurpose;
|
|
53
|
+
readonly limitation?: string;
|
|
54
|
+
}
|
|
55
|
+
/** Static (unresolved) client definition — the declarative table row. */
|
|
56
|
+
export interface ClientDef {
|
|
57
|
+
readonly id: string;
|
|
58
|
+
readonly displayName: string;
|
|
59
|
+
/** Home-relative marker whose existence means "detected" (dir or file). */
|
|
60
|
+
readonly markerSegments: readonly string[];
|
|
61
|
+
readonly configs: readonly ConfigTarget[];
|
|
62
|
+
readonly capabilities: readonly Capability[];
|
|
63
|
+
/** The agent's native hook events. Empty = no native hooks. */
|
|
64
|
+
readonly events: readonly string[];
|
|
65
|
+
/** Root key MCP server entries are written under for this dialect. */
|
|
66
|
+
readonly mcpServersKey: string;
|
|
67
|
+
/** Directory base used for marker + `client`-base config targets. */
|
|
68
|
+
readonly base: ClientPathBase;
|
|
69
|
+
/** App name inside the per-platform config dir (base `platform-config`). */
|
|
70
|
+
readonly platformConfigAppName?: string;
|
|
71
|
+
/** Vendor env var that replaces the client directory itself. */
|
|
72
|
+
readonly homeOverrideEnv?: string;
|
|
73
|
+
/** Base for the marker when it differs from {@link ClientDef.base}. */
|
|
74
|
+
readonly markerBase?: ClientPathBase;
|
|
75
|
+
/** Honest capability caveat (e.g. Warp's hookless MCP-only story). */
|
|
76
|
+
readonly limitation?: string;
|
|
77
|
+
/** Mechanism note that is not a limitation (e.g. Hermes plugin install). */
|
|
78
|
+
readonly notes?: string;
|
|
79
|
+
/** How the hooks file holds one hook: Claude Code's `{ matcher, hooks: [...] }` group (default) or a bare `{ command }`. */
|
|
80
|
+
readonly hookEntries?: HookEntryShape;
|
|
81
|
+
/** A schema version the hooks file must declare at its top level (`version`). */
|
|
82
|
+
readonly hooksVersion?: number;
|
|
83
|
+
/** Another client whose installed hooks this one also runs; when both are managed, only that client's are installed. */
|
|
84
|
+
readonly runsHooksOf?: string;
|
|
85
|
+
/** Additional detection markers (e.g. GUI ADE app-support directory when CLI and GUI share configs). */
|
|
86
|
+
readonly altMarkers?: readonly {
|
|
87
|
+
readonly segments: readonly string[];
|
|
88
|
+
readonly base: ClientPathBase;
|
|
89
|
+
}[];
|
|
90
|
+
}
|
|
91
|
+
/** How a hooks file holds one hook command. */
|
|
92
|
+
export type HookEntryShape = "group" | "flat";
|
|
93
|
+
/** A fully resolved client: absolute marker + config paths for one platform. */
|
|
94
|
+
export interface ResolvedClient {
|
|
95
|
+
readonly id: string;
|
|
96
|
+
readonly displayName: string;
|
|
97
|
+
readonly markerPath: string;
|
|
98
|
+
readonly altMarkerPaths?: readonly string[];
|
|
99
|
+
readonly configs: readonly ResolvedConfigTarget[];
|
|
100
|
+
readonly capabilities: readonly Capability[];
|
|
101
|
+
readonly events: readonly string[];
|
|
102
|
+
readonly mcpServersKey: string;
|
|
103
|
+
readonly limitation?: string;
|
|
104
|
+
readonly notes?: string;
|
|
105
|
+
readonly hookEntries?: HookEntryShape;
|
|
106
|
+
readonly hooksVersion?: number;
|
|
107
|
+
readonly runsHooksOf?: string;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Resolve every client definition into absolute marker/config paths for the
|
|
111
|
+
* given context. Pure: reads only `ctx` — never `process.env`, `os`, or disk.
|
|
112
|
+
*/
|
|
113
|
+
export declare function supportedClients(ctx: ResolveContext): ResolvedClient[];
|
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Declarative table of supported coding-agent clients.
|
|
3
|
+
*
|
|
4
|
+
* One source of truth for every agent `heedvane-hook` can integrate with:
|
|
5
|
+
* detection marker, config targets (path + format + purpose), capabilities,
|
|
6
|
+
* and the agent's native hook-event set. Path fragments are home-relative;
|
|
7
|
+
* {@link supportedClients} resolves them into absolute per-platform paths
|
|
8
|
+
* from a pure `{ home, platform, env }` context (no process state, so the
|
|
9
|
+
* table is trivially testable against fixture homes).
|
|
10
|
+
*
|
|
11
|
+
* This module imports nothing outside `node:path` — the package must stay
|
|
12
|
+
* standalone-publishable (no intra-monorepo imports, no Effect).
|
|
13
|
+
*/
|
|
14
|
+
import { posix, win32 } from "node:path";
|
|
15
|
+
// Single owner of the canonical taxonomy: src/events/contract.ts.
|
|
16
|
+
import { CANONICAL_EVENTS } from "../events/contract.js";
|
|
17
|
+
/** Harness-neutral canonical taxonomy (v1). Native sets normalize into this.
|
|
18
|
+
* Single owner: src/events/contract.ts — re-exported here for the table. */
|
|
19
|
+
/** Claude Code's 14 native hook events (verified against its hook contract). */
|
|
20
|
+
const CLAUDE_EVENTS = [
|
|
21
|
+
"SessionStart",
|
|
22
|
+
"SessionEnd",
|
|
23
|
+
"UserPromptSubmit",
|
|
24
|
+
"PreToolUse",
|
|
25
|
+
"PostToolUse",
|
|
26
|
+
"PostToolUseFailure",
|
|
27
|
+
"PermissionRequest",
|
|
28
|
+
"Stop",
|
|
29
|
+
"StopFailure",
|
|
30
|
+
"SubagentStart",
|
|
31
|
+
"SubagentStop",
|
|
32
|
+
"TeammateIdle",
|
|
33
|
+
"PreCompact",
|
|
34
|
+
"PostCompact",
|
|
35
|
+
];
|
|
36
|
+
/** Codex's 11 native hook events (Claude's set minus TeammateIdle/PostCompact). */
|
|
37
|
+
const CODEX_EVENTS = [
|
|
38
|
+
"SessionStart",
|
|
39
|
+
"SessionEnd",
|
|
40
|
+
"UserPromptSubmit",
|
|
41
|
+
"PreToolUse",
|
|
42
|
+
"PostToolUse",
|
|
43
|
+
"PostToolUseFailure",
|
|
44
|
+
"PermissionRequest",
|
|
45
|
+
"Stop",
|
|
46
|
+
"StopFailure",
|
|
47
|
+
"SubagentStart",
|
|
48
|
+
"SubagentStop",
|
|
49
|
+
];
|
|
50
|
+
/** Gemini CLI's own hook event names; its registry skips any other key (https://geminicli.com/docs/hooks/reference/). */
|
|
51
|
+
const GEMINI_EVENTS = [
|
|
52
|
+
"SessionStart",
|
|
53
|
+
"SessionEnd",
|
|
54
|
+
"BeforeAgent",
|
|
55
|
+
"AfterAgent",
|
|
56
|
+
"BeforeTool",
|
|
57
|
+
"AfterTool",
|
|
58
|
+
"PreCompress",
|
|
59
|
+
"Notification",
|
|
60
|
+
];
|
|
61
|
+
/** Cursor's camelCase hook event names (https://cursor.com/docs/hooks). */
|
|
62
|
+
const CURSOR_EVENTS = [
|
|
63
|
+
"sessionStart",
|
|
64
|
+
"sessionEnd",
|
|
65
|
+
"beforeSubmitPrompt",
|
|
66
|
+
"preToolUse",
|
|
67
|
+
"postToolUse",
|
|
68
|
+
"stop",
|
|
69
|
+
"subagentStop",
|
|
70
|
+
];
|
|
71
|
+
/** Kimi Code documents Claude-style PascalCase hook events in config.toml. */
|
|
72
|
+
const KIMI_EVENTS = ["PreToolUse", "PostToolUse"];
|
|
73
|
+
const settings = (segments, purpose) => ({
|
|
74
|
+
segments,
|
|
75
|
+
base: "client",
|
|
76
|
+
format: "json",
|
|
77
|
+
purpose,
|
|
78
|
+
});
|
|
79
|
+
/**
|
|
80
|
+
* The declarative table. First-class rows (claude, codex, gemini, cursor,
|
|
81
|
+
* warp, opencode, hermes) are behavior-tested; the remaining rows ride the
|
|
82
|
+
* same resolver and get snapshot tests only.
|
|
83
|
+
*/
|
|
84
|
+
const SUPPORTED_CLIENTS = [
|
|
85
|
+
{
|
|
86
|
+
id: "claude",
|
|
87
|
+
displayName: "Claude Code",
|
|
88
|
+
markerSegments: [".claude"],
|
|
89
|
+
base: "home",
|
|
90
|
+
platformConfigAppName: "Claude",
|
|
91
|
+
altMarkers: [{ segments: [], base: "platform-config" }],
|
|
92
|
+
homeOverrideEnv: "CLAUDE_CONFIG_DIR",
|
|
93
|
+
configs: [
|
|
94
|
+
settings(["settings.json"], "hooks"),
|
|
95
|
+
{ segments: [".claude.json"], base: "home", format: "json", purpose: "mcp" },
|
|
96
|
+
],
|
|
97
|
+
capabilities: ["hooks", "mcp", "skill"],
|
|
98
|
+
events: CLAUDE_EVENTS,
|
|
99
|
+
mcpServersKey: "mcpServers",
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
id: "codex",
|
|
103
|
+
displayName: "Codex",
|
|
104
|
+
markerSegments: [".codex"],
|
|
105
|
+
base: "home",
|
|
106
|
+
platformConfigAppName: "Codex",
|
|
107
|
+
altMarkers: [{ segments: [], base: "platform-config" }],
|
|
108
|
+
homeOverrideEnv: "CODEX_HOME",
|
|
109
|
+
configs: [
|
|
110
|
+
{ segments: ["hooks.json"], base: "client", format: "json", purpose: "hooks" },
|
|
111
|
+
{ segments: ["config.toml"], base: "client", format: "toml", purpose: "mcp" },
|
|
112
|
+
],
|
|
113
|
+
capabilities: ["hooks", "mcp", "skill"],
|
|
114
|
+
events: CODEX_EVENTS,
|
|
115
|
+
mcpServersKey: "mcp_servers",
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
id: "gemini",
|
|
119
|
+
displayName: "Gemini CLI",
|
|
120
|
+
markerSegments: [".gemini"],
|
|
121
|
+
base: "home",
|
|
122
|
+
configs: [
|
|
123
|
+
settings(["settings.json"], "hooks"),
|
|
124
|
+
settings(["settings.json"], "mcp"),
|
|
125
|
+
],
|
|
126
|
+
capabilities: ["hooks", "mcp", "skill"],
|
|
127
|
+
events: GEMINI_EVENTS,
|
|
128
|
+
mcpServersKey: "mcpServers",
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
id: "cursor",
|
|
132
|
+
displayName: "Cursor",
|
|
133
|
+
markerSegments: [".cursor"],
|
|
134
|
+
base: "home",
|
|
135
|
+
configs: [
|
|
136
|
+
{ segments: ["hooks.json"], base: "client", format: "json", purpose: "hooks" },
|
|
137
|
+
{ segments: ["mcp.json"], base: "client", format: "json", purpose: "mcp" },
|
|
138
|
+
],
|
|
139
|
+
capabilities: ["hooks", "mcp", "skill"],
|
|
140
|
+
events: CURSOR_EVENTS,
|
|
141
|
+
mcpServersKey: "mcpServers",
|
|
142
|
+
hookEntries: "flat",
|
|
143
|
+
hooksVersion: 1,
|
|
144
|
+
// Cursor loads ~/.claude/settings.json hooks by default (https://cursor.com/docs/reference/third-party-hooks).
|
|
145
|
+
runsHooksOf: "claude",
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
id: "warp",
|
|
149
|
+
displayName: "Warp",
|
|
150
|
+
markerSegments: [".warp"],
|
|
151
|
+
base: "home",
|
|
152
|
+
configs: [
|
|
153
|
+
{
|
|
154
|
+
segments: [".mcp.json"],
|
|
155
|
+
base: "client",
|
|
156
|
+
format: "json",
|
|
157
|
+
purpose: "mcp",
|
|
158
|
+
limitation: "Warp has no native hook events (hooks unavailable); ~/.warp/.mcp.json is project-level, and user-level MCP requires explicit in-app approval of the import.",
|
|
159
|
+
},
|
|
160
|
+
],
|
|
161
|
+
capabilities: ["mcp", "skill"],
|
|
162
|
+
events: [],
|
|
163
|
+
mcpServersKey: "mcpServers",
|
|
164
|
+
limitation: "Warp exposes no native hook events — policies ship via MCP only, and the MCP import needs explicit user approval in Warp.",
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
id: "opencode",
|
|
168
|
+
displayName: "OpenCode",
|
|
169
|
+
markerSegments: ["opencode"],
|
|
170
|
+
base: "xdg-config",
|
|
171
|
+
configs: [
|
|
172
|
+
{ segments: ["opencode.json"], base: "client", format: "opencode", purpose: "hooks" },
|
|
173
|
+
{ segments: ["opencode.json"], base: "client", format: "opencode", purpose: "mcp" },
|
|
174
|
+
],
|
|
175
|
+
capabilities: ["hooks", "mcp", "skill"],
|
|
176
|
+
events: [
|
|
177
|
+
"session_start",
|
|
178
|
+
"session_end",
|
|
179
|
+
"user_prompt_submit",
|
|
180
|
+
"pre_tool_use",
|
|
181
|
+
"post_tool_use",
|
|
182
|
+
"permission_request",
|
|
183
|
+
],
|
|
184
|
+
mcpServersKey: "mcp",
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
id: "hermes",
|
|
188
|
+
displayName: "Hermes",
|
|
189
|
+
markerSegments: [".hermes"],
|
|
190
|
+
base: "home",
|
|
191
|
+
configs: [
|
|
192
|
+
{ segments: ["plugins", "heedvane-hooks"], base: "client", format: "yaml", purpose: "plugin" },
|
|
193
|
+
],
|
|
194
|
+
capabilities: ["plugin-install", "skill"],
|
|
195
|
+
events: [
|
|
196
|
+
"on_session_start",
|
|
197
|
+
"pre_llm_call",
|
|
198
|
+
"pre_tool_call",
|
|
199
|
+
"post_tool_call",
|
|
200
|
+
"pre_approval_request",
|
|
201
|
+
"post_approval_response",
|
|
202
|
+
"on_session_end",
|
|
203
|
+
"on_session_finalize",
|
|
204
|
+
],
|
|
205
|
+
mcpServersKey: "mcpServers",
|
|
206
|
+
notes: "Hermes integrates via a plugin install (~/.hermes/plugins/heedvane-hooks) using its native hook API — not a hooks.json/config.toml write.",
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
// --- Snapshot-tested rows (same table, same resolver, no bespoke tests) ---
|
|
210
|
+
id: "qwen-code",
|
|
211
|
+
displayName: "Qwen Code",
|
|
212
|
+
markerSegments: [".qwen"],
|
|
213
|
+
base: "home",
|
|
214
|
+
configs: [
|
|
215
|
+
settings(["settings.json"], "hooks"),
|
|
216
|
+
settings(["settings.json"], "mcp"),
|
|
217
|
+
],
|
|
218
|
+
capabilities: ["hooks", "mcp", "skill"],
|
|
219
|
+
events: CANONICAL_EVENTS,
|
|
220
|
+
mcpServersKey: "mcpServers",
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
id: "kimi",
|
|
224
|
+
displayName: "Kimi Code",
|
|
225
|
+
markerSegments: [".kimi-code"],
|
|
226
|
+
base: "home",
|
|
227
|
+
homeOverrideEnv: "KIMI_CODE_HOME",
|
|
228
|
+
configs: [
|
|
229
|
+
{ segments: ["config.toml"], base: "client", format: "toml", purpose: "hooks" },
|
|
230
|
+
{ segments: ["config.toml"], base: "client", format: "toml", purpose: "mcp" },
|
|
231
|
+
],
|
|
232
|
+
capabilities: ["hooks", "mcp", "skill"],
|
|
233
|
+
events: KIMI_EVENTS,
|
|
234
|
+
mcpServersKey: "mcp_servers",
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
id: "droid",
|
|
238
|
+
displayName: "Droid (Factory)",
|
|
239
|
+
markerSegments: [".factory"],
|
|
240
|
+
base: "home",
|
|
241
|
+
configs: [
|
|
242
|
+
{ segments: ["mcp.json"], base: "client", format: "json", purpose: "mcp" },
|
|
243
|
+
],
|
|
244
|
+
capabilities: ["mcp", "skill"],
|
|
245
|
+
events: [],
|
|
246
|
+
mcpServersKey: "mcpServers",
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
id: "antigravity",
|
|
250
|
+
displayName: "Antigravity",
|
|
251
|
+
markerSegments: [".gemini", "antigravity"],
|
|
252
|
+
altMarkers: [{ segments: [".gemini", "config"], base: "home" }],
|
|
253
|
+
base: "home",
|
|
254
|
+
configs: [
|
|
255
|
+
{ segments: [".gemini", "config", "hooks.json"], base: "home", format: "json", purpose: "hooks" },
|
|
256
|
+
{ segments: ["mcp_config.json"], base: "client", format: "json", purpose: "mcp" },
|
|
257
|
+
],
|
|
258
|
+
capabilities: ["hooks", "mcp", "skill"],
|
|
259
|
+
events: CLAUDE_EVENTS,
|
|
260
|
+
mcpServersKey: "mcpServers",
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
id: "copilot_cli",
|
|
264
|
+
displayName: "GitHub Copilot CLI",
|
|
265
|
+
markerSegments: [".copilot"],
|
|
266
|
+
base: "home",
|
|
267
|
+
homeOverrideEnv: "COPILOT_HOME",
|
|
268
|
+
configs: [
|
|
269
|
+
{ segments: ["mcp.json"], base: "client", format: "json", purpose: "mcp" },
|
|
270
|
+
],
|
|
271
|
+
capabilities: ["mcp", "skill"],
|
|
272
|
+
events: [],
|
|
273
|
+
mcpServersKey: "mcpServers",
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
id: "windsurf",
|
|
277
|
+
displayName: "Windsurf",
|
|
278
|
+
markerSegments: [".codeium", "windsurf"],
|
|
279
|
+
base: "home",
|
|
280
|
+
configs: [
|
|
281
|
+
{ segments: ["mcp_config.json"], base: "client", format: "json", purpose: "mcp" },
|
|
282
|
+
],
|
|
283
|
+
capabilities: ["mcp"],
|
|
284
|
+
events: [],
|
|
285
|
+
mcpServersKey: "mcpServers",
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
id: "vscode",
|
|
289
|
+
displayName: "VS Code",
|
|
290
|
+
markerSegments: [".vscode"],
|
|
291
|
+
markerBase: "home",
|
|
292
|
+
base: "platform-config",
|
|
293
|
+
platformConfigAppName: "Code",
|
|
294
|
+
configs: [
|
|
295
|
+
{ segments: ["User", "mcp.json"], base: "client", format: "vscode", purpose: "mcp" },
|
|
296
|
+
],
|
|
297
|
+
capabilities: ["mcp"],
|
|
298
|
+
events: [],
|
|
299
|
+
mcpServersKey: "servers",
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
id: "claude_desktop",
|
|
303
|
+
displayName: "Claude Desktop",
|
|
304
|
+
markerSegments: [],
|
|
305
|
+
base: "platform-config",
|
|
306
|
+
platformConfigAppName: "Claude",
|
|
307
|
+
configs: [
|
|
308
|
+
{ segments: ["claude_desktop_config.json"], base: "client", format: "json", purpose: "mcp" },
|
|
309
|
+
],
|
|
310
|
+
capabilities: ["mcp", "skill"],
|
|
311
|
+
events: [],
|
|
312
|
+
mcpServersKey: "mcpServers",
|
|
313
|
+
},
|
|
314
|
+
];
|
|
315
|
+
const pathImplFor = (platform) => (platform === "win32" ? win32 : posix);
|
|
316
|
+
function xdgConfigRoot(ctx, p) {
|
|
317
|
+
const override = ctx.env["XDG_CONFIG_HOME"];
|
|
318
|
+
return override ? absoluteOrHome(override, ctx, p) : p.join(ctx.home, ".config");
|
|
319
|
+
}
|
|
320
|
+
function platformConfigDir(def, ctx, p) {
|
|
321
|
+
const appName = def.platformConfigAppName;
|
|
322
|
+
if (!appName)
|
|
323
|
+
throw new Error(`Client ${def.id} uses base "platform-config" without platformConfigAppName`);
|
|
324
|
+
switch (ctx.platform) {
|
|
325
|
+
case "darwin":
|
|
326
|
+
return p.join(ctx.home, "Library", "Application Support", appName);
|
|
327
|
+
case "win32": {
|
|
328
|
+
const appData = ctx.env["APPDATA"] ?? p.join(ctx.home, "AppData", "Roaming");
|
|
329
|
+
return p.join(absoluteOrHome(appData, ctx, p), appName);
|
|
330
|
+
}
|
|
331
|
+
default:
|
|
332
|
+
return p.join(xdgConfigRoot(ctx, p), appName);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
/** Treat a relative env override as home-relative so resolution never throws. */
|
|
336
|
+
function absoluteOrHome(value, ctx, p) {
|
|
337
|
+
return p.isAbsolute(value) ? value : p.join(ctx.home, value);
|
|
338
|
+
}
|
|
339
|
+
/** The client's own directory (marker + `client`-base configs resolve here). */
|
|
340
|
+
function clientDir(def, ctx, p) {
|
|
341
|
+
switch (def.base) {
|
|
342
|
+
case "home": {
|
|
343
|
+
const override = def.homeOverrideEnv ? ctx.env[def.homeOverrideEnv] : undefined;
|
|
344
|
+
if (override)
|
|
345
|
+
return absoluteOrHome(override, ctx, p);
|
|
346
|
+
return p.join(ctx.home, ...def.markerSegments);
|
|
347
|
+
}
|
|
348
|
+
case "xdg-config":
|
|
349
|
+
return p.join(xdgConfigRoot(ctx, p), ...def.markerSegments);
|
|
350
|
+
case "platform-config":
|
|
351
|
+
return p.join(platformConfigDir(def, ctx, p), ...def.markerSegments);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
function resolveMarkerBase(target, ctx, p) {
|
|
355
|
+
const { def, markerBase, segments } = target;
|
|
356
|
+
switch (markerBase) {
|
|
357
|
+
case "home":
|
|
358
|
+
return p.join(ctx.home, ...segments);
|
|
359
|
+
case "xdg-config":
|
|
360
|
+
return p.join(xdgConfigRoot(ctx, p), ...segments);
|
|
361
|
+
case "platform-config":
|
|
362
|
+
return p.join(platformConfigDir(def, ctx, p), ...segments);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
function markerPath(def, ctx, p) {
|
|
366
|
+
const markerBase = def.markerBase ?? def.base;
|
|
367
|
+
if (markerBase === def.base)
|
|
368
|
+
return clientDir(def, ctx, p);
|
|
369
|
+
return resolveMarkerBase({ def, markerBase, segments: def.markerSegments }, ctx, p);
|
|
370
|
+
}
|
|
371
|
+
function resolveConfigTarget(def, target, resolve) {
|
|
372
|
+
const { ctx, p } = resolve;
|
|
373
|
+
let path;
|
|
374
|
+
switch (target.base) {
|
|
375
|
+
case "client": {
|
|
376
|
+
// When the marker lives outside the platform config dir (markerBase
|
|
377
|
+
// override), `client`-base targets resolve against the app config dir
|
|
378
|
+
// root itself, not the marker-relative client directory.
|
|
379
|
+
const base = def.base === "platform-config" && def.markerBase !== def.base
|
|
380
|
+
? platformConfigDir(def, ctx, p)
|
|
381
|
+
: clientDir(def, ctx, p);
|
|
382
|
+
path = p.join(base, ...target.segments);
|
|
383
|
+
break;
|
|
384
|
+
}
|
|
385
|
+
case "home":
|
|
386
|
+
path = p.join(ctx.home, ...target.segments);
|
|
387
|
+
break;
|
|
388
|
+
case "platform-config":
|
|
389
|
+
path = p.join(platformConfigDir(def, ctx, p), ...target.segments);
|
|
390
|
+
break;
|
|
391
|
+
}
|
|
392
|
+
return target.limitation === undefined
|
|
393
|
+
? { path, format: target.format, purpose: target.purpose }
|
|
394
|
+
: { path, format: target.format, purpose: target.purpose, limitation: target.limitation };
|
|
395
|
+
}
|
|
396
|
+
function assertSupportedPlatform(platform) {
|
|
397
|
+
if (platform !== "darwin" && platform !== "linux" && platform !== "win32") {
|
|
398
|
+
throw new Error(`Unsupported platform: ${String(platform)}`);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Resolve every client definition into absolute marker/config paths for the
|
|
403
|
+
* given context. Pure: reads only `ctx` — never `process.env`, `os`, or disk.
|
|
404
|
+
*/
|
|
405
|
+
export function supportedClients(ctx) {
|
|
406
|
+
assertSupportedPlatform(ctx.platform);
|
|
407
|
+
const p = pathImplFor(ctx.platform);
|
|
408
|
+
return SUPPORTED_CLIENTS.map((def) => ({
|
|
409
|
+
id: def.id,
|
|
410
|
+
displayName: def.displayName,
|
|
411
|
+
markerPath: markerPath(def, ctx, p),
|
|
412
|
+
...(def.altMarkers === undefined
|
|
413
|
+
? {}
|
|
414
|
+
: {
|
|
415
|
+
altMarkerPaths: def.altMarkers.map((m) => resolveMarkerBase({ def, markerBase: m.base, segments: m.segments }, ctx, p)),
|
|
416
|
+
}),
|
|
417
|
+
configs: def.configs.map((target) => resolveConfigTarget(def, target, { ctx, p })),
|
|
418
|
+
capabilities: def.capabilities,
|
|
419
|
+
events: def.events,
|
|
420
|
+
mcpServersKey: def.mcpServersKey,
|
|
421
|
+
...(def.limitation === undefined ? {} : { limitation: def.limitation }),
|
|
422
|
+
...(def.notes === undefined ? {} : { notes: def.notes }),
|
|
423
|
+
...(def.hookEntries === undefined ? {} : { hookEntries: def.hookEntries }),
|
|
424
|
+
...(def.hooksVersion === undefined ? {} : { hooksVersion: def.hooksVersion }),
|
|
425
|
+
...(def.runsHooksOf === undefined ? {} : { runsHooksOf: def.runsHooksOf }),
|
|
426
|
+
}));
|
|
427
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { HeedGatingConfig } from "../heed-file.js";
|
|
2
|
+
/**
|
|
3
|
+
* The hooks this plugin registers with the Hermes host. `pre_tool_call` and `pre_llm_call` are directive
|
|
4
|
+
* hooks: Hermes acts on what they return. `on_session_end` fires once per turn despite its name;
|
|
5
|
+
* `on_session_finalize` is the session's end (https://hermes-agent.nousresearch.com/docs/user-guide/features/hooks).
|
|
6
|
+
*/
|
|
7
|
+
export declare const HERMES_EVENTS: readonly ["on_session_start", "pre_llm_call", "pre_tool_call", "post_tool_call", "pre_approval_request", "post_approval_response", "on_session_end", "on_session_finalize"];
|
|
8
|
+
export type HermesPluginFile = {
|
|
9
|
+
relativePath: string;
|
|
10
|
+
content: string;
|
|
11
|
+
};
|
|
12
|
+
export type HermesPluginInstallStatus = "installed" | "skipped" | "unchanged";
|
|
13
|
+
/** heed.yaml `gating.mode`, baked into the plugin at setup as the POSIX dispatcher gets it. */
|
|
14
|
+
type GatingMode = HeedGatingConfig["mode"];
|
|
15
|
+
/** The two files that make up the Hermes plugin, ready to install. */
|
|
16
|
+
export declare function hermesPluginFiles(gating?: GatingMode): HermesPluginFile[];
|
|
17
|
+
/** Does `<home>/.hermes/plugins/heedvane-hooks/plugin.yaml` declare our plugin? Uninstall removes only then. */
|
|
18
|
+
export declare function hermesPluginOwned(home: string): Promise<boolean>;
|
|
19
|
+
/**
|
|
20
|
+
* Install the Hermes plugin into `<home>/.hermes/plugins/heedvane-hooks/`.
|
|
21
|
+
*
|
|
22
|
+
* - "skipped" — a foreign plugin owns the target directory. Whether Hermes is installed at all
|
|
23
|
+
* is the planner's decision (detected, or named with `--agent`).
|
|
24
|
+
* - "unchanged" — the dir is heed-owned and every file already matches.
|
|
25
|
+
* - "installed" — files were written (fresh install or update).
|
|
26
|
+
*
|
|
27
|
+
* A heed-owned directory is one whose plugin.yaml exists and contains
|
|
28
|
+
* `name: heedvane-hooks`. Differing heed-owned files are backed up to
|
|
29
|
+
* `<file>.heedvane.backup` before being replaced. Foreign plugin.yaml files
|
|
30
|
+
* are never overwritten.
|
|
31
|
+
*/
|
|
32
|
+
export declare function installHermesPlugin(home: string, gating?: GatingMode): Promise<HermesPluginInstallStatus>;
|
|
33
|
+
export {};
|