@gitkraken/agent-tools 0.2.0 → 0.3.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/CHANGELOG.md +33 -3
- package/README.md +29 -0
- package/dist/agents.d.ts +11 -2
- package/dist/agents.js +1 -1
- package/dist/hooks/claude.d.ts +3 -2
- package/dist/hooks/claude.js +2 -3
- package/dist/hooks/errors.d.ts +4 -2
- package/dist/hooks/errors.js +1 -1
- package/dist/hooks/events.d.ts +11 -8
- package/dist/hooks/events.js +1 -1
- package/dist/hooks/inspect.d.ts +9 -0
- package/dist/hooks/inspect.js +1 -0
- package/dist/hooks/install.js +1 -1
- package/dist/hooks/registry.d.ts +26 -0
- package/dist/hooks/registry.js +1 -0
- package/dist/hooks/results.d.ts +23 -1
- package/dist/hooks/templates.d.ts +4 -2
- package/dist/hooks/templates.js +20 -9
- package/dist/hooks/validate.d.ts +19 -0
- package/dist/hooks/validate.js +1 -0
- package/dist/hooks/writers.d.ts +8 -3
- package/dist/hooks/writers.js +1 -1
- package/dist/index.d.ts +6 -3
- package/dist/index.js +1 -1
- package/dist/mcp/installer.js +2 -2
- package/dist/mcp/jsonc-edit.d.ts +8 -4
- package/dist/mcp/jsonc-edit.js +8 -2
- package/dist/mcp/uninstaller.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,10 +1,40 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
## 0.3.0
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
### Minor Changes
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
- [#194](https://github.com/gitkraken/core-tools/pull/194) [`6a05082`](https://github.com/gitkraken/core-tools/commit/6a05082e9efb35203bb209c51845539c4be25a00) Thanks [@JBUinfo](https://github.com/JBUinfo)! - A default `installJsonHooks` or `installHooks` install removes the GitKraken-owned entries for every event in the host's `unsafeEvents` already on disk (for Cursor, a stale `afterAgentThought` entry written by 0.1.x or 0.2.0), keeping other entries and events, so Cursor stops aborting every turn. A request that names `events` or `blockingEvents` does not prune.
|
|
8
|
+
|
|
9
|
+
- [#194](https://github.com/gitkraken/core-tools/pull/194) [`6a05082`](https://github.com/gitkraken/core-tools/commit/6a05082e9efb35203bb209c51845539c4be25a00) Thanks [@JBUinfo](https://github.com/JBUinfo)! - Cursor refuses blocking events like OpenCode and Pi: `installHooks` and `installJsonHooks` throw `HookConfigError` `blocking-unsupported` (`cursor does not support blocking hooks`) before touching the hooks file, because Cursor ignores a blocking decision. The check reads `noBlocking` from `hookClientInfo` for every host. A default Cursor install no longer writes `afterAgentThought`, which aborts every Cursor turn; requesting it with `events` still writes it.
|
|
10
|
+
|
|
11
|
+
- [#194](https://github.com/gitkraken/core-tools/pull/194) [`6a05082`](https://github.com/gitkraken/core-tools/commit/6a05082e9efb35203bb209c51845539c4be25a00) Thanks [@JBUinfo](https://github.com/JBUinfo)! - `eventManifest` is regenerated from gkcli's `events.json`. Entries gain `promptSubmitEvent`, `eventFlag` and `blockingWait`; Antigravity lists `SessionStart` as an event and as its session start, and has no session-end event (`sessionStartEvent` was empty and `sessionEndEvent` was `Stop`); Cursor skips `afterAgentThought` in a default install and has `promptSubmitEvent`; Pi and OpenCode event lists match gkcli, and OpenCode's `v1OnlyEvents` also lists `session.idle`, `session.diff`, `file.edited` and `permission.updated` while `v2EventAliases` no longer maps `session.status`. `hookHosts` is derived from the manifest, in gkcli's order.
|
|
12
|
+
|
|
13
|
+
- [#193](https://github.com/gitkraken/core-tools/pull/193) [`92a5945`](https://github.com/gitkraken/core-tools/commit/92a594597d013ca901ce47e8d023b0c0da1642b5) Thanks [@JBUinfo](https://github.com/JBUinfo)! - Hook registry: `hookHosts`, `hookClientInfo(host)` (`{ displayName, binaryNames, clientType?, noBlocking, unsafeEvents, blockingWait? }`, the values of gkcli's hook registry for the nine hosts) and `executableForHook(host, config)`, which returns the first of the host's `binaryNames` that `config.resolveExecutable` finds. `resolveHookClient` is backed by the registry with unchanged results.
|
|
14
|
+
|
|
15
|
+
- [#193](https://github.com/gitkraken/core-tools/pull/193) [`92a5945`](https://github.com/gitkraken/core-tools/commit/92a594597d013ca901ce47e8d023b0c0da1642b5) Thanks [@JBUinfo](https://github.com/JBUinfo)! - `hookCapabilities(config)` builds the `HookCapability[]` that `listAgents` merges, from the hook registry, `inspectHooks` and `config.resolveExecutable`, so hosts no longer assemble it. Its `detected` is PATH-only, as in gkcli.
|
|
16
|
+
|
|
17
|
+
- [#193](https://github.com/gitkraken/core-tools/pull/193) [`92a5945`](https://github.com/gitkraken/core-tools/commit/92a594597d013ca901ce47e8d023b0c0da1642b5) Thanks [@JBUinfo](https://github.com/JBUinfo)! - `HookConfigErrorCode` gains `unreadable`, reported by `inspectHooks` when the filesystem fails.
|
|
18
|
+
|
|
19
|
+
- [#193](https://github.com/gitkraken/core-tools/pull/193) [`92a5945`](https://github.com/gitkraken/core-tools/commit/92a594597d013ca901ce47e8d023b0c0da1642b5) Thanks [@JBUinfo](https://github.com/JBUinfo)! - `inspectHooks(host, config)` reads whether GitKraken hooks are registered without writing or removing anything, and returns `{ host, installed, usable, path, layout, events, blockingEvents, needsMigration, error? }`. A corrupt or unreadable file never throws: `error` carries the code. `installed` mirrors gkcli's `IsInstalled`, so Claude Code counts an enabled plugin even when its hooks file is corrupt or missing; `usable` is true only when `installed`, there is no `error`, at least one GitKraken event is registered and, for Claude Code, no plugin files are missing (a legacy plugin's own files are not checked, so a legacy install is usable only with `settings.json` hooks). For Claude Code, `installed` is true for any layout gkcli counts: a GitKraken entry in `settings.json` `hooks`, the legacy `gitkraken@local` or `gk@gitkraken` plugin enabled, a legacy record in `installed_plugins.json`, or `gitkraken-hooks@gitkraken` enabled; `needsMigration` is set for legacy state and for an enabled current plugin whose cached manifest, cached or marketplace hooks file, marketplace manifest or marketplace path is missing or wrong. `layout` is `file`, `plugin`, `legacy` or `none`, and `events` includes the blocking ones listed in `blockingEvents`.
|
|
20
|
+
|
|
21
|
+
- [#193](https://github.com/gitkraken/core-tools/pull/193) [`92a5945`](https://github.com/gitkraken/core-tools/commit/92a594597d013ca901ce47e8d023b0c0da1642b5) Thanks [@JBUinfo](https://github.com/JBUinfo)! - `installHooks` and `uninstallHooks` reject an invalid event with the `validateHookEvents` message, which now quotes the event and lists the valid ones (previously `<event> is not valid for <host>`).
|
|
22
|
+
|
|
23
|
+
- [#194](https://github.com/gitkraken/core-tools/pull/194) [`6a05082`](https://github.com/gitkraken/core-tools/commit/6a05082e9efb35203bb209c51845539c4be25a00) Thanks [@JBUinfo](https://github.com/JBUinfo)! - JSON hook entries match what gkcli writes. Antigravity entries carry `--event <name>` (before any blocking tail), Copilot blocking entries end with `--timeout 5s --blocking`, and a non-blocking Codex `SessionEnd` entry gets `timeout: 3`. Entries already on disk are rewritten on the next install.
|
|
24
|
+
|
|
25
|
+
- [#193](https://github.com/gitkraken/core-tools/pull/193) [`92a5945`](https://github.com/gitkraken/core-tools/commit/92a594597d013ca901ce47e8d023b0c0da1642b5) Thanks [@JBUinfo](https://github.com/JBUinfo)! - `listAgents` takes a third argument, `{ hookDetection?: 'merged' | 'path-only' }`. `path-only` makes a hook capability replace `detected` on every row that is not a shared multi-row host (so the MCP config-directory fallback no longer marks a hook row detected), matching gkcli's `internal/agents.List`. The default, `merged`, is the 0.2.0 behavior.
|
|
26
|
+
|
|
27
|
+
- [#245](https://github.com/gitkraken/core-tools/pull/245) [`6770dec`](https://github.com/gitkraken/core-tools/commit/6770dec81ff6a849ecdd9cb454a8e558b311943b) Thanks [@JBUinfo](https://github.com/JBUinfo)! - The OpenCode relay plugin is now template version 8 and the Pi extension version 3, so installed files regenerate on the next install. Relayed events carry `relay_instance`, `relay_seq` and `host_pid`, and the OpenCode plugin's v1-only list now includes `session.idle`, `session.diff`, `file.edited` and `permission.updated`, while `session.status` no longer aliases `session.updated`.
|
|
28
|
+
|
|
29
|
+
- [#193](https://github.com/gitkraken/core-tools/pull/193) [`92a5945`](https://github.com/gitkraken/core-tools/commit/92a594597d013ca901ce47e8d023b0c0da1642b5) Thanks [@JBUinfo](https://github.com/JBUinfo)! - `validateHookEvents(host, events)` throws `HookConfigError` (`invalid-event`) for the first event the host does not list, with the message `'<event>' is not a valid event for <host>. Valid events: <list>` that `gk ai hook` prints.
|
|
30
|
+
|
|
31
|
+
### Patch Changes
|
|
32
|
+
|
|
33
|
+
- [#254](https://github.com/gitkraken/core-tools/pull/254) [`a1cc87d`](https://github.com/gitkraken/core-tools/commit/a1cc87d942b2c66f6353b94aeb058280dc6e9225) Thanks [@JBUinfo](https://github.com/JBUinfo)! - Claude Code hook install and uninstall now edit only the GitKraken entries in `settings.json`, `known_marketplaces.json` and `installed_plugins.json`, keeping the formatting and comments of every other entry.
|
|
34
|
+
|
|
35
|
+
- [#193](https://github.com/gitkraken/core-tools/pull/193) [`92a5945`](https://github.com/gitkraken/core-tools/commit/92a594597d013ca901ce47e8d023b0c0da1642b5) Thanks [@JBUinfo](https://github.com/JBUinfo)! - `installJsonHooks` throws `HookConfigError` (`unsupported-host`) for a prototype key such as `constructor`, instead of a raw `TypeError`.
|
|
36
|
+
|
|
37
|
+
- [#193](https://github.com/gitkraken/core-tools/pull/193) [`92a5945`](https://github.com/gitkraken/core-tools/commit/92a594597d013ca901ce47e8d023b0c0da1642b5) Thanks [@JBUinfo](https://github.com/JBUinfo)! - `uninstallHooks` for Claude Code removes the legacy `gitkraken@local` or `gk@gitkraken` `enabledPlugins` entry when it is the only GitKraken state left, instead of returning without changes.
|
|
8
38
|
|
|
9
39
|
## [0.2.0] - 2026-10-12
|
|
10
40
|
|
package/README.md
CHANGED
|
@@ -116,6 +116,35 @@ try {
|
|
|
116
116
|
|
|
117
117
|
Only GitKraken-owned hooks are added or removed; other entries and keys in the hooks file are preserved, and a file that is not valid JSON (or, for the OpenCode and Pi relays, not written by GitKraken) is refused without being modified.
|
|
118
118
|
|
|
119
|
+
Inspect, list and validate without writing anything:
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
import {
|
|
123
|
+
executableForHook,
|
|
124
|
+
hookCapabilities,
|
|
125
|
+
hookClientInfo,
|
|
126
|
+
hookHosts,
|
|
127
|
+
inspectHooks,
|
|
128
|
+
listAgents,
|
|
129
|
+
validateHookEvents,
|
|
130
|
+
} from '@gitkraken/agent-tools';
|
|
131
|
+
|
|
132
|
+
const inspection = await inspectHooks('claude-code', config);
|
|
133
|
+
// { host, installed, usable, path, layout: 'file' | 'plugin' | 'legacy' | 'none', events, blockingEvents, needsMigration, error? }
|
|
134
|
+
|
|
135
|
+
// Never throws on a corrupt or unreadable file: `error` carries the code instead.
|
|
136
|
+
const agents = await listAgents(config, await hookCapabilities(config), { hookDetection: 'path-only' });
|
|
137
|
+
|
|
138
|
+
for (const host of hookHosts) {
|
|
139
|
+
hookClientInfo(host); // { displayName, binaryNames, clientType?, noBlocking, unsafeEvents, blockingWait? }
|
|
140
|
+
executableForHook(host, config); // the first of binaryNames that config.resolveExecutable finds, or undefined
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
validateHookEvents('codex', ['PreToolUse']); // throws HookConfigError ('invalid-event') for the first unknown event
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
`installed` mirrors gkcli's `IsInstalled`: hooks are registered, even if the Claude plugin's files are broken. `usable` additionally requires no `error`, at least one event and, for Claude Code, intact plugin files. `needsMigration` is true for legacy Claude state or plugin drift that a reinstall would fix. `hookCapabilities` builds the hook list for `listAgents` (`hooksInstalled` follows `installed`); `hookDetection: 'path-only'` makes a hook's PATH lookup replace `detected` on rows that are not shared multi-row hosts, as gkcli does.
|
|
147
|
+
|
|
119
148
|
## Client detection
|
|
120
149
|
|
|
121
150
|
```ts
|
package/dist/agents.d.ts
CHANGED
|
@@ -38,15 +38,24 @@ interface Agent {
|
|
|
38
38
|
hooksSupported: boolean;
|
|
39
39
|
hooksInstalled: boolean;
|
|
40
40
|
}
|
|
41
|
+
interface ListAgentsOptions {
|
|
42
|
+
/** `merged` (default) ORs a hook capability's `detected` into the MCP result; `path-only` replaces it, like gkcli. */
|
|
43
|
+
hookDetection?: 'merged' | 'path-only';
|
|
44
|
+
}
|
|
41
45
|
/**
|
|
42
46
|
* The unified list of agents, sorted by name: every MCP client, plus any
|
|
43
47
|
* hook capability the host passes in (see {@link HookCapability}). Mirrors
|
|
44
48
|
* gkcli's `internal/agents.List`.
|
|
45
49
|
*/
|
|
46
|
-
declare function listAgents(config: AgentHostConfig, hookCapabilities?: readonly HookCapability[]): Promise<Agent[]>;
|
|
50
|
+
declare function listAgents(config: AgentHostConfig, hookCapabilities?: readonly HookCapability[], options?: ListAgentsOptions): Promise<Agent[]>;
|
|
47
51
|
/** MCP row ids that report one hook host's capability. */
|
|
48
52
|
declare function mcpNamesForHook(host: HookHost): string[];
|
|
49
53
|
/** Resolve aliases accepted by `gk ai hook` to the backing hook registry id. */
|
|
50
54
|
declare function resolveHookClient(client: string): string;
|
|
55
|
+
/**
|
|
56
|
+
* One {@link HookCapability} per hook host, with PATH-only `detected`, for {@link listAgents}.
|
|
57
|
+
* `installed` is `inspectHooks().installed`, not `usable`, so a broken Claude plugin still prompts a repair.
|
|
58
|
+
*/
|
|
59
|
+
declare function hookCapabilities(config: AgentHostConfig): Promise<HookCapability[]>;
|
|
51
60
|
//#endregion
|
|
52
|
-
export { Agent, type Client, HookCapability, listAgents, mcpNamesForHook, resolveHookClient };
|
|
61
|
+
export { Agent, type Client, HookCapability, ListAgentsOptions, hookCapabilities, listAgents, mcpNamesForHook, resolveHookClient };
|
package/dist/agents.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{executableForHook as e,hookClientInfo as t,hookHosts as n}from"./hooks/registry.js";import{inspectHooks as r}from"./hooks/inspect.js";import{allClients as i,clientDisplayName as a,clientType as o,clientsForHost as s,detectClient as c,getClientInstallation as l}from"./mcp/clients.js";async function u(e,t=[],n={}){let r=new Map;for(let t of i()){let n=await c(t,e),i=await l(t,e);r.set(t,{name:t,type:o(t),displayName:a(t),detected:n.detected,executable:n.executablePath,detection:n,mcpSupported:!0,mcpInstalled:i.installed,mcpConfigPaths:i.paths,hooksSupported:!1,hooksInstalled:!1})}for(let e of t)for(let t of e.mcpNames){let i=r.get(t);i||(i={name:t,type:e.clientType??`unknown`,displayName:e.displayName,detected:!1,mcpSupported:!1,mcpInstalled:!1,mcpConfigPaths:[],hooksSupported:!1,hooksInstalled:!1},r.set(t,i)),i.hooksSupported=!0,i.hooksInstalled=e.installed,!(e.mcpNames.length>1)&&(i.detected=n.hookDetection===`path-only`?e.detected:i.detected||e.detected,i.executable||=e.executable)}return[...r.values()].sort((e,t)=>e.name.localeCompare(t.name))}function d(e){if(e===`claude-code`)return[`claude-cli`];let t=s(e);return t.length?t:[e]}function f(e){for(let t of n)if(d(t).includes(e))return t;return e}async function p(i){return Promise.all(n.map(async n=>{let a=t(n),o=e(n,i);return{mcpNames:d(n),displayName:a.displayName,clientType:a.clientType,detected:o!==void 0,executable:o,installed:(await r(n,i)).installed}}))}export{p as hookCapabilities,u as listAgents,d as mcpNamesForHook,f as resolveHookClient};
|
package/dist/hooks/claude.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AgentHostConfig } from "../config.js";
|
|
2
|
-
import { HookInstallResult, HookUninstallResult } from "./results.js";
|
|
2
|
+
import { HookInspection, HookInstallResult, HookUninstallResult } from "./results.js";
|
|
3
3
|
|
|
4
4
|
//#region src/hooks/claude.d.ts
|
|
5
5
|
interface ClaudeHookRequest {
|
|
@@ -12,5 +12,6 @@ interface ClaudeHookRequest {
|
|
|
12
12
|
}
|
|
13
13
|
declare function installClaudeHooks(request: ClaudeHookRequest, config: AgentHostConfig): Promise<HookInstallResult>;
|
|
14
14
|
declare function uninstallClaudeHooks(events: readonly string[] | undefined, config: AgentHostConfig): Promise<HookUninstallResult>;
|
|
15
|
+
declare function inspectClaudeHooks(config: AgentHostConfig): Promise<HookInspection>;
|
|
15
16
|
//#endregion
|
|
16
|
-
export { ClaudeHookRequest, installClaudeHooks, uninstallClaudeHooks };
|
|
17
|
+
export { ClaudeHookRequest, inspectClaudeHooks, installClaudeHooks, uninstallClaudeHooks };
|
package/dist/hooks/claude.js
CHANGED
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
import{joinPath as e}from"../config.js";import{
|
|
2
|
-
`)?
|
|
3
|
-
`)?f:`${f}\n`,o,{mode:384})}function A(t){return e(t,`plugins`,`installed_plugins.json`)}function j(e,t){return t===e||t.startsWith(`${e}/`)}function M(e,t){let n=e.replaceAll(`\\`,`/`);return t===`win32`?n.toLowerCase():n}function N(e,t,n,r=``){let i=t.filter(t=>t in e).map(e=>`/${e}`),a=m(e.plugins),o=M(r,n),s=o.slice(0,o.lastIndexOf(`/`));for(let e of t){if(!(e in a))continue;if(e!==d||!o){i.push(`/plugins/${e}`);continue}let t=a[e];if(!Array.isArray(t))continue;let r=t.flatMap((e,t)=>{let r=m(e).installPath;if(typeof r!=`string`)return[];let i=M(r,n);return i!==s&&j(s,i)&&!j(o,i)?[t]:[]});r.length&&r.length===t.length?i.push(`/plugins/${e}`):i.push(...r.reverse().map(t=>`/plugins/${e}/${t}`))}return i}async function P(e,t){let n=A(e),r=await t.fs.readFile(n);if(r!==null)try{return[r,h(r,n)]}catch{return}}async function F(e,t,r,a){let o=await P(t,e);if(!o)return;let s=A(t),[c,l]=o,u=c;for(let t of N(l,[...p,d],e.platform,a))u=n(u,t,s).text;u!==c&&await i(e.fs,s,u,r,{mode:384})}async function I(t,n){for(let r of[e(n,`.claude-plugin`,`plugin.json`),e(n,`plugin.json`)]){let e=await t.fs.readFile(r);if(e!==null)try{let t=m(JSON.parse(e)).name;return t===u||t===`gk`}catch{return!1}}return!1}async function L(t,n,i,a){let o=e(n,`plugins`,`cache`,l,u);for(let n of await t.fs.readDir(o)??[]){let s=e(o,n);n!==i&&await I(t,s)&&await r(t.fs,s,a,{recursive:!0})}}async function R(t,n,i,a=!1){let o=[e(n.source,`.claude-plugin`,`plugin.json`),e(n.source,`hooks`,`hooks.json`)];for(let e of o)await r(t.fs,e,i);for(let a of[e(n.source,`.claude-plugin`),e(n.source,`hooks`)])(await t.fs.readDir(a))?.length||await r(t.fs,a,i,{recursive:!0});(await t.fs.readDir(n.source))?.length||await r(t.fs,n.source,i,{recursive:!0});let s=e(n.marketplace,`plugins`);(await t.fs.readDir(s))?.length||await r(t.fs,s,i,{recursive:!0});let c=e(n.base,`plugins`,`cache`,l,u);for(let n of await t.fs.readDir(c)??[]){let a=e(c,n);await r(t.fs,e(a,`.claude-plugin`,`plugin.json`),i),await r(t.fs,e(a,`hooks`,`hooks.json`),i);for(let n of[e(a,`.claude-plugin`),e(a,`hooks`)])(await t.fs.readDir(n))?.length||await r(t.fs,n,i,{recursive:!0});(await t.fs.readDir(a))?.length||await r(t.fs,a,i,{recursive:!0})}(await t.fs.readDir(c))?.length||await r(t.fs,c,i,{recursive:!0});let d=e(n.marketplace,`.claude-plugin`,`marketplace.json`),f=await t.fs.readFile(d);!a&&f?.trimEnd()===JSON.stringify(b(),null,2)&&await r(t.fs,d,i);let p=e(n.marketplace,`.claude-plugin`);!a&&!(await t.fs.readDir(p))?.length&&await r(t.fs,p,i,{recursive:!0});let m=await t.fs.readDir(n.marketplace);!a&&!m?.length&&(m===null&&i.some(e=>e.path.startsWith(`${n.marketplace}/`))?(await t.fs.remove(n.marketplace,{recursive:!0}),i.push({path:n.marketplace,change:`removed`})):await r(t.fs,n.marketplace,i,{recursive:!0}))}async function z(t,n){return(await t.fs.readDir(e(n.marketplace,`plugins`))??[]).some(e=>e!==u)}function B(e,t){return Object.keys(e).some(e=>e!==`hooks`)||Object.values(t).some(Array.isArray)}async function V(t,n){let i=[],a=t.version||`0.0.0-dev`,c=y(n,a),d=e(c.source,`hooks`,`hooks.json`),p=h(await n.fs.readFile(d),d),v=g(p,d),x=h(await n.fs.readFile(c.known),c.known),C=c.settings;_(h(await n.fs.readFile(C),C),C);let w=s(t.executablePath,`claude-code`,n,!1);for(let e of t.events??[])S(v,e,w,!1);for(let e of t.blockingEvents??[])S(v,e,w,!0);p.hooks=v,await D(e(c.marketplace,`.claude-plugin`,`marketplace.json`),b(),n,i);for(let t of[c.source,c.cache])await D(e(t,`.claude-plugin`,`plugin.json`),{name:u,version:a,description:f},n,i),await D(e(t,`hooks`,`hooks.json`),p,n,i);return x[l]={...m(x[l]),source:{source:`directory`,path:c.marketplace},installLocation:c.marketplace,lastUpdated:t.now??new Date().toISOString().replace(/\.\d{3}Z$/,`Z`)},await D(c.known,x,n,i),await O(c.settings,c.marketplace,!0,o[`claude-code`].events,n,i),await F(n,c.base,i,c.cache),await r(n.fs,e(c.marketplace,`plugins`,`gk`),i,{recursive:!0}),await r(n.fs,e(c.base,`plugins`,`cache`,l,`gk`),i,{recursive:!0}),await L(n,c.base,a,i),{host:`claude-code`,path:d,events:[...t.events??[]],blockingEvents:[...t.blockingEvents??[]],files:i}}async function H(t,n){let i=[],a=y(n,`0.0.0-dev`),s=e(a.source,`hooks`,`hooks.json`),c=await n.fs.readFile(s),f=h(c,s),v=g(f,s),b=h(await n.fs.readFile(a.settings),a.settings);_(b,a.settings);let x=await n.fs.readFile(a.known),S={};try{x!==null&&(S=h(x,a.known))}catch{}let E=t??Object.keys(v),A=t??o[`claude-code`].events,j=w(v).filter(e=>E.includes(e));C(v,E),f.hooks=v;let M=w(v),I={host:`claude-code`,path:s,removedEvents:j,remainingEvents:M,files:i};j.length||(I.reason=c===null?`file-missing`:`not-installed`);let L=B(f,v);if(!j.length){if(t!==void 0||L)return I;let r=await P(a.base,n),i=!!r&&N(r[1],p,n.platform).length>0,o=e(a.base,`plugins`,`cache`,l,u);if(!(d in m(b.enabledPlugins)||l in S||i||T(b,A)||(await n.fs.readDir(o))?.length))return I}else if(M.length||L){await D(s,f,n,i);let t=e(a.base,`plugins`,`cache`,l,u);for(let r of await n.fs.readDir(t)??[])await D(e(t,r,`hooks`,`hooks.json`),f,n,i);return I}let V=await z(n,a);return V?await k(a.settings,A,n,i):await O(a.settings,a.marketplace,!1,A,n,i),l in S&&!V&&(delete S[l],Object.keys(S).length?await D(a.known,S,n,i):await r(n.fs,a.known,i)),await R(n,a,i,V),await F(n,a.base,i),I}export{V as installClaudeHooks,H as uninstallClaudeHooks};
|
|
1
|
+
import{joinPath as e}from"../config.js";import{removeTracked as t,writeFileTracked as n}from"../file-change.js";import{addAtPointer as r,removeAtPointer as i,removeAtPointerAndEmptyParent as a}from"../mcp/jsonc-edit.js";import{HookConfigError as o,hookErrorCode as s}from"./errors.js";import{eventManifest as c}from"./events.js";import{command as l}from"./writers.js";import{parse as u}from"jsonc-parser";const d=`gitkraken`,f=`gitkraken-hooks`,p=`${f}@${d}`,m=`Live AI session tracking for GitKraken products. See what Claude is doing in real time — current tool, active prompt, subagents — and optionally approve or deny tool calls from the UI.`,h=[`gitkraken@local`,`gk@gitkraken`];function g(e){return e&&typeof e==`object`&&!Array.isArray(e)?e:{}}function _(e,t){if(e===null||e.trim()===``)return{};let n=[],r=u(e,n,{allowTrailingComma:!0});if(n.length||!r||typeof r!=`object`||Array.isArray(r))throw new o(`invalid-json`,`${t} is not valid JSON; refusing to overwrite shared configuration`,t);return r}function v(e,t){if(e.hooks===void 0)return{};if(!e.hooks||typeof e.hooks!=`object`||Array.isArray(e.hooks))throw new o(`wrong-shape`,`${t} has an invalid hooks wrapper`,t);let n=e.hooks;for(let[e,r]of Object.entries(n))if(!Array.isArray(r)||!r.every(e=>{if(!e||typeof e!=`object`||Array.isArray(e))return!1;let t=e.hooks;return t===void 0||Array.isArray(t)&&t.every(e=>!!e&&typeof e==`object`&&!Array.isArray(e))}))throw new o(`wrong-shape`,`${t} has an invalid ${e} hook group`,t);return n}function y(e,t){for(let n of[`enabledPlugins`,`extraKnownMarketplaces`]){let r=e[n];if(r!==void 0&&(!r||typeof r!=`object`||Array.isArray(r)))throw new o(`wrong-shape`,`${t} has an invalid ${n} map`,t)}}function b(t){return t.paths.claudeConfigDir||e(t.paths.home,`.claude`)}function x(t,n){let r=b(t),i=e(r,`plugins`,`marketplaces`,d);return{base:r,marketplace:i,source:e(i,`plugins`,f),cache:e(r,`plugins`,`cache`,d,f,n),settings:e(r,`settings.json`),known:e(r,`plugins`,`known_marketplaces.json`)}}function S(){return{name:d,description:`Plugins from GitKraken to enhance AI coding workflows in GitKraken Desktop, GitLens, and other GitKraken products.`,owner:{name:`GitKraken`},plugins:[{name:f,source:`./plugins/${f}`,description:m}]}}function C(e){return typeof g(e).command==`string`&&g(e).command.includes(`ai hook run`)}function w(e,t,n,r){T(e,[t]);let i=Array.isArray(e[t])?[...e[t]]:[],a=i.findIndex(e=>(typeof e.matcher!=`string`||e.matcher===``)&&Array.isArray(e.hooks)&&e.hooks.length>0);a<0&&(a=i.length,i.push({matcher:``,hooks:[]}));let o={...i[a]};o.hooks=[...(Array.isArray(o.hooks)?o.hooks:[]).filter(e=>!C(e)),{type:`command`,command:n,...r?{timeout:86400}:{}}],i[a]=o,e[t]=i}function T(e,t){for(let n of t){let t=Array.isArray(e[n])?e[n]:[],r=!1,i=t.flatMap(e=>{if(!Array.isArray(e.hooks))return[e];let t=e.hooks.filter(e=>!C(e));return t.length===e.hooks.length?[e]:(r=!0,t.length?[{...e,hooks:t}]:[])});r&&(i.length?e[n]=i:delete e[n])}}function E(e,t=!1){let n=e=>C(e)&&(!t||g(e).command.endsWith(` --blocking`));return Object.keys(e).filter(t=>{let r=e[t];return Array.isArray(r)&&r.some(e=>Array.isArray(g(e).hooks)&&g(e).hooks.some(n))})}function D(e,t){let n=e.hooks;if(!n||typeof n!=`object`||Array.isArray(n))return;let r={...n};return T(r,t),Object.keys(n).every(e=>r[e]===n[e])?void 0:r}function O(e,t,n,a){let o=D(t,n);if(!o)return e;for(let[n,s]of Object.entries(t.hooks)){if(o[n]===s)continue;let t=`/hooks/${n.replace(/~/g,`~0`).replace(/\//g,`~1`)}`;e=o[n]===void 0?i(e,t,a).text:r(e,t,o[n],a)}return e}async function k(e,t,r,i){await n(r.fs,e,`${JSON.stringify(t,null,2)}\n`,i,{mode:384})}async function A(e,t,r,i){await n(e.fs,t,r.endsWith(`
|
|
2
|
+
`)?r:`${r}\n`,i,{mode:384})}const j={preserveSiblings:!0};function M(e,t,n,r){for(let i of n)e=a(e,`${t}/${i}`,r,j).text;return e}async function N(e,t,n,i,a,o){let s=await a.fs.readFile(e),c=_(s,e);if(y(c,e),!n&&s===null)return;let l=s===null||s.trim()===``;if(!n&&l){o.push({path:e,change:`unchanged`});return}let u=l?`{}`:O(s,c,i,e),f=`/extraKnownMarketplaces/${d}`;if(n){u=r(u,`/enabledPlugins/${p}`,!0,e,j),u=M(u,`/enabledPlugins`,h,e);let n={source:`directory`,path:t},i=g(c.extraKnownMarketplaces)[d];u=i&&typeof i==`object`&&!Array.isArray(i)?r(u,`${f}/source`,n,e,j):r(u,f,{source:n},e,j)}else u=M(u,`/enabledPlugins`,[...h,p],e),u=M(u,`/extraKnownMarketplaces`,[d],e);u===s?o.push({path:e,change:`unchanged`}):await A(a,e,u,o)}async function P(e,t,n,r){let i=await n.fs.readFile(e);if(i===null)return;let a=_(i,e);if(y(a,e),i.trim()===``){r.push({path:e,change:`unchanged`});return}let o=M(O(i,a,t,e),`/enabledPlugins`,[...h,p],e);o===i?r.push({path:e,change:`unchanged`}):await A(n,e,o,r)}function F(t){return e(t,`plugins`,`installed_plugins.json`)}function I(e,t){return t===e||t.startsWith(`${e}/`)}function L(e,t){let n=e.replaceAll(`\\`,`/`);return t===`win32`?n.toLowerCase():n}function R(e,t,n,r=``){let i=t.filter(t=>t in e).map(e=>`/${e}`),a=g(e.plugins),o=L(r,n),s=o.slice(0,o.lastIndexOf(`/`));for(let e of t){if(!(e in a))continue;if(e!==p||!o){i.push(`/plugins/${e}`);continue}let t=a[e];if(!Array.isArray(t))continue;let r=t.flatMap((e,t)=>{let r=g(e).installPath;if(typeof r!=`string`)return[];let i=L(r,n);return i!==s&&I(s,i)&&!I(o,i)?[t]:[]});r.length&&r.length===t.length?i.push(`/plugins/${e}`):i.push(...r.reverse().map(t=>`/plugins/${e}/${t}`))}return i}async function z(e,t){let n=F(e),r=await t.fs.readFile(n);if(r!==null)try{return[r,_(r,n)]}catch{return}}async function B(e,t,r,a){let o=await z(t,e);if(!o)return;let s=F(t),[c,l]=o,u=c;for(let t of R(l,[...h,p],e.platform,a))u=i(u,t,s,j).text;u!==c&&await n(e.fs,s,u,r,{mode:384})}async function V(t,n){for(let r of[e(n,`.claude-plugin`,`plugin.json`),e(n,`plugin.json`)]){let e=await t.fs.readFile(r);if(e!==null)try{let t=g(JSON.parse(e)).name;return t===f||t===`gk`}catch{return!1}}return!1}async function H(n,r,i,a){let o=e(r,`plugins`,`cache`,d,f);for(let r of await n.fs.readDir(o)??[]){let s=e(o,r);r!==i&&await V(n,s)&&await t(n.fs,s,a,{recursive:!0})}}async function U(n,r,i,a=!1){let o=[e(r.source,`.claude-plugin`,`plugin.json`),e(r.source,`hooks`,`hooks.json`)];for(let e of o)await t(n.fs,e,i);for(let a of[e(r.source,`.claude-plugin`),e(r.source,`hooks`)])(await n.fs.readDir(a))?.length||await t(n.fs,a,i,{recursive:!0});(await n.fs.readDir(r.source))?.length||await t(n.fs,r.source,i,{recursive:!0});let s=e(r.marketplace,`plugins`);(await n.fs.readDir(s))?.length||await t(n.fs,s,i,{recursive:!0});let c=e(r.base,`plugins`,`cache`,d,f);for(let r of await n.fs.readDir(c)??[]){let a=e(c,r);await t(n.fs,e(a,`.claude-plugin`,`plugin.json`),i),await t(n.fs,e(a,`hooks`,`hooks.json`),i);for(let r of[e(a,`.claude-plugin`),e(a,`hooks`)])(await n.fs.readDir(r))?.length||await t(n.fs,r,i,{recursive:!0});(await n.fs.readDir(a))?.length||await t(n.fs,a,i,{recursive:!0})}(await n.fs.readDir(c))?.length||await t(n.fs,c,i,{recursive:!0});let l=e(r.marketplace,`.claude-plugin`,`marketplace.json`),u=await n.fs.readFile(l);!a&&u?.trimEnd()===JSON.stringify(S(),null,2)&&await t(n.fs,l,i);let p=e(r.marketplace,`.claude-plugin`);!a&&!(await n.fs.readDir(p))?.length&&await t(n.fs,p,i,{recursive:!0});let m=await n.fs.readDir(r.marketplace);!a&&!m?.length&&(m===null&&i.some(e=>e.path.startsWith(`${r.marketplace}/`))?(await n.fs.remove(r.marketplace,{recursive:!0}),i.push({path:r.marketplace,change:`removed`})):await t(n.fs,r.marketplace,i,{recursive:!0}))}async function W(t,n){return(await t.fs.readDir(e(n.marketplace,`plugins`))??[]).some(e=>e!==f)}function G(e,t){return Object.keys(e).some(e=>e!==`hooks`)||Object.values(t).some(Array.isArray)}async function K(n,i){let a=[],o=n.version||`0.0.0-dev`,s=x(i,o),u=e(s.source,`hooks`,`hooks.json`),p=_(await i.fs.readFile(u),u),h=v(p,u),b=await i.fs.readFile(s.known),C=_(b,s.known),T=s.settings;y(_(await i.fs.readFile(T),T),T);let E=l(n.executablePath,`claude-code`,i,!1),D=l(n.executablePath,`claude-code`,i,!0);for(let e of n.events??[])w(h,e,E,!1);for(let e of n.blockingEvents??[])w(h,e,D,!0);p.hooks=h,await k(e(s.marketplace,`.claude-plugin`,`marketplace.json`),S(),i,a);for(let t of[s.source,s.cache])await k(e(t,`.claude-plugin`,`plugin.json`),{name:f,version:o,description:m},i,a),await k(e(t,`hooks`,`hooks.json`),p,i,a);let O={...g(C[d]),source:{source:`directory`,path:s.marketplace},installLocation:s.marketplace,lastUpdated:n.now??new Date().toISOString().replace(/\.\d{3}Z$/,`Z`)};if(b===null||b.trim()===``)await k(s.known,{[d]:O},i,a);else{let e=r(b,`/${d}`,O,s.known,j);await A(i,s.known,e,a)}return await N(s.settings,s.marketplace,!0,c[`claude-code`].events,i,a),await B(i,s.base,a,s.cache),await t(i.fs,e(s.marketplace,`plugins`,`gk`),a,{recursive:!0}),await t(i.fs,e(s.base,`plugins`,`cache`,d,`gk`),a,{recursive:!0}),await H(i,s.base,o,a),{host:`claude-code`,path:u,events:[...n.events??[]],blockingEvents:[...n.blockingEvents??[]],files:a}}async function q(r,a){let o=[],s=x(a,`0.0.0-dev`),l=e(s.source,`hooks`,`hooks.json`),u=await a.fs.readFile(l),m=_(u,l),b=v(m,l),S=_(await a.fs.readFile(s.settings),s.settings);y(S,s.settings);let C=await a.fs.readFile(s.known),w={};try{C!==null&&(w=_(C,s.known))}catch{}let O=r??Object.keys(b),A=r??c[`claude-code`].events,M=E(b).filter(e=>O.includes(e));T(b,O),m.hooks=b;let F=E(b),I={host:`claude-code`,path:l,removedEvents:M,remainingEvents:F,files:o};M.length||(I.reason=u===null?`file-missing`:`not-installed`);let L=G(m,b);if(!M.length){if(r!==void 0||L)return I;let t=await z(s.base,a),n=!!t&&R(t[1],h,a.platform).length>0,i=e(s.base,`plugins`,`cache`,d,f);if(!(p in g(S.enabledPlugins)||h.some(e=>g(S.enabledPlugins)[e]===!0)||d in w||n||D(S,A)||(await a.fs.readDir(i))?.length))return I}else if(F.length||L){await k(l,m,a,o);let t=e(s.base,`plugins`,`cache`,d,f);for(let n of await a.fs.readDir(t)??[])await k(e(t,n,`hooks`,`hooks.json`),m,a,o);return I}let V=await W(a,s);if(V?await P(s.settings,A,a,o):await N(s.settings,s.marketplace,!1,A,a,o),d in w&&!V){let e=i(C??``,`/${d}`,s.known,j).text;Object.keys(w).length>1?await n(a.fs,s.known,e,o,{mode:384}):await t(a.fs,s.known,o)}return await U(a,s,o,V),await B(a,s.base,o),I}async function J(t,n,r){let i=e(n.base,`plugins`,`cache`,d,f),a=!1;for(let n of await t.fs.readDir(i)??[]){let r=await t.fs.readFile(e(i,n,`.claude-plugin`,`plugin.json`)),o=await t.fs.readFile(e(i,n,`hooks`,`hooks.json`));r!==null&&o!==null&&(a=!0)}let o=await t.fs.readFile(e(n.source,`hooks`,`hooks.json`)),s=await t.fs.readFile(e(n.marketplace,`.claude-plugin`,`marketplace.json`)),c=g(g(g(r.extraKnownMarketplaces)[d]).source).path;return!a||o===null||s===null||typeof c!=`string`||L(c,t.platform).replace(/\/+$/,``)!==L(n.marketplace,t.platform)}async function Y(t){let n=x(t,`0.0.0-dev`),r=e(n.source,`hooks`,`hooks.json`),i,a=async(e,t)=>{try{return await t()}catch(t){return i??=s(t),e}},o=e=>a({},async()=>_(await t.fs.readFile(e),e)),c=await o(r),l=await a({},()=>v(c,r)),u=await o(n.settings),d=g(u.hooks),f=g(u.enabledPlugins),m=E(d),y=await a(void 0,()=>z(n.base,t)),b=m.length>0||h.some(e=>f[e]===!0)||!!y&&R(y[1],h,t.platform).length>0,S=f[p]===!0,C=S&&await a(!1,()=>J(t,n,u)),w=e=>[...new Set(e)],T=w([...S?E(l):[],...b?m:[]]);return{host:`claude-code`,installed:b||S,usable:(b||S)&&!i&&!C&&T.length>0,path:r,layout:b?`legacy`:S?`plugin`:`none`,events:T,blockingEvents:w([...S?E(l,!0):[],...b?E(d,!0):[]]),needsMigration:b||C,...i?{error:i}:{}}}export{Y as inspectClaudeHooks,K as installClaudeHooks,q as uninstallClaudeHooks};
|
package/dist/hooks/errors.d.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
//#region src/hooks/errors.d.ts
|
|
2
|
-
type HookConfigErrorCode = 'invalid-json' | 'wrong-shape' | 'invalid-event' | 'unsupported-host' | 'blocking-unsupported' | 'unmanaged-file';
|
|
2
|
+
type HookConfigErrorCode = 'invalid-json' | 'wrong-shape' | 'invalid-event' | 'unsupported-host' | 'blocking-unsupported' | 'unmanaged-file' | 'unreadable';
|
|
3
3
|
/** A hook install or uninstall was refused; `path` names the file involved, when there is one. An existing file is left untouched. */
|
|
4
4
|
declare class HookConfigError extends Error {
|
|
5
5
|
readonly code: HookConfigErrorCode;
|
|
6
6
|
readonly path?: string | undefined;
|
|
7
7
|
constructor(code: HookConfigErrorCode, message: string, path?: string | undefined);
|
|
8
8
|
}
|
|
9
|
+
/** The code `inspectHooks` reports for a failed read: the refusal's own code, or `unreadable` for anything else. */
|
|
10
|
+
declare function hookErrorCode(error: unknown): HookConfigErrorCode;
|
|
9
11
|
//#endregion
|
|
10
|
-
export { HookConfigError, HookConfigErrorCode };
|
|
12
|
+
export { HookConfigError, HookConfigErrorCode, hookErrorCode };
|
package/dist/hooks/errors.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var e=class extends Error{constructor(e,t,n){super(t),this.code=e,this.path=n,this.name=`HookConfigError`}};export{e as HookConfigError};
|
|
1
|
+
var e=class extends Error{constructor(e,t,n){super(t),this.code=e,this.path=n,this.name=`HookConfigError`}};function t(t){return t instanceof e?t.code:`unreadable`}export{e as HookConfigError,t as hookErrorCode};
|
package/dist/hooks/events.d.ts
CHANGED
|
@@ -9,9 +9,8 @@ declare const eventManifest: {
|
|
|
9
9
|
};
|
|
10
10
|
readonly opencode: {
|
|
11
11
|
readonly events: readonly ["session.created", "session.updated", "session.deleted", "session.idle", "session.error", "session.status", "session.diff", "session.execution.failed", "session.compacted", "permission.asked", "permission.updated", "permission.replied", "message.updated", "message.part.updated", "tool.execute.before", "tool.execute.after", "file.edited", "todo.updated", "command.executed", "vcs.branch.updated", "installation.updated"];
|
|
12
|
-
readonly v1OnlyEvents: readonly ["session.updated", "session.error"];
|
|
12
|
+
readonly v1OnlyEvents: readonly ["session.updated", "session.error", "session.idle", "session.diff", "file.edited", "permission.updated"];
|
|
13
13
|
readonly v2EventAliases: {
|
|
14
|
-
readonly 'session.status': readonly ["session.updated"];
|
|
15
14
|
readonly 'session.execution.failed': readonly ["session.error"];
|
|
16
15
|
};
|
|
17
16
|
readonly skippedDefaultEvents: readonly ["message.updated", "message.part.updated", "installation.updated"];
|
|
@@ -20,7 +19,8 @@ declare const eventManifest: {
|
|
|
20
19
|
};
|
|
21
20
|
readonly cursor: {
|
|
22
21
|
readonly events: readonly ["sessionStart", "sessionEnd", "preToolUse", "postToolUse", "postToolUseFailure", "subagentStart", "subagentStop", "beforeShellExecution", "afterShellExecution", "beforeMCPExecution", "afterMCPExecution", "beforeReadFile", "afterFileEdit", "beforeSubmitPrompt", "preCompact", "stop", "afterAgentResponse", "afterAgentThought", "beforeTabFileRead", "afterTabFileEdit", "workspaceOpen"];
|
|
23
|
-
readonly skippedDefaultEvents: readonly ["beforeTabFileRead", "afterTabFileEdit", "workspaceOpen"];
|
|
22
|
+
readonly skippedDefaultEvents: readonly ["afterAgentThought", "beforeTabFileRead", "afterTabFileEdit", "workspaceOpen"];
|
|
23
|
+
readonly promptSubmitEvent: "beforeSubmitPrompt";
|
|
24
24
|
readonly sessionEndEvent: "sessionEnd";
|
|
25
25
|
readonly sessionStartEvent: "sessionStart";
|
|
26
26
|
};
|
|
@@ -39,16 +39,19 @@ declare const eventManifest: {
|
|
|
39
39
|
readonly sessionEndEvent: "SessionEnd";
|
|
40
40
|
readonly sessionStartEvent: "SessionStart";
|
|
41
41
|
readonly windowsShell: "powershell";
|
|
42
|
+
readonly blockingWait: "5s";
|
|
42
43
|
};
|
|
43
44
|
readonly antigravity: {
|
|
44
|
-
readonly events: readonly ["PreToolUse", "PostToolUse", "PreInvocation", "PostInvocation", "Stop"];
|
|
45
|
+
readonly events: readonly ["SessionStart", "PreToolUse", "PostToolUse", "PreInvocation", "PostInvocation", "Stop"];
|
|
45
46
|
readonly skippedDefaultEvents: readonly [];
|
|
46
|
-
readonly
|
|
47
|
-
readonly
|
|
47
|
+
readonly backfillEvents: readonly ["SessionStart"];
|
|
48
|
+
readonly sessionEndEvent: "";
|
|
49
|
+
readonly sessionStartEvent: "SessionStart";
|
|
50
|
+
readonly eventFlag: true;
|
|
48
51
|
};
|
|
49
52
|
readonly pi: {
|
|
50
|
-
readonly events: readonly ["project_trust", "resources_discover", "session_start", "session_info_changed", "session_before_switch", "session_before_fork", "session_before_compact", "session_compact", "session_compact_failed", "session_before_tree", "session_tree", "session_shutdown", "before_agent_start", "agent_start", "agent_end", "agent_settled", "ui_prompt_start", "ui_prompt_end", "turn_start", "turn_end", "message_start", "message_update", "message_end", "tool_call", "tool_result", "tool_execution_start", "tool_execution_update", "tool_execution_end", "context", "before_provider_headers", "before_provider_request", "after_provider_response", "model_select", "thinking_level_select", "user_bash", "input"];
|
|
51
|
-
readonly skippedDefaultEvents: readonly ["resources_discover", "message_start", "message_update", "message_end", "tool_execution_start", "tool_execution_update", "tool_execution_end", "context", "before_provider_headers", "before_provider_request", "after_provider_response", "input"];
|
|
53
|
+
readonly events: readonly ["project_trust", "resources_discover", "session_start", "session_info_changed", "session_before_switch", "session_before_fork", "session_before_compact", "session_compact", "session_compact_failed", "session_before_tree", "session_tree", "session_shutdown", "before_agent_start", "agent_start", "agent_end", "agent_before_settle", "agent_settled", "ui_prompt_start", "ui_prompt_end", "turn_start", "turn_end", "message_start", "message_update", "message_end", "tool_call", "tool_result", "tool_execution_start", "tool_execution_update", "tool_execution_end", "context", "cache_warming_decision", "context_with_system", "before_provider_headers", "before_provider_request", "after_provider_response", "provider_stream_event", "model_select", "thinking_level_select", "user_bash", "input"];
|
|
54
|
+
readonly skippedDefaultEvents: readonly ["resources_discover", "message_start", "message_update", "message_end", "tool_execution_start", "tool_execution_update", "tool_execution_end", "context", "cache_warming_decision", "context_with_system", "before_provider_headers", "before_provider_request", "after_provider_response", "provider_stream_event", "input"];
|
|
52
55
|
readonly sessionEndEvent: "session_shutdown";
|
|
53
56
|
readonly sessionStartEvent: "session_start";
|
|
54
57
|
};
|
package/dist/hooks/events.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const e={"claude-code":{events:`SessionStart.Setup.InstructionsLoaded.UserPromptSubmit.UserPromptExpansion.MessageDisplay.PreToolUse.PermissionRequest.PermissionDenied.PostToolUse.PostToolUseFailure.PostToolBatch.Notification.SubagentStart.SubagentStop.TaskCreated.TaskCompleted.Stop.StopFailure.TeammateIdle.ConfigChange.CwdChanged.DirectoryAdded.FileChanged.WorktreeCreate.WorktreeRemove.PreCompact.PostCompact.PreModelSwitch.PostModelSwitch.Elicitation.ElicitationResult.SessionEnd`.split(`.`),skippedDefaultEvents:[`Setup`,`MessageDisplay`,`FileChanged`,`WorktreeCreate`,`WorktreeRemove`],backfillEvents:[`PostModelSwitch`],sessionEndEvent:`SessionEnd`,sessionStartEvent:`SessionStart`},opencode:{events:[`session.created`,`session.updated`,`session.deleted`,`session.idle`,`session.error`,`session.status`,`session.diff`,`session.execution.failed`,`session.compacted`,`permission.asked`,`permission.updated`,`permission.replied`,`message.updated`,`message.part.updated`,`tool.execute.before`,`tool.execute.after`,`file.edited`,`todo.updated`,`command.executed`,`vcs.branch.updated`,`installation.updated`],v1OnlyEvents:[`session.updated`,`session.error
|
|
1
|
+
const e={"claude-code":{events:`SessionStart.Setup.InstructionsLoaded.UserPromptSubmit.UserPromptExpansion.MessageDisplay.PreToolUse.PermissionRequest.PermissionDenied.PostToolUse.PostToolUseFailure.PostToolBatch.Notification.SubagentStart.SubagentStop.TaskCreated.TaskCompleted.Stop.StopFailure.TeammateIdle.ConfigChange.CwdChanged.DirectoryAdded.FileChanged.WorktreeCreate.WorktreeRemove.PreCompact.PostCompact.PreModelSwitch.PostModelSwitch.Elicitation.ElicitationResult.SessionEnd`.split(`.`),skippedDefaultEvents:[`Setup`,`MessageDisplay`,`FileChanged`,`WorktreeCreate`,`WorktreeRemove`],backfillEvents:[`PostModelSwitch`],sessionEndEvent:`SessionEnd`,sessionStartEvent:`SessionStart`},opencode:{events:[`session.created`,`session.updated`,`session.deleted`,`session.idle`,`session.error`,`session.status`,`session.diff`,`session.execution.failed`,`session.compacted`,`permission.asked`,`permission.updated`,`permission.replied`,`message.updated`,`message.part.updated`,`tool.execute.before`,`tool.execute.after`,`file.edited`,`todo.updated`,`command.executed`,`vcs.branch.updated`,`installation.updated`],v1OnlyEvents:[`session.updated`,`session.error`,`session.idle`,`session.diff`,`file.edited`,`permission.updated`],v2EventAliases:{"session.execution.failed":[`session.error`]},skippedDefaultEvents:[`message.updated`,`message.part.updated`,`installation.updated`],sessionEndEvent:`session.deleted`,sessionStartEvent:`session.created`},cursor:{events:[`sessionStart`,`sessionEnd`,`preToolUse`,`postToolUse`,`postToolUseFailure`,`subagentStart`,`subagentStop`,`beforeShellExecution`,`afterShellExecution`,`beforeMCPExecution`,`afterMCPExecution`,`beforeReadFile`,`afterFileEdit`,`beforeSubmitPrompt`,`preCompact`,`stop`,`afterAgentResponse`,`afterAgentThought`,`beforeTabFileRead`,`afterTabFileEdit`,`workspaceOpen`],skippedDefaultEvents:[`afterAgentThought`,`beforeTabFileRead`,`afterTabFileEdit`,`workspaceOpen`],promptSubmitEvent:`beforeSubmitPrompt`,sessionEndEvent:`sessionEnd`,sessionStartEvent:`sessionStart`},codex:{events:[`SessionStart`,`UserPromptSubmit`,`PreToolUse`,`PermissionRequest`,`PostToolUse`,`PreCompact`,`PostCompact`,`SubagentStart`,`SubagentStop`,`Stop`,`Interrupt`,`SessionEnd`],skippedDefaultEvents:[],backfillEvents:[`SessionEnd`,`Interrupt`],sessionEndEvent:`SessionEnd`,sessionStartEvent:`SessionStart`,windowsShell:`powershell`},copilot:{events:[`SessionStart`,`SessionEnd`,`UserPromptSubmit`,`PreToolUse`,`PostToolUse`,`PostToolUseFailure`,`Stop`,`SubagentStop`,`ErrorOccurred`,`PreCompact`,`Notification`,`PermissionRequest`],legacyEvents:[`sessionStart`,`sessionEnd`,`userPromptSubmitted`,`preToolUse`,`postToolUse`,`postToolUseFailure`,`agentStop`,`subagentStart`,`subagentStop`,`errorOccurred`,`preCompact`,`notification`,`permissionRequest`],skippedDefaultEvents:[],sessionEndEvent:`SessionEnd`,sessionStartEvent:`SessionStart`,windowsShell:`powershell`,blockingWait:`5s`},antigravity:{events:[`SessionStart`,`PreToolUse`,`PostToolUse`,`PreInvocation`,`PostInvocation`,`Stop`],skippedDefaultEvents:[],backfillEvents:[`SessionStart`],sessionEndEvent:``,sessionStartEvent:`SessionStart`,eventFlag:!0},pi:{events:`project_trust.resources_discover.session_start.session_info_changed.session_before_switch.session_before_fork.session_before_compact.session_compact.session_compact_failed.session_before_tree.session_tree.session_shutdown.before_agent_start.agent_start.agent_end.agent_before_settle.agent_settled.ui_prompt_start.ui_prompt_end.turn_start.turn_end.message_start.message_update.message_end.tool_call.tool_result.tool_execution_start.tool_execution_update.tool_execution_end.context.cache_warming_decision.context_with_system.before_provider_headers.before_provider_request.after_provider_response.provider_stream_event.model_select.thinking_level_select.user_bash.input`.split(`.`),skippedDefaultEvents:[`resources_discover`,`message_start`,`message_update`,`message_end`,`tool_execution_start`,`tool_execution_update`,`tool_execution_end`,`context`,`cache_warming_decision`,`context_with_system`,`before_provider_headers`,`before_provider_request`,`after_provider_response`,`provider_stream_event`,`input`],sessionEndEvent:`session_shutdown`,sessionStartEvent:`session_start`},augment:{events:[`SessionStart`,`SessionEnd`,`PreToolUse`,`PostToolUse`,`Stop`,`Notification`,`PromptSubmit`],skippedDefaultEvents:[],sessionEndEvent:`SessionEnd`,sessionStartEvent:`SessionStart`,blockingTimeoutMultiplier:1e3,promptSubmitEvent:`PromptSubmit`},grok:{events:[`SessionStart`,`SessionEnd`,`UserPromptSubmit`,`PreToolUse`,`PostToolUse`,`PostToolUseFailure`,`PermissionDenied`,`Stop`,`StopFailure`,`StopCancelled`,`Notification`,`SubagentStart`,`SubagentStop`,`PreCompact`,`PostCompact`],skippedDefaultEvents:[],sessionEndEvent:`SessionEnd`,sessionStartEvent:`SessionStart`,windowsShell:`powershell`}};export{e as eventManifest};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { AgentHostConfig } from "../config.js";
|
|
2
|
+
import { HookHost } from "./events.js";
|
|
3
|
+
import { HookInspection } from "./results.js";
|
|
4
|
+
|
|
5
|
+
//#region src/hooks/inspect.d.ts
|
|
6
|
+
/** Read-only: whether GitKraken hooks are registered for `host`. A corrupt or unreadable file sets an `error` code, never a throw. */
|
|
7
|
+
declare function inspectHooks(host: HookHost, config: AgentHostConfig): Promise<HookInspection>;
|
|
8
|
+
//#endregion
|
|
9
|
+
export { inspectHooks };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{eventManifest as e}from"./events.js";import{inspectJsonHooks as t}from"./writers.js";import{inspectClaudeHooks as n}from"./claude.js";import{inspectMarkerHooks as r}from"./templates.js";async function i(i,a){return Object.hasOwn(e,i)?i===`claude-code`?n(a):i===`opencode`||i===`pi`?r(i,a):t(i,a):{host:i,installed:!1,usable:!1,path:``,layout:`none`,events:[],blockingEvents:[],needsMigration:!1,error:`unsupported-host`}}export{i as inspectHooks};
|
package/dist/hooks/install.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{eventManifest as e}from"./events.js";import{selectHookEvents as t,validateHookEvents as n}from"./validate.js";import{installJsonHooks as r,uninstallJsonHooks as i}from"./writers.js";import{installClaudeHooks as a,uninstallClaudeHooks as o}from"./claude.js";import{installMarkerHooks as s,uninstallMarkerHooks as c}from"./templates.js";async function l(e,n){if(e.host===`claude-code`){let{events:r,blockingEvents:i}=t(e);return a({executablePath:e.executablePath,events:r,blockingEvents:i,version:e.version},n)}if(e.host===`opencode`||e.host===`pi`){let{events:r,blockingEvents:i}=t(e);return s({host:e.host,executablePath:e.executablePath,events:r,blockingEvents:i},n)}return r(e,n)}async function u(t,r,a){a&&n(t,a);let s=a?.length?a:e[t].events;return t===`claude-code`?o(a?.length?s:void 0,r):t===`opencode`||t===`pi`?c(t,s,r):i(t,r,s)}export{l as installHooks,u as uninstallHooks};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { AgentHostConfig } from "../config.js";
|
|
2
|
+
import { HookHost } from "./events.js";
|
|
3
|
+
import { ClientType } from "../mcp/clients.js";
|
|
4
|
+
|
|
5
|
+
//#region src/hooks/registry.d.ts
|
|
6
|
+
/** Per-host hook registry data, mirroring gkcli's `aihook` client registry. */
|
|
7
|
+
interface HookClientInfo {
|
|
8
|
+
displayName: string;
|
|
9
|
+
/** Binary names looked up on `PATH`, in order; the first one found is the executable. */
|
|
10
|
+
binaryNames: readonly string[];
|
|
11
|
+
/** Set only for hosts with no MCP registry row to take a type from. */
|
|
12
|
+
clientType?: ClientType;
|
|
13
|
+
/** The host cannot honor a blocking decision, so blocking events are refused at install. */
|
|
14
|
+
noBlocking: boolean;
|
|
15
|
+
/** Events where any GitKraken entry breaks the host; the manifest lists them in `skippedDefaultEvents`, so a default install omits them. */
|
|
16
|
+
unsafeEvents: readonly string[];
|
|
17
|
+
/** How long a blocking entry waits for a decision (a Go duration) before the host's own UI takes over. */
|
|
18
|
+
blockingWait?: string;
|
|
19
|
+
}
|
|
20
|
+
/** Every hook host, in the manifest's order (gkcli's registry order). */
|
|
21
|
+
declare const hookHosts: readonly HookHost[];
|
|
22
|
+
declare function hookClientInfo(host: HookHost): HookClientInfo;
|
|
23
|
+
/** The first of the host's binary names that `config.resolveExecutable` finds, like Go's `ExecutablePath`. */
|
|
24
|
+
declare function executableForHook(host: HookHost, config: AgentHostConfig): string | undefined;
|
|
25
|
+
//#endregion
|
|
26
|
+
export { HookClientInfo, executableForHook, hookClientInfo, hookHosts };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{eventManifest as e}from"./events.js";const t=Object.keys(e),n={"claude-code":{displayName:`Claude Code`,binaryNames:[`claude`,`claude-code`],noBlocking:!1,unsafeEvents:[]},opencode:{displayName:`OpenCode`,binaryNames:[`opencode`],noBlocking:!0,unsafeEvents:[]},cursor:{displayName:`Cursor`,binaryNames:[`cursor`,`cursor-agent`],noBlocking:!0,unsafeEvents:[`afterAgentThought`]},codex:{displayName:`Codex`,binaryNames:[`codex`],noBlocking:!1,unsafeEvents:[]},copilot:{displayName:`GitHub Copilot CLI`,binaryNames:[`copilot`],noBlocking:!1,unsafeEvents:[],blockingWait:`5s`},antigravity:{displayName:`Antigravity`,binaryNames:[`antigravity`,`agy`],noBlocking:!1,unsafeEvents:[]},pi:{displayName:`Pi`,binaryNames:[`pi`],clientType:`cli`,noBlocking:!0,unsafeEvents:[]},augment:{displayName:`Augment`,binaryNames:[`auggie`],clientType:`cli`,noBlocking:!1,unsafeEvents:[]},grok:{displayName:`Grok Build`,binaryNames:[`grok`],clientType:`cli`,noBlocking:!1,unsafeEvents:[]}};function r(e){if(!Object.hasOwn(n,e))throw Error(`Unknown hook host: ${e}`);return n[e]}function i(e,t){if(Object.hasOwn(n,e))for(let r of n[e].binaryNames){let e=t.resolveExecutable?.(r);if(e)return e}}export{i as executableForHook,r as hookClientInfo,t as hookHosts};
|
package/dist/hooks/results.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { HookHost } from "./events.js";
|
|
2
2
|
import { FileChange } from "../file-change.js";
|
|
3
|
+
import { HookConfigErrorCode } from "./errors.js";
|
|
3
4
|
|
|
4
5
|
//#region src/hooks/results.d.ts
|
|
5
6
|
interface HookInstallResult {
|
|
@@ -26,5 +27,26 @@ interface HookUninstallResult {
|
|
|
26
27
|
/** Set when no GitKraken hook was removed: the hooks file does not exist, or holds none for the requested events. */
|
|
27
28
|
reason?: 'file-missing' | 'not-installed';
|
|
28
29
|
}
|
|
30
|
+
type HookLayout = 'none' | 'file' | 'plugin' | 'legacy';
|
|
31
|
+
interface HookInspection {
|
|
32
|
+
host: HookHost;
|
|
33
|
+
/** Whether GitKraken hooks are registered, like gkcli's `IsInstalled`; Claude counts an enabled plugin even when its files are broken. */
|
|
34
|
+
installed: boolean;
|
|
35
|
+
/** `installed`, readable and intact (no `error`, no missing Claude plugin files) and carrying at least one event.
|
|
36
|
+
* A legacy Claude plugin's own files are not checked, so legacy installs are usable only with settings.json hooks. */
|
|
37
|
+
usable: boolean;
|
|
38
|
+
/** The hooks file the agent reads (Claude: the marketplace plugin's hooks.json). */
|
|
39
|
+
path: string;
|
|
40
|
+
/** `file` for a dedicated hooks or relay file, `plugin` for Claude's current plugin, `legacy` for any older Claude layout, `none` when not installed. */
|
|
41
|
+
layout: HookLayout;
|
|
42
|
+
/** Every event that carries a GitKraken hook, blocking ones included. */
|
|
43
|
+
events: string[];
|
|
44
|
+
/** The subset of `events` registered as blocking. */
|
|
45
|
+
blockingEvents: string[];
|
|
46
|
+
/** Legacy Claude state or drift, or Copilot entries under replaced event names, that a reinstall would move. */
|
|
47
|
+
needsMigration: boolean;
|
|
48
|
+
/** Set when a file could not be read or parsed; the rest of the result then reflects what could still be read. */
|
|
49
|
+
error?: HookConfigErrorCode;
|
|
50
|
+
}
|
|
29
51
|
//#endregion
|
|
30
|
-
export { HookInstallResult, HookUninstallResult };
|
|
52
|
+
export { HookInspection, HookInstallResult, HookLayout, HookUninstallResult };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AgentHostConfig } from "../config.js";
|
|
2
|
-
import { HookInstallResult, HookUninstallResult } from "./results.js";
|
|
2
|
+
import { HookInspection, HookInstallResult, HookUninstallResult } from "./results.js";
|
|
3
3
|
|
|
4
4
|
//#region src/hooks/templates.d.ts
|
|
5
5
|
type MarkerHost = 'opencode' | 'pi';
|
|
@@ -11,5 +11,7 @@ interface MarkerHookRequest {
|
|
|
11
11
|
}
|
|
12
12
|
declare function installMarkerHooks(request: MarkerHookRequest, config: AgentHostConfig): Promise<HookInstallResult>;
|
|
13
13
|
declare function uninstallMarkerHooks(host: MarkerHost, events: readonly string[] | undefined, config: AgentHostConfig): Promise<HookUninstallResult>;
|
|
14
|
+
/** Read-only: reports the events recorded in a managed relay file's header without ever writing it. */
|
|
15
|
+
declare function inspectMarkerHooks(host: MarkerHost, config: AgentHostConfig): Promise<HookInspection>;
|
|
14
16
|
//#endregion
|
|
15
|
-
export { MarkerHookRequest, MarkerHost, installMarkerHooks, uninstallMarkerHooks };
|
|
17
|
+
export { MarkerHookRequest, MarkerHost, inspectMarkerHooks, installMarkerHooks, uninstallMarkerHooks };
|
package/dist/hooks/templates.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import{joinPath as e}from"../config.js";import{removeTracked as t,writeFileTracked as n}from"../file-change.js";import{HookConfigError as r}from"./errors.js";const
|
|
2
|
-
`,1)[0].startsWith(
|
|
3
|
-
`,1)[0];try{let e=JSON.parse(t.slice(14));if(e&&typeof e==`object`&&Array.isArray(e.events)){let t=e;return{events:t.events.filter(e=>typeof e==`string`),executable:typeof t.executable==`string`?t.executable:``,hostMajor:typeof t.hostMajor==`number`?t.hostMajor:0}}}catch{}}function
|
|
1
|
+
import{joinPath as e}from"../config.js";import{removeTracked as t,writeFileTracked as n}from"../file-change.js";import{HookConfigError as r,hookErrorCode as i}from"./errors.js";import{eventManifest as a}from"./events.js";import{assertBlockingSupported as o}from"./validate.js";const s=`// gk-managed:`;function c(t,n){return e(t===`opencode`?e(n.paths.configHome,`opencode`,`plugins`):e(n.paths.piAgentDir||e(n.paths.home,`.pi`,`agent`),`extensions`),`gk-hooks.js`)}function l(e){return e?.split(`
|
|
2
|
+
`,1)[0].startsWith(s)??!1}function u(e){if(!l(e))return;let t=e.split(`
|
|
3
|
+
`,1)[0];try{let e=JSON.parse(t.slice(14));if(e&&typeof e==`object`&&Array.isArray(e.events)){let t=e;return{events:t.events.filter(e=>typeof e==`string`),executable:typeof t.executable==`string`?t.executable:``,hostMajor:typeof t.hostMajor==`number`?t.hostMajor:0}}}catch{}}function d(e,t,n,r){let i={events:t,executable:n,version:e===`opencode`?8:3};e===`opencode`&&r&&(i.hostMajor=r);let o=e===`opencode`?`import { spawn } from "node:child_process";
|
|
4
|
+
import { randomBytes } from "node:crypto";
|
|
4
5
|
|
|
5
6
|
const EVENTS = new Set({{.EventsJSON}});
|
|
6
7
|
const EXECUTABLE = {{.ExecJSON}};
|
|
7
8
|
const HOST = {{.HostJSON}};
|
|
8
9
|
const MAX_HOOK_INPUT_BYTES = {{.MaxInputBytesJSON}};
|
|
10
|
+
const RELAY_INSTANCE = process.pid + "-" + randomBytes(8).toString("hex");
|
|
11
|
+
let relaySeq = 0;
|
|
9
12
|
|
|
10
13
|
function getString(value) {
|
|
11
14
|
return typeof value === "string" && value.length > 0 ? value : "";
|
|
@@ -182,11 +185,14 @@ const setup = async (ctx) => {
|
|
|
182
185
|
// this v2-capable template.
|
|
183
186
|
export default { id: "gk-hooks", server, setup };
|
|
184
187
|
`:`import { spawn } from "node:child_process";
|
|
188
|
+
import { randomBytes } from "node:crypto";
|
|
185
189
|
|
|
186
190
|
const EVENTS = new Set({{.EventsJSON}});
|
|
187
191
|
const EXECUTABLE = {{.ExecJSON}};
|
|
188
192
|
const HOST = {{.HostJSON}};
|
|
189
193
|
const MAX_HOOK_INPUT_BYTES = {{.MaxInputBytesJSON}};
|
|
194
|
+
const RELAY_INSTANCE = process.pid + "-" + randomBytes(8).toString("hex");
|
|
195
|
+
let relaySeq = 0;
|
|
190
196
|
|
|
191
197
|
function getString(value) {
|
|
192
198
|
return typeof value === "string" && value.length > 0 ? value : "";
|
|
@@ -224,18 +230,20 @@ export default function (pi) {
|
|
|
224
230
|
});
|
|
225
231
|
}
|
|
226
232
|
}
|
|
227
|
-
`,
|
|
233
|
+
`,c={EventsJSON:JSON.stringify(t),ExecJSON:JSON.stringify(n),HostJSON:JSON.stringify(e),MaxInputBytesJSON:`1048576`,V1OnlyEventsJSON:JSON.stringify(a.opencode.v1OnlyEvents),V2EventAliasesJSON:JSON.stringify(a.opencode.v2EventAliases)},l=e===`opencode`?`
|
|
228
234
|
|
|
229
235
|
const V1_ONLY_EVENTS`:`
|
|
230
236
|
|
|
231
237
|
|
|
232
238
|
|
|
233
|
-
function sessionIdOf`,
|
|
239
|
+
function sessionIdOf`,u=o.indexOf(l);if(u<0)throw Error(`missing ${e} relay insertion point`);let d=o.slice(0,u)+`
|
|
234
240
|
|
|
235
|
-
function buildHookInput(eventName, sessionID, cwd, payload) {
|
|
241
|
+
function buildHookInput(eventName, sessionID, cwd, payload, seq) {
|
|
236
242
|
const toolName = extractToolName(payload);
|
|
237
243
|
const hookInput = {
|
|
238
244
|
hook_event_name: eventName,
|
|
245
|
+
relay_instance: RELAY_INSTANCE,
|
|
246
|
+
relay_seq: seq,
|
|
239
247
|
session_id: sessionID || "",
|
|
240
248
|
cwd,
|
|
241
249
|
tool_name: toolName || null,
|
|
@@ -244,6 +252,7 @@ function buildHookInput(eventName, sessionID, cwd, payload) {
|
|
|
244
252
|
reason: null,
|
|
245
253
|
agent_id: null,
|
|
246
254
|
agent_type: null,
|
|
255
|
+
host_pid: process.pid,
|
|
247
256
|
hook_payload: payload || null,
|
|
248
257
|
};
|
|
249
258
|
|
|
@@ -261,8 +270,10 @@ function buildHookInput(eventName, sessionID, cwd, payload) {
|
|
|
261
270
|
|
|
262
271
|
function runHook(eventName, sessionID, cwd, payload, aliases = []) {
|
|
263
272
|
if (!EVENTS.has(eventName) && !aliases.some((alias) => EVENTS.has(alias))) return;
|
|
273
|
+
relaySeq += 1;
|
|
274
|
+
const seq = relaySeq;
|
|
264
275
|
try {
|
|
265
|
-
const input = buildHookInput(eventName, sessionID, cwd, payload);
|
|
276
|
+
const input = buildHookInput(eventName, sessionID, cwd, payload, seq);
|
|
266
277
|
const child = spawn(EXECUTABLE, ["ai", "hook", "run", "--host", HOST], {
|
|
267
278
|
stdio: ["pipe", "ignore", "ignore"],
|
|
268
279
|
windowsHide: true,
|
|
@@ -274,5 +285,5 @@ function runHook(eventName, sessionID, cwd, payload, aliases = []) {
|
|
|
274
285
|
} catch (_) {
|
|
275
286
|
// Hooks must never block the host
|
|
276
287
|
}
|
|
277
|
-
}`+o.slice(
|
|
278
|
-
`+
|
|
288
|
+
}`+o.slice(u);return s+JSON.stringify(i)+`
|
|
289
|
+
`+d.replace(/{{\.(\w+)}}/g,(e,t)=>c[t]??``)}async function f(n,r,i){let a=c(n,r),o=a.slice(0,a.lastIndexOf(`/`));for(let n of await r.fs.readDir(o)??[]){if(n===`gk-hooks.js`||!n.startsWith(`gk-hooks.`))continue;let a=e(o,n);l(await r.fs.readFile(a))&&await t(r.fs,a,i)}}async function p(e,t){let i=c(e.host,t);o(e.host,e.blockingEvents,i);let a=await t.fs.readFile(i),s=u(a);if(a!==null&&!l(a))throw new r(`unmanaged-file`,`${i} already exists and was not written by GitKraken`,i);let p=[...new Set([...s?.events??[],...e.events??[]])].sort(),m=e.host===`opencode`?t.openCodeMajor??0:0,h=[];return await n(t.fs,i,d(e.host,p,e.executablePath.replace(/\\/g,`/`),m),h,{mode:384}),await f(e.host,t,h),{host:e.host,path:i,events:[...e.events??[]],blockingEvents:[],files:h}}async function m(e,r,i){let a=c(e,i),o=[],s=await i.fs.readFile(a),l=u(s),p=[],m=[];l&&(p=l.events.filter(e=>(r??l.events).includes(e)),m=l.events.filter(e=>!p.includes(e)).sort(),m.length?await n(i.fs,a,d(e,m,l.executable,l.hostMajor),o,{mode:384}):await t(i.fs,a,o)),await f(e,i,o);let h={host:e,path:a,removedEvents:p,remainingEvents:m,files:o};return p.length||(h.reason=s===null?`file-missing`:`not-installed`),h}async function h(e,t){let n=c(e,t),r={host:e,installed:!1,usable:!1,path:n,layout:`none`,events:[],blockingEvents:[],needsMigration:!1};try{let e=await t.fs.readFile(n);if(!e?.trim())return r;let i=u(e);return i?{...r,installed:!0,usable:i.events.length>0,layout:`file`,events:i.events}:{...r,error:l(e)?`invalid-json`:`unmanaged-file`}}catch(e){return{...r,error:i(e)}}}export{h as inspectMarkerHooks,p as installMarkerHooks,m as uninstallMarkerHooks};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { HookHost } from "./events.js";
|
|
2
|
+
|
|
3
|
+
//#region src/hooks/validate.d.ts
|
|
4
|
+
/** Throws `invalid-event` for the first event the host's manifest does not list, with gkcli's wording. */
|
|
5
|
+
declare function validateHookEvents(host: HookHost, events: readonly string[]): void;
|
|
6
|
+
/** Throws `blocking-unsupported` when the host cannot honor a decision (`noBlocking` in the registry) and `blockingEvents` is not empty. */
|
|
7
|
+
declare function assertBlockingSupported(host: HookHost, blockingEvents: readonly string[] | undefined, path?: string): void;
|
|
8
|
+
/** Resolves the events to write: the explicit ones, or the manifest's minus `skippedDefaultEvents` when none are requested. */
|
|
9
|
+
declare function selectHookEvents(request: {
|
|
10
|
+
host: HookHost;
|
|
11
|
+
events?: readonly string[];
|
|
12
|
+
blockingEvents?: readonly string[];
|
|
13
|
+
}): {
|
|
14
|
+
events: string[];
|
|
15
|
+
blockingEvents: string[];
|
|
16
|
+
isDefault: boolean;
|
|
17
|
+
};
|
|
18
|
+
//#endregion
|
|
19
|
+
export { assertBlockingSupported, selectHookEvents, validateHookEvents };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{HookConfigError as e}from"./errors.js";import{eventManifest as t}from"./events.js";import{hookClientInfo as n}from"./registry.js";function r(n,r){let i=t[n].events;for(let t of r)if(!i.includes(t))throw new e(`invalid-event`,`'${t}' is not a valid event for ${n}. Valid events: ${i.join(`, `)}`)}function i(t,r,i){if(r?.length&&n(t).noBlocking)throw new e(`blocking-unsupported`,`${t} does not support blocking hooks`,i)}function a(e){let n=t[e.host],i=!e.events?.length&&!e.blockingEvents?.length,a=i?n.events.filter(e=>!n.skippedDefaultEvents.includes(e)):[...e.events??[]],o=[...e.blockingEvents??[]];return r(e.host,[...a,...o]),{events:a,blockingEvents:o,isDefault:i}}export{i as assertBlockingSupported,a as selectHookEvents,r as validateHookEvents};
|
package/dist/hooks/writers.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AgentHostConfig } from "../config.js";
|
|
2
2
|
import { HookHost } from "./events.js";
|
|
3
|
-
import { HookInstallResult, HookUninstallResult } from "./results.js";
|
|
3
|
+
import { HookInspection, HookInstallResult, HookUninstallResult } from "./results.js";
|
|
4
4
|
|
|
5
5
|
//#region src/hooks/writers.d.ts
|
|
6
6
|
type JsonHookHost = Extract<HookHost, 'cursor' | 'codex' | 'copilot' | 'antigravity' | 'augment' | 'grok'>;
|
|
@@ -10,9 +10,14 @@ interface HookWriteRequest {
|
|
|
10
10
|
events?: readonly string[];
|
|
11
11
|
blockingEvents?: readonly string[];
|
|
12
12
|
}
|
|
13
|
-
declare
|
|
13
|
+
declare const BLOCKING_SUFFIX = " --blocking";
|
|
14
|
+
declare const HOOK_RUN_MARKER = " ai hook run --host ";
|
|
15
|
+
/** `event` is appended as `--event` for hosts whose payload omits the event name; a blocking entry ends with `--blocking`. */
|
|
16
|
+
declare function command(executablePath: string, host: HookHost, config: AgentHostConfig, blocking: boolean, event?: string): string;
|
|
14
17
|
/** Merges only GitKraken-owned hooks, preserving unrelated entries and document keys. */
|
|
15
18
|
declare function installJsonHooks(request: HookWriteRequest, config: AgentHostConfig): Promise<HookInstallResult>;
|
|
16
19
|
declare function uninstallJsonHooks(host: JsonHookHost, config: AgentHostConfig, events?: readonly string[]): Promise<HookUninstallResult>;
|
|
20
|
+
/** Read-only: reports the GitKraken events in `host`'s hooks file without ever writing it. */
|
|
21
|
+
declare function inspectJsonHooks(host: HookHost, config: AgentHostConfig): Promise<HookInspection>;
|
|
17
22
|
//#endregion
|
|
18
|
-
export { HookWriteRequest, command, installJsonHooks, uninstallJsonHooks };
|
|
23
|
+
export { BLOCKING_SUFFIX, HOOK_RUN_MARKER, HookWriteRequest, command, inspectJsonHooks, installJsonHooks, uninstallJsonHooks };
|
package/dist/hooks/writers.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{joinPath as e}from"../config.js";import{removeTracked as t,writeFileTracked as n}from"../file-change.js";import{HookConfigError as r}from"./errors.js";import{eventManifest as
|
|
1
|
+
import{joinPath as e}from"../config.js";import{removeTracked as t,writeFileTracked as n}from"../file-change.js";import{HookConfigError as r,hookErrorCode as i}from"./errors.js";import{eventManifest as a}from"./events.js";import{hookClientInfo as o}from"./registry.js";import{assertBlockingSupported as s,selectHookEvents as c}from"./validate.js";const l={cursor:{path:t=>e(t.paths.home,`.cursor`,`hooks.json`),key:`hooks`,version:!0,matcher:!1},codex:{path:t=>e(t.paths.home,`.codex`,`hooks.json`),key:`hooks`,version:!1,matcher:!0},copilot:{path:t=>e(t.paths.home,`.copilot`,`hooks`,`gitkraken-hooks.json`),key:`hooks`,version:!0,matcher:!1},antigravity:{path:t=>e(t.paths.home,`.gemini`,`config`,`hooks.json`),key:`gitkraken`,version:!1,matcher:!1},augment:{path:t=>e(t.paths.home,`.augment`,`settings.json`),key:`hooks`,version:!1,matcher:!0,neverDelete:!0},grok:{path:t=>e(t.paths.grokHome??e(t.paths.home,`.grok`),`hooks`,`gitkraken-hooks.json`),key:`hooks`,version:!1,matcher:!0}},u=` --blocking`,d=` ai hook run --host `;function f(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function p(e){return typeof e==`string`&&e.includes(` ai hook run --host `)}function m(e,t,n,r,i){let s=a[t],c=n.platform===`win32`&&`windowsShell`in s&&s.windowsShell===`powershell`?`& `:``,l=i&&`eventFlag`in s&&s.eventFlag?` --event ${i}`:``,f=o(t).blockingWait,p=r?(f?` --timeout ${f}`:``)+u:``;return`${c}"${e.replaceAll(`\\`,`/`)}"${d}${t}${l}${p}`}function h(e,t){return e===`antigravity`?t===`PreToolUse`||t===`PostToolUse`:l[e].matcher}function g(e,t,n){if(!Array.isArray(e)||!e.every(f))throw new r(`wrong-shape`,`${n} has the wrong event shape`,n);if(t&&e.some(e=>`command`in e||`hooks`in e&&(!Array.isArray(e.hooks)||!e.hooks.every(f))))throw new r(`wrong-shape`,`${n} has the wrong matcher shape`,n);if(!t&&e.some(e=>`hooks`in e))throw new r(`wrong-shape`,`${n} has the wrong flat shape`,n)}function _(e,t){return Array.isArray(e)?e.flatMap(e=>t&&f(e)&&Array.isArray(e.hooks)?e.hooks.map(e=>f(e)?e.command:void 0):[f(e)?e.command:void 0]).filter(e=>p(e)):[]}function v(e,t){return Object.keys(t).filter(n=>_(t[n],h(e,n)).length>0)}function y(e,t){return t?e.flatMap(e=>{if(!Array.isArray(e.hooks))return[e];let t=e.hooks.filter(e=>!f(e)||!p(e.command));return t.length===e.hooks.length?[e]:t.length?[{...e,hooks:t}]:[]}):e.filter(e=>!p(e.command))}function b(e,t,n){return n?86400*(`blockingTimeoutMultiplier`in a[e]?Number(a[e].blockingTimeoutMultiplier):1):e===`codex`&&t===`SessionEnd`?3:void 0}function x(e,t,n,r,i){let a={type:`command`,command:r,...i?{timeout:i}:{}};if(!t)return[...y(e,!1),a];let o=y(e,!0),s=o.find(e=>!e.matcher&&Array.isArray(e.hooks)&&e.hooks.length>0);s||(s=n===`augment`?{hooks:[]}:{matcher:``,hooks:[]},o.push(s));let c=Array.isArray(s.hooks)?s.hooks:[];return s.hooks=[...c.filter(e=>!p(e.command)),a],o}function S(e,t){let n;try{n=JSON.parse(e.replace(/^\uFEFF/,``))}catch{throw new r(`invalid-json`,`Invalid JSON at ${t}`,t)}if(!f(n))throw new r(`wrong-shape`,`Invalid hooks document at ${t}`,t);return n}function C(e,t,n){let i=e[t];if(i!=null){if(!f(i))throw new r(`wrong-shape`,`Invalid hooks wrapper at ${n}`,n);return i}}async function w(e,t){if(!Object.hasOwn(l,e.host))throw new r(`unsupported-host`,`No JSON writer for ${e.host}`);let i=e.host,u=l[i],d=u.path(t),{events:f,blockingEvents:p,isDefault:_}=c(e);s(i,p,d);let v=await t.fs.readFile(d),w=v?.trim()?S(v,d):{},T={...C(w,u.key,d)};for(let[e,t]of Object.entries(T))g(t,h(i,e),d);for(let[n,r]of[[f,!1],[p,!0]])for(let a of n){let n=h(i,a);T[a]=x(T[a]??[],n,i,m(e.executablePath,i,t,r,a),b(i,a,r))}if(`legacyEvents`in a[i])for(let e of a[i].legacyEvents){let t=T[e];if(t){let n=y(t,h(i,e));n.length?T[e]=n:delete T[e]}}if(_)for(let e of o(i).unsafeEvents){let t=T[e];if(!t)continue;let n=y(t,h(i,e));n.length?T[e]=n:delete T[e]}Object.keys(T).length?w[u.key]=T:delete w[u.key],u.version&&w.version==null&&(w.version=1);let E=[];return await n(t.fs,d,`${JSON.stringify(w,null,2)}\n`,E),{host:i,path:d,events:[...f],blockingEvents:[...p],files:E}}async function T(e,r,i){let o=l[e],s=o.path(r),c=[],u=(t,n=[])=>({host:e,path:s,removedEvents:[],remainingEvents:n,files:c,reason:t}),d=await r.fs.readFile(s);if(d===null)return u(`file-missing`);if(!d.trim())return u(`not-installed`);let f=S(d,s),p=C(f,o.key,s);if(!p)return u(`not-installed`);let m={...p};for(let[t,n]of Object.entries(m))g(n,h(e,t),s);let _=[],b=i?.length?i:a[e].events;for(let t of[...b,...`legacyEvents`in a[e]?a[e].legacyEvents:[]]){let n=m[t];if(!n)continue;let r=y(n,h(e,t));JSON.stringify(r)!==JSON.stringify(n)&&(_.push(t),r.length?m[t]=r:delete m[t])}let x=v(e,m);if(!_.length)return u(`not-installed`,x);Object.keys(m).length?f[o.key]=m:delete f[o.key];let w=Object.keys(f).some(e=>e!==o.key&&!(e===`version`&&o.version&&f.version===1));return!Object.keys(m).length&&!w&&!o.neverDelete?await t(r.fs,s,c):await n(r.fs,s,`${JSON.stringify(f,null,2)}\n`,c),{host:e,path:s,removedEvents:_,remainingEvents:x,files:c}}async function E(e,t){let n=e,r=l[n],o=r.path(t),s={host:e,installed:!1,usable:!1,path:o,layout:`none`,events:[],blockingEvents:[],needsMigration:!1};try{let i=await t.fs.readFile(o);if(!i?.trim())return s;let c=C(S(i,o),r.key,o)??{};for(let[e,t]of Object.entries(c))g(t,h(n,e),o);let l=v(n,c),d=`legacyEvents`in a[e]?a[e].legacyEvents:[];return{...s,installed:l.length>0,usable:l.length>0,layout:l.length?`file`:`none`,events:l,blockingEvents:l.filter(e=>_(c[e],h(n,e)).some(e=>e.endsWith(u))),needsMigration:l.some(e=>d.includes(e))}}catch(e){return{...s,error:i(e)}}}export{u as BLOCKING_SUFFIX,d as HOOK_RUN_MARKER,m as command,E as inspectJsonHooks,w as installJsonHooks,T as uninstallJsonHooks};
|
package/dist/index.d.ts
CHANGED
|
@@ -4,15 +4,18 @@ import { McpConfigEntry, generateConfigEntry } from "./mcp/config-entry.js";
|
|
|
4
4
|
import { McpInstallRequest, McpInstallResult, McpInstallState, McpInstallStateReason, McpInstallStatus, inspectMcpInstall, installMcp } from "./mcp/installer.js";
|
|
5
5
|
import { McpLayoutOptions, OpenCodeMcpCleanupError, OpenCodeMcpLayout, OpenCodeMcpLayoutUnknownError, OpenCodeMcpPolicy } from "./mcp/opencode-layout.js";
|
|
6
6
|
import { Client, ClientDetection, ClientDetectionSource, ClientInstallation, ClientNotDetectedReason, ClientType, allClients, antigravityLegacyPath, clientCommand, clientConfigPath, clientConfigPaths, clientDisplayName, clientExecutablePath, clientJSONPath, clientType, clientsForHost, detectClient, getClientInstallation, hostIdentity, hostOf, isInstallable, isLegacy, isSupported, legacyClientIds } from "./mcp/clients.js";
|
|
7
|
-
import { Agent, HookCapability, listAgents, mcpNamesForHook, resolveHookClient } from "./agents.js";
|
|
7
|
+
import { Agent, HookCapability, ListAgentsOptions, hookCapabilities, listAgents, mcpNamesForHook, resolveHookClient } from "./agents.js";
|
|
8
8
|
import { FileChange, FileChangeKind } from "./file-change.js";
|
|
9
|
-
import { HookInstallResult, HookUninstallResult } from "./hooks/results.js";
|
|
10
9
|
import { HookConfigError, HookConfigErrorCode } from "./hooks/errors.js";
|
|
10
|
+
import { HookInspection, HookInstallResult, HookLayout, HookUninstallResult } from "./hooks/results.js";
|
|
11
|
+
import { inspectHooks } from "./hooks/inspect.js";
|
|
11
12
|
import { HookWriteRequest, installJsonHooks, uninstallJsonHooks } from "./hooks/writers.js";
|
|
12
13
|
import { InstallHooksRequest, installHooks, uninstallHooks } from "./hooks/install.js";
|
|
14
|
+
import { HookClientInfo, executableForHook, hookClientInfo, hookHosts } from "./hooks/registry.js";
|
|
15
|
+
import { validateHookEvents } from "./hooks/validate.js";
|
|
13
16
|
import { getMcpCliInstallCommand } from "./mcp/cli-install-command.js";
|
|
14
17
|
import { McpUninstallFileResult, McpUninstallRequest, McpUninstallResult, uninstallMcp } from "./mcp/uninstaller.js";
|
|
15
18
|
import { SKILL_MANIFEST_FILE_NAME, SKILL_MANIFEST_SCHEMA_VERSION, SkillManifest } from "./skills/manifest.js";
|
|
16
19
|
import { SkillAgent, allSkillAgents, isSkillAgentSupported, skillDir } from "./skills/skill-dirs.js";
|
|
17
20
|
import { SkillConflictError, SkillFiles, SkillInstallAction, SkillInstallOptions, SkillInstallResult, SkillOperation, SkillReason, SkillState, SkillStatus, SkillUninstallAction, SkillUninstallOptions, SkillUninstallResult, SkillUnsupportedManifestError, inspectSkillStatus, installSkill, uninstallSkill } from "./skills/install.js";
|
|
18
|
-
export { type Agent, type AgentHostConfig, type AgentHostPaths, type AgentOrigin, type Client, type ClientDetection, type ClientDetectionSource, type ClientInstallation, type ClientNotDetectedReason, type ClientType, type FileChange, type FileChangeKind, type HookCapability, HookConfigError, type HookConfigErrorCode, type HookHost, type HookInstallResult, type HookUninstallResult, type HookWriteRequest, type InstallHooksRequest, type McpConfigEntry, type McpInstallRequest, type McpInstallResult, type McpInstallState, type McpInstallStateReason, type McpInstallStatus, type McpLayoutOptions, type McpUninstallFileResult, type McpUninstallRequest, type McpUninstallResult, OpenCodeMcpCleanupError, type OpenCodeMcpLayout, OpenCodeMcpLayoutUnknownError, type OpenCodeMcpPolicy, SKILL_MANIFEST_FILE_NAME, SKILL_MANIFEST_SCHEMA_VERSION, type SkillAgent, SkillConflictError, type SkillFiles, type SkillInstallAction, type SkillInstallOptions, type SkillInstallResult, type SkillManifest, type SkillOperation, type SkillReason, type SkillState, type SkillStatus, type SkillUninstallAction, type SkillUninstallOptions, type SkillUninstallResult, SkillUnsupportedManifestError, allClients, allSkillAgents, antigravityLegacyPath, clientCommand, clientConfigPath, clientConfigPaths, clientDisplayName, clientExecutablePath, clientJSONPath, clientType, clientsForHost, detectClient, eventManifest, generateConfigEntry, getClientInstallation, getMcpCliInstallCommand, hostIdentity, hostOf, inspectMcpInstall, inspectSkillStatus, installHooks, installJsonHooks, installMcp, installSkill, isInstallable, isLegacy, isSkillAgentSupported, isSupported, joinPath, legacyClientIds, listAgents, mcpNamesForHook, resolveHookClient, skillDir, uninstallHooks, uninstallJsonHooks, uninstallMcp, uninstallSkill };
|
|
21
|
+
export { type Agent, type AgentHostConfig, type AgentHostPaths, type AgentOrigin, type Client, type ClientDetection, type ClientDetectionSource, type ClientInstallation, type ClientNotDetectedReason, type ClientType, type FileChange, type FileChangeKind, type HookCapability, type HookClientInfo, HookConfigError, type HookConfigErrorCode, type HookHost, type HookInspection, type HookInstallResult, type HookLayout, type HookUninstallResult, type HookWriteRequest, type InstallHooksRequest, type ListAgentsOptions, type McpConfigEntry, type McpInstallRequest, type McpInstallResult, type McpInstallState, type McpInstallStateReason, type McpInstallStatus, type McpLayoutOptions, type McpUninstallFileResult, type McpUninstallRequest, type McpUninstallResult, OpenCodeMcpCleanupError, type OpenCodeMcpLayout, OpenCodeMcpLayoutUnknownError, type OpenCodeMcpPolicy, SKILL_MANIFEST_FILE_NAME, SKILL_MANIFEST_SCHEMA_VERSION, type SkillAgent, SkillConflictError, type SkillFiles, type SkillInstallAction, type SkillInstallOptions, type SkillInstallResult, type SkillManifest, type SkillOperation, type SkillReason, type SkillState, type SkillStatus, type SkillUninstallAction, type SkillUninstallOptions, type SkillUninstallResult, SkillUnsupportedManifestError, allClients, allSkillAgents, antigravityLegacyPath, clientCommand, clientConfigPath, clientConfigPaths, clientDisplayName, clientExecutablePath, clientJSONPath, clientType, clientsForHost, detectClient, eventManifest, executableForHook, generateConfigEntry, getClientInstallation, getMcpCliInstallCommand, hookCapabilities, hookClientInfo, hookHosts, hostIdentity, hostOf, inspectHooks, inspectMcpInstall, inspectSkillStatus, installHooks, installJsonHooks, installMcp, installSkill, isInstallable, isLegacy, isSkillAgentSupported, isSupported, joinPath, legacyClientIds, listAgents, mcpNamesForHook, resolveHookClient, skillDir, uninstallHooks, uninstallJsonHooks, uninstallMcp, uninstallSkill, validateHookEvents };
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{joinPath as e}from"./config.js";import{
|
|
1
|
+
import{joinPath as e}from"./config.js";import{HookConfigError as t}from"./hooks/errors.js";import{eventManifest as n}from"./hooks/events.js";import{executableForHook as r,hookClientInfo as i,hookHosts as a}from"./hooks/registry.js";import{validateHookEvents as o}from"./hooks/validate.js";import{installJsonHooks as s,uninstallJsonHooks as c}from"./hooks/writers.js";import{inspectHooks as l}from"./hooks/inspect.js";import{OpenCodeMcpCleanupError as u,OpenCodeMcpLayoutUnknownError as d}from"./mcp/opencode-layout.js";import{allClients as f,antigravityLegacyPath as p,clientCommand as m,clientConfigPath as h,clientConfigPaths as g,clientDisplayName as _,clientExecutablePath as v,clientJSONPath as y,clientType as b,clientsForHost as x,detectClient as S,getClientInstallation as C,hostIdentity as w,hostOf as T,isInstallable as E,isLegacy as D,isSupported as O,legacyClientIds as k}from"./mcp/clients.js";import{hookCapabilities as A,listAgents as j,mcpNamesForHook as M,resolveHookClient as N}from"./agents.js";import{installHooks as P,uninstallHooks as F}from"./hooks/install.js";import{generateConfigEntry as I}from"./mcp/config-entry.js";import{inspectMcpInstall as L,installMcp as R}from"./mcp/installer.js";import{getMcpCliInstallCommand as z}from"./mcp/cli-install-command.js";import{uninstallMcp as B}from"./mcp/uninstaller.js";import{SKILL_MANIFEST_FILE_NAME as V,SKILL_MANIFEST_SCHEMA_VERSION as H}from"./skills/manifest.js";import{allSkillAgents as U,isSkillAgentSupported as W,skillDir as G}from"./skills/skill-dirs.js";import{SkillConflictError as K,SkillUnsupportedManifestError as q,inspectSkillStatus as J,installSkill as Y,uninstallSkill as X}from"./skills/install.js";export{t as HookConfigError,u as OpenCodeMcpCleanupError,d as OpenCodeMcpLayoutUnknownError,V as SKILL_MANIFEST_FILE_NAME,H as SKILL_MANIFEST_SCHEMA_VERSION,K as SkillConflictError,q as SkillUnsupportedManifestError,f as allClients,U as allSkillAgents,p as antigravityLegacyPath,m as clientCommand,h as clientConfigPath,g as clientConfigPaths,_ as clientDisplayName,v as clientExecutablePath,y as clientJSONPath,b as clientType,x as clientsForHost,S as detectClient,n as eventManifest,r as executableForHook,I as generateConfigEntry,C as getClientInstallation,z as getMcpCliInstallCommand,A as hookCapabilities,i as hookClientInfo,a as hookHosts,w as hostIdentity,T as hostOf,l as inspectHooks,L as inspectMcpInstall,J as inspectSkillStatus,P as installHooks,s as installJsonHooks,R as installMcp,Y as installSkill,E as isInstallable,D as isLegacy,W as isSkillAgentSupported,O as isSupported,e as joinPath,k as legacyClientIds,j as listAgents,M as mcpNamesForHook,N as resolveHookClient,G as skillDir,F as uninstallHooks,c as uninstallJsonHooks,B as uninstallMcp,X as uninstallSkill,o as validateHookEvents};
|
package/dist/mcp/installer.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{addAtPointer as
|
|
1
|
+
import{writeFileTracked as e}from"../file-change.js";import{addAtPointer as t,assertValidJsonc as n,findAtPointer as r,isValidJsonc as i,removeAtPointerAndEmptyParent as a}from"./jsonc-edit.js";import{OpenCodeMcpCleanupError as o,OpenCodeMcpLayoutUnknownError as s,openCodeNativeServers as c,openCodeRefusal as l,resolveOpenCodePolicy as u}from"./opencode-layout.js";import{assertValidToml as d,parseTomlSafe as f,tomlDottedPath as p,tomlLiteralKey as m}from"./toml-read.js";import{clientConfigPath as h,clientJSONPath as g,isInstallable as _,openCodeJSONPath as v}from"./clients.js";import{generateConfigEntry as y}from"./config-entry.js";import{tomlSemanticEqual as b,withoutCodexEntry as x}from"./toml-safety.js";import{removeBlock as S,replaceOrAppendBlock as C}from"./toml-splice.js";import{stringify as w}from"smol-toml";const T=[`command`,`args`];async function E(n,i){let{client:a}=n,o=_(a)?n.configPath??await h(a,i):void 0;if(!o)throw Error(`unsupported client: ${a}`);if(a===`opencode`)return D(n,i,o);let s=y(a,n.executablePath,n.args);if(a===`codex`)return P(n,i,o,s);let c=await i.fs.readFile(o),l=c===null||c.trim()===``?`{}`:c,u=g(a),d=r(l,u),f=c!==null&&I(d)&&H(d,s,{client:a,path:o,location:u}).state===`current`;return{client:a,path:o,change:f?`unchanged`:await e(i.fs,o,t(l,u,s,o),[],{mode:420}),format:`json`,location:u,entry:f?d:s,removedLocations:[]}}async function D(r,i,s){let c=u(r.openCode),d=await i.fs.readFile(s),f=d===null||d.trim()===``?`{}`:d;n(f,s);let p=l(f,k(c),s);if(p!==void 0)throw Error(p);let m=c.mode===`authoritative`?c.layout:O(f,s),h=j(m),g=v(m),_=v(h),b=y(`opencode`,r.executablePath,r.args,{openCode:{mode:`authoritative`,layout:m}}),x=M(f,m),S=d!==null&&I(x)&&H(x,b,{client:`opencode`,path:s,location:g}).state===`current`,C=c.mode===`authoritative`?M(f,h):void 0,w=S?f:t(f,g,b,s),T=C===void 0?void 0:a(w,_,s),E=S?`unchanged`:await e(i.fs,s,w,[],{mode:420}),D=e=>({client:`opencode`,path:s,change:E,format:`json`,location:g,entry:S?x:b,removedLocations:e,openCodeLayout:m});if(T===void 0||!T.removed)return D([]);try{if(await i.fs.readFile(s)!==w)throw Error(`${s} changed while the OpenCode MCP entry was being installed`);let t=await e(i.fs,s,T.text,[],{mode:420});E===`unchanged`&&(E=t)}catch(e){throw A(e)?e:new o(D([]),e)}return D([_])}function O(e,t){let n=N(e);if(!n)throw new s;if(!I(n.found))throw Error(`cannot safely update OpenCode MCP entry in ${t}: the GitKraken entry is not an object`);return n.layout}function k(e){return e.mode===`authoritative`?e.layout:void 0}function A(e){return e instanceof Error&&e.name===`AbortError`}function j(e){return e===`v1`?`v2`:`v1`}function M(e,t){if(!(t===`v2`&&c(e)===void 0))return r(e,v(t))}function N(e){for(let t of[`v1`,`v2`]){let n=M(e,t);if(n!==void 0)return{layout:t,found:n}}}async function P(t,n,r,i){let a=await n.fs.readFile(r);a!==null&&a.trim()!==``&&d(a,r);let o=g(`codex`),s=a??``,c=f(s),l=p(c,o),u=m(c,o);if(a!==null&&u===void 0&&I(l)&&H(l,i,{client:t.client,path:r,location:o}).state===`current`)return{client:t.client,path:r,change:`unchanged`,format:`toml`,location:o,entry:l,removedLocations:[]};let h,_,v=[];if(u!==void 0){if(!I(u)||l!==void 0&&!I(l))throw Error(`cannot safely migrate Codex MCP entry in ${r}: ambiguous legacy and nested tables`);let e=I(l)&&l.env!==void 0?l.env:u.env;if(e!==void 0&&!I(e))throw Error(`cannot safely migrate Codex MCP entry in ${r}: legacy env is not a table`);if(Object.entries(u).some(([e,t])=>e!==`env`&&I(t))||e&&Object.values(e).some(I))throw Error(`cannot safely migrate Codex MCP entry in ${r}: unsupported legacy subtable`);_=e;let t=S(s,o).text;h=C(l===void 0?t:S(t,o).text,o,F(o,i,e)),v.push(`["${o}"]`)}else h=C(s,o,F(o,i));let y=f(h),w=p(y,o);if(!I(w)||H(w,i,{client:t.client,path:r,location:o}).state!==`current`||m(y,o)!==void 0||u!==void 0&&!b(w.env,_)||!b(x(y),x(c)))throw Error(`cannot safely update Codex MCP entry in ${r}: unsupported TOML representation`);let T=await e(n.fs,r,h,[],{mode:384});return{client:t.client,path:r,change:T,format:`toml`,location:o,entry:w,removedLocations:v}}function F(e,t,n){let r=w(t),i=r.endsWith(`
|
|
2
2
|
`)?r:`${r}\n`,a=n===void 0?``:w(n);return`[${e}]\n${i}${n===void 0?``:`\n[${e}.env]\n${a.endsWith(`
|
|
3
|
-
`)?a:`${a}\n`}`}`}function I(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function L(e,t){return{...e,state:`missing`,reason:t}}function R(e,t){return{...e,state:`drifted`,reason:`legacy-location`,...I(t)&&{entry:t}}}async function z(e,t){let{client:
|
|
3
|
+
`)?a:`${a}\n`}`}`}function I(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function L(e,t){return{...e,state:`missing`,reason:t}}function R(e,t){return{...e,state:`drifted`,reason:`legacy-location`,...I(t)&&{entry:t}}}async function z(e,t){let{client:n}=e,a=n===`opencode`?u(e.openCode):void 0,o=a?.mode===`infer-existing`?void 0:a?.layout,s=n===`opencode`?v(o??`v2`):g(n),c=_(n)?e.configPath??await h(n,t):void 0;if(!c)return L({client:n,path:``,location:s},`unsupported-client`);let l={client:n,path:c,location:s,...o&&{openCodeLayout:o}},d=await t.fs.readFile(c);if(d===null)return L(l,a?.mode===`infer-existing`?`layout-unknown`:`file-missing`);if(n===`codex`)return B(d,y(n,e.executablePath,e.args),l);if(d.trim()===``)return L(l,a?.mode===`infer-existing`?`layout-unknown`:`no-entry`);if(!i(d))return L(l,`invalid-config`);if(a)return V(d,e,a,l);let f=r(d,s);if(f===void 0)return L(l,`no-entry`);let p=y(n,e.executablePath,e.args);return I(f)?H(f,p,l):L(l,`entry-not-object`)}function B(e,t,n){if(e.trim()===``)return L(n,`no-entry`);let r=f(e);if(!r)return L(n,`invalid-config`);let i=m(r,n.location);if(i!==void 0)return R(n,i);let a=p(r,n.location);return a===void 0?L(n,`no-entry`):I(a)?H(a,t,n):L(n,`entry-not-object`)}function V(e,t,n,r){let i=e=>y(`opencode`,t.executablePath,t.args,{openCode:{mode:`authoritative`,layout:e}});if(l(e,k(n),r.path)!==void 0)return L(r,`unsafe-config`);if(n.mode===`infer-existing`){let t=N(e);if(!t)return L(r,`layout-unknown`);let{layout:n,found:a}=t,o=j(n),s={...r,location:v(n),openCodeLayout:n,...M(e,o)!==void 0&&{openCodeDuplicateLocation:v(o)}};return I(a)?H(a,i(n),s):L(s,`entry-not-object`)}let a=M(e,n.layout),o=j(n.layout),s=M(e,o);if(a===void 0)return s===void 0?L(r,`no-entry`):R({...r,location:v(o)},s);let c=s===void 0?r:{...r,openCodeDuplicateLocation:v(o)};if(!I(a))return L(c,`entry-not-object`);let u=H(a,i(n.layout),c);return u.state===`current`&&s!==void 0?R(c,a):u}function H(e,t,n){for(let[r,i]of Object.entries(t)){if(!(r in e)){if(T.includes(r))return{...n,state:`drifted`,reason:`pinned-key-missing`,key:r,entry:e};continue}if(!U(e[r],i))return{...n,state:`drifted`,reason:`value-mismatch`,key:r,entry:e}}return{...n,state:`current`,entry:e}}function U(e,t){if(e===t)return!0;if(typeof e!=typeof t||e===null||t===null||typeof e!=`object`)return!1;if(Array.isArray(e)||Array.isArray(t))return!Array.isArray(e)||!Array.isArray(t)||e.length!==t.length?!1:e.every((e,n)=>U(e,t[n]));let n=Object.keys(e),r=Object.keys(t);return n.length===r.length?n.every(n=>U(e[n],t[n])):!1}export{z as inspectMcpInstall,E as installMcp};
|
package/dist/mcp/jsonc-edit.d.ts
CHANGED
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
declare function isValidJsonc(text: string): boolean;
|
|
4
4
|
/** Throws when `text` isn't valid JSONC; never touches `text`. */
|
|
5
5
|
declare function assertValidJsonc(text: string, path: string): void;
|
|
6
|
+
interface EditOptions {
|
|
7
|
+
/** Edit only the targeted member's text; the formatting of its siblings is left byte-for-byte. */
|
|
8
|
+
preserveSiblings?: boolean;
|
|
9
|
+
}
|
|
6
10
|
/** Reads the value at `pointer` in JSONC `text`, or `undefined` when absent or the document is unparsable. */
|
|
7
11
|
declare function findAtPointer(text: string, pointer: string): unknown;
|
|
8
12
|
/**
|
|
@@ -13,7 +17,7 @@ declare function findAtPointer(text: string, pointer: string): unknown;
|
|
|
13
17
|
* is touched. `sourcePath` names `text`'s file for the error thrown when it
|
|
14
18
|
* isn't valid JSONC; no edit is made in that case.
|
|
15
19
|
*/
|
|
16
|
-
declare function addAtPointer(text: string, pointer: string, value: unknown, sourcePath: string): string;
|
|
20
|
+
declare function addAtPointer(text: string, pointer: string, value: unknown, sourcePath: string, options?: EditOptions): string;
|
|
17
21
|
/**
|
|
18
22
|
* Removes the value at `pointer`, leaving its parent container in place even
|
|
19
23
|
* if that empties it. Mirrors gkcli's `uninstaller.writeConfig`, the shape
|
|
@@ -22,7 +26,7 @@ declare function addAtPointer(text: string, pointer: string, value: unknown, sou
|
|
|
22
26
|
*
|
|
23
27
|
* Returns `removed: false` when there was nothing at `pointer` to begin with.
|
|
24
28
|
*/
|
|
25
|
-
declare function removeAtPointer(text: string, pointer: string, sourcePath: string): {
|
|
29
|
+
declare function removeAtPointer(text: string, pointer: string, sourcePath: string, options?: EditOptions): {
|
|
26
30
|
text: string;
|
|
27
31
|
removed: boolean;
|
|
28
32
|
};
|
|
@@ -35,9 +39,9 @@ declare function removeAtPointer(text: string, pointer: string, sourcePath: stri
|
|
|
35
39
|
*
|
|
36
40
|
* Returns `removed: false` when there was nothing at `pointer` to begin with.
|
|
37
41
|
*/
|
|
38
|
-
declare function removeAtPointerAndEmptyParent(text: string, pointer: string, sourcePath: string): {
|
|
42
|
+
declare function removeAtPointerAndEmptyParent(text: string, pointer: string, sourcePath: string, options?: EditOptions): {
|
|
39
43
|
text: string;
|
|
40
44
|
removed: boolean;
|
|
41
45
|
};
|
|
42
46
|
//#endregion
|
|
43
|
-
export { addAtPointer, assertValidJsonc, findAtPointer, isValidJsonc, removeAtPointer, removeAtPointerAndEmptyParent };
|
|
47
|
+
export { EditOptions, addAtPointer, assertValidJsonc, findAtPointer, isValidJsonc, removeAtPointer, removeAtPointerAndEmptyParent };
|
package/dist/mcp/jsonc-edit.js
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
1
|
import{applyEdits as e,findNodeAtLocation as t,getNodeValue as n,modify as r,parseTree as i,visit as a}from"jsonc-parser";const o={tabSize:2,insertSpaces:!0,eol:`
|
|
2
|
-
`},s={allowTrailingComma:!0};function c(e){return e.startsWith(``)?{bom:``,body:e.slice(1)}:{bom:``,body:e}}function l(e){let t=[];return i(c(e).body,t,s),t.length===0}function u(e,t){if(!l(e))throw Error(`invalid JSON in ${t}`)}function d(e
|
|
3
|
-
|
|
2
|
+
`},s={allowTrailingComma:!0};function c(e){return e.startsWith(``)?{bom:``,body:e.slice(1)}:{bom:``,body:e}}function l(e){let t=[];return i(c(e).body,t,s),t.length===0}function u(e,t){if(!l(e))throw Error(`invalid JSON in ${t}`)}function d(e,t){return e.lastIndexOf(`
|
|
3
|
+
`,t-1)+1}function f(e,t){let n=e.indexOf(`
|
|
4
|
+
`,t);return n<0?e.length:n}function p(e,t){let n=f(e,t);return n>t&&e[n-1]===`\r`?n-1:n}function m(e,t,n=`
|
|
5
|
+
`){return JSON.stringify(e,null,2).replace(/\n/g,`${n}${t}`)}function h(t,n,r,i){let a=n.children??[],o=a[a.length-1],s=o.offset+o.length,c=n.offset+n.length-1,l=E(t,s,c),u=t.slice(d(t,o.offset),o.offset),f=p(t,l??s),h=t.includes(`\r
|
|
6
|
+
`)?`\r
|
|
7
|
+
`:`
|
|
8
|
+
`,g=`${JSON.stringify(r)}: `;if(!/^\s*$/.test(u)||f>=c)return e(t,[{offset:s,length:0,content:`, ${g}${JSON.stringify(i)}`}]);let _=`${u}${g}${m(i,u,h)}`;return e(t,l===void 0?[{offset:s,length:0,content:`,`},{offset:f,length:0,content:`${h}${_}`}]:[{offset:f,length:0,content:`${h}${_},`}])}function g(t,r,i){if(JSON.stringify(n(r))===JSON.stringify(i))return t;let a=t.slice(d(t,r.offset),r.offset).match(/^\s*/)?.[0]??``;return e(t,[{offset:r.offset,length:r.length,content:m(i,a)}])}function _(e,n,r){let a=i(e,void 0,s);if(!a)return;let o=x(a,n),c=a,l=0;for(;l<o.length;l++){let e=t(c,[o[l]]);if(!e)break;c=e}if(l===o.length)return g(e,c,r);if(c.type!==`object`||!c.children?.length)return;let u=o.slice(l+1).reduceRight((e,t)=>({[t]:e}),r);return v(h(e,c,String(o[l]),u),n,r)}function v(e,r,a){let o=i(e,void 0,s),c=o?t(o,x(o,r)):void 0;return c&&JSON.stringify(n(c))===JSON.stringify(a)?e:void 0}function y(t,n){let r=n.parent,i=r?.children??[],a=i.indexOf(n),o=n.offset+n.length,s=r?r.offset+r.length-1:t.length,c=E(t,o,a<i.length-1?i[a+1].offset:s),l=[];if(c===void 0&&a>0){let e=i[a-1],r=E(t,e.offset+e.length,n.offset);r!==void 0&&l.push({offset:r,length:1,content:``})}let u=c===void 0?o:c+1,p=d(t,n.offset),m=f(t,u),h=/^\s*$/.test(t.slice(p,n.offset))&&/^\s*(\/\/[^\n]*)?$/.test(t.slice(u,m))&&m<s;return l.push(h?{offset:p,length:m+1-p,content:``}:{offset:n.offset,length:u-n.offset,content:``}),e(t,l.sort((e,t)=>e.offset-t.offset))}function b(e){return e?e.split(`/`).slice(1).map(e=>e.replace(/~1/g,`/`).replace(/~0/g,`~`)):[]}function x(e,n){let r=[],i=e;for(let e of n){let n=i?.type===`array`&&/^(0|[1-9]\d*)$/.test(e)?Number(e):e;r.push(n),i=i?t(i,[n]):void 0}return r}function S(e,r){let a=[],o=i(c(e).body,a,s);if(!o||a.length>0)return;let l=t(o,x(o,b(r)));return l?n(l):void 0}function C(n,a,s,l,d={}){u(n,l);let{bom:f,body:p}=c(n),m=b(a);if(d.preserveSiblings){let e=_(p,m,s);if(e!==void 0)return f+e}let h=p;for(let n=1;n<m.length;n++){let a=i(h),s=a?x(a,m.slice(0,n)):m.slice(0,n);a&&t(a,s)||(h=e(h,r(h,s,{},{formattingOptions:o})))}let g=i(h),v=g?x(g,m):m;return f+e(h,r(h,v,s,{formattingOptions:o}))}function w(n,a,c={}){let l=i(n,void 0,s),u=l?x(l,a):a,d=l?t(l,u):void 0;if(!(a.length===0||!l||!d))return d.parent?.type===`array`&&typeof u.at(-1)==`number`?T(n,d.parent,d):c.preserveSiblings&&d.parent?.type===`property`?y(n,d.parent):D(e(n,r(n,u,void 0,{formattingOptions:o})))}function T(t,n,r){let i=n.children??[],a=i.indexOf(r),o=[{offset:r.offset,length:r.length,content:``}];if(a<i.length-1){let e=E(t,r.offset+r.length,i[a+1].offset);e!==void 0&&o.push({offset:e,length:1,content:``})}else if(a>0){let e=i[a-1],n=E(t,e.offset+e.length,r.offset);n!==void 0&&o.push({offset:n,length:1,content:``})}else{let e=E(t,r.offset+r.length,n.offset+n.length-1);e!==void 0&&o.push({offset:e,length:1,content:``})}return e(t,o.sort((e,t)=>e.offset-t.offset))}function E(e,t,n){for(let r=t;r<n;r++)if(e.startsWith(`//`,r))for(;r<n&&e[r]!==`
|
|
9
|
+
`;)r++;else if(e.startsWith(`/*`,r)){let t=e.indexOf(`*/`,r+2);if(t<0||t>=n)return;r=t+1}else if(e[r]===`,`)return r}function D(e){let t={justOpened:!1},n=()=>{t.justOpened=!1};a(e,{onObjectBegin:()=>{t.justOpened=!0},onSeparator:(e,r)=>{t.justOpened&&e===`,`&&(t.commaOffset??=r),n()},onObjectProperty:n,onObjectEnd:n},s);let{commaOffset:r}=t;return r===void 0?e:e.slice(0,r)+e.slice(r+1)}function O(e,t,n,r={}){u(e,n);let{bom:i,body:a}=c(e),o=w(a,b(t),r);return o===void 0?{text:e,removed:!1}:{text:i+o,removed:!0}}function k(e,t,n,r={}){u(e,n);let{bom:i,body:a}=c(e),o=b(t),s=w(a,o,r);if(s===void 0)return{text:e,removed:!1};let l=o.slice(0,-1);return l.length>0&&A(s,l)&&(s=w(s,l,r)??s),{text:i+s,removed:!0}}function A(e,n){let r=i(e,void 0,s);if(!r)return!1;let a=t(r,x(r,n));return!a||a.type!==`object`?!1:(a.children?.length??0)===0}export{C as addAtPointer,u as assertValidJsonc,S as findAtPointer,l as isValidJsonc,O as removeAtPointer,k as removeAtPointerAndEmptyParent};
|
package/dist/mcp/uninstaller.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{removeAtPointer as
|
|
1
|
+
import{writeFileTracked as e}from"../file-change.js";import{removeAtPointer as t,removeAtPointerAndEmptyParent as n}from"./jsonc-edit.js";import{assertValidToml as r,parseTomlSafe as i,tomlDottedPath as a,tomlLiteralKey as o}from"./toml-read.js";import{clientConfigPaths as s,clientJSONPath as c,isSupported as l,openCodeEntryPointers as u}from"./clients.js";import{tomlSemanticEqual as d,withoutCodexEntry as f}from"./toml-safety.js";import{removeBlock as p,trimTrailingBlankLines as m}from"./toml-splice.js";function h(e,t){return{file:{path:e,change:`unchanged`,removedLocations:[],reason:t}}}async function g(t,n){let{client:r}=t;if(!l(r))throw Error(`unsupported client: ${r}`);let i=t.configPath?[t.configPath]:await s(r,n),a=[];for(let e of i)a.push(await _(r,e,n));let o=[];for(let{file:t,text:r}of a)r!==void 0&&await e(n.fs,t.path,r,[]),o.push(t);return{client:r,removed:o.some(e=>e.removedLocations.length>0),files:o}}async function _(e,t,n){let r=await n.fs.readFile(t);if(r===null)return h(t,`file-missing`);if(r.trim()===``)return h(t,`file-empty`);let{text:i,removedLocations:a}=e===`codex`?y(r,t):v(r,e,t);return a.length===0?h(t,`no-entry`):{file:{path:t,change:`updated`,removedLocations:a},text:i}}function v(e,r,i){let a=r===`opencode`,o=a?u(e):[c(r)],s=e,l=[];for(let e of o){let r=a?n(s,e,i):t(s,e,i);r.removed&&(s=r.text,l.push(e))}return{text:s,removedLocations:l}}function y(e,t){r(e,t);let n=c(`codex`),s=i(e);if(o(s,n)===void 0&&a(s,n)===void 0)return{text:e,removedLocations:[]};let{text:l,removed:u}=p(e,n);if(!u){if(o(s,n)!==void 0||a(s,n)!==void 0)throw Error(`cannot safely remove Codex MCP entry in ${t}: unsupported TOML representation`);return{text:e,removedLocations:[]}}let h=m(l),g=i(h);if(o(g,n)!==void 0||a(g,n)!==void 0)throw Error(`cannot safely remove Codex MCP entry in ${t}: target remains after splice`);if(!g||!d(f(g),f(s)))throw Error(`cannot safely remove Codex MCP entry in ${t}: splice would change another TOML value`);return{text:h,removedLocations:[n]}}export{g as uninstallMcp};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gitkraken/agent-tools",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Host configuration and path helpers for AI coding agent integrations, with an in-memory filesystem for testing.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"author": "Axosoft, LLC dba GitKraken",
|