@gitkraken/agent-tools 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/LICENSE +19 -0
  3. package/README.md +163 -2
  4. package/dist/agents.d.ts +52 -0
  5. package/dist/agents.js +1 -0
  6. package/dist/config.d.ts +69 -0
  7. package/dist/config.js +1 -0
  8. package/dist/file-change.d.ts +28 -0
  9. package/dist/file-change.js +1 -0
  10. package/dist/hooks/claude.d.ts +16 -0
  11. package/dist/hooks/claude.js +3 -0
  12. package/dist/hooks/errors.d.ts +10 -0
  13. package/dist/hooks/errors.js +1 -0
  14. package/dist/hooks/events.d.ts +73 -0
  15. package/dist/hooks/events.js +1 -0
  16. package/dist/hooks/install.d.ts +15 -0
  17. package/dist/hooks/install.js +1 -0
  18. package/dist/hooks/results.d.ts +30 -0
  19. package/dist/hooks/results.js +1 -0
  20. package/dist/hooks/templates.d.ts +15 -0
  21. package/dist/hooks/templates.js +278 -0
  22. package/dist/hooks/writers.d.ts +18 -0
  23. package/dist/hooks/writers.js +1 -0
  24. package/dist/index.d.ts +18 -0
  25. package/dist/index.js +1 -0
  26. package/dist/mcp/cli-install-command.d.ts +27 -0
  27. package/dist/mcp/cli-install-command.js +1 -0
  28. package/dist/mcp/clients.d.ts +96 -0
  29. package/dist/mcp/clients.js +1 -0
  30. package/dist/mcp/config-entry.d.ts +15 -0
  31. package/dist/mcp/config-entry.js +1 -0
  32. package/dist/mcp/index.d.ts +7 -0
  33. package/dist/mcp/index.js +1 -0
  34. package/dist/mcp/installer.d.ts +130 -0
  35. package/dist/mcp/installer.js +3 -0
  36. package/dist/mcp/jsonc-edit.d.ts +43 -0
  37. package/dist/mcp/jsonc-edit.js +3 -0
  38. package/dist/mcp/opencode-layout.d.ts +54 -0
  39. package/dist/mcp/opencode-layout.js +1 -0
  40. package/dist/mcp/toml-read.d.ts +11 -0
  41. package/dist/mcp/toml-read.js +1 -0
  42. package/dist/mcp/toml-safety.d.ts +7 -0
  43. package/dist/mcp/toml-safety.js +1 -0
  44. package/dist/mcp/toml-splice.d.ts +16 -0
  45. package/dist/mcp/toml-splice.js +11 -0
  46. package/dist/mcp/uninstaller.d.ts +41 -0
  47. package/dist/mcp/uninstaller.js +1 -0
  48. package/dist/skills/index.d.ts +4 -0
  49. package/dist/skills/index.js +1 -0
  50. package/dist/skills/install.d.ts +126 -0
  51. package/dist/skills/install.js +1 -0
  52. package/dist/skills/manifest.d.ts +56 -0
  53. package/dist/skills/manifest.js +1 -0
  54. package/dist/skills/skill-dirs.d.ts +22 -0
  55. package/dist/skills/skill-dirs.js +1 -0
  56. package/dist/testing/index.d.ts +2 -0
  57. package/dist/testing/index.js +1 -0
  58. package/dist/testing/memory-fs.d.ts +22 -0
  59. package/dist/testing/memory-fs.js +1 -0
  60. package/package.json +39 -3
@@ -0,0 +1,278 @@
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 i=`// gk-managed:`;function a(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 o(e){return e?.split(`
2
+ `,1)[0].startsWith(i)??!1}function s(e){if(!o(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 c(e,t,n,r){let a={events:t,executable:n,version:e===`opencode`?5:1};e===`opencode`&&r&&(a.hostMajor=r);let o=e===`opencode`?`import { spawn } from "node:child_process";
4
+
5
+ const EVENTS = new Set({{.EventsJSON}});
6
+ const EXECUTABLE = {{.ExecJSON}};
7
+ const HOST = {{.HostJSON}};
8
+ const MAX_HOOK_INPUT_BYTES = {{.MaxInputBytesJSON}};
9
+
10
+ function getString(value) {
11
+ return typeof value === "string" && value.length > 0 ? value : "";
12
+ }
13
+
14
+ // extractToolName is the one piece of the relay every host must supply: only
15
+ // the host knows where its payloads carry a tool name. The default below
16
+ // returns null so a host that forgets it still forwards events with
17
+ // tool_name: null, instead of throwing inside runHook's catch-all and
18
+ // silently forwarding nothing at all. Hosts override it with
19
+ // {{define "extractToolName"}} — see hook_pi.js.tmpl and
20
+ // hook_opencode.js.tmpl.
21
+ function extractToolName(payload) {
22
+ return (
23
+ getString(payload?.input?.tool) ||
24
+ getString(payload?.event?.properties?.part?.tool) ||
25
+ getString(payload?.event?.properties?.tool) ||
26
+ // v2's tool.hook("execute.before"/"execute.after") callbacks pass { event },
27
+ // where event.tool is the tool name directly (e.g. "read").
28
+ getString(payload?.event?.tool) ||
29
+ ""
30
+ );
31
+ }
32
+
33
+ const V1_ONLY_EVENTS = new Set({{.V1OnlyEventsJSON}});
34
+ const V2_EVENT_ALIASES = new Map(Object.entries({{.V2EventAliasesJSON}}));
35
+
36
+ function extractSessionID(event) {
37
+ return (
38
+ getString(event?.properties?.sessionID) ||
39
+ getString(event?.properties?.sessionId) ||
40
+ getString(event?.properties?.session_id) ||
41
+ // A message or part names its session in its own sessionID; its id names
42
+ // the message or part. Only a session's own info carries the session id
43
+ // as id, so that fallback comes after every explicit session field.
44
+ getString(event?.properties?.info?.sessionID) ||
45
+ getString(event?.properties?.part?.sessionID) ||
46
+ getString(event?.properties?.info?.id) ||
47
+ getString(event?.properties?.id) ||
48
+ getString(event?.sessionID) ||
49
+ getString(event?.sessionId) ||
50
+ getString(event?.session_id) ||
51
+ // v2's event bus nests the id under \`data\` instead of \`properties\`. The
52
+ // event's own top-level id is never a fallback: on v2 it names the event,
53
+ // and relaying it would report a session that does not exist.
54
+ getString(event?.data?.sessionID)
55
+ );
56
+ }
57
+
58
+
59
+
60
+ // server is the OpenCode v1 plugin entrypoint (returns a Hooks object). v1
61
+ // calls this directly off the default export below.
62
+ const server = async (ctx) => {
63
+ // No process.cwd() fallback: that is the host's launch directory, which the
64
+ // relay cannot tell apart from a real per-session cwd and would use to
65
+ // attribute the session to the wrong repo. Each hook below drops its event
66
+ // instead when cwd is empty, so the spawn is skipped entirely rather than
67
+ // paid for and discarded by the relay's own missing-cwd guard.
68
+ const cwd = getString(ctx.directory) || getString(ctx.worktree);
69
+ const hooks = {};
70
+
71
+ // General event hook — forwards native dot names as-is.
72
+ hooks.event = async ({ event }) => {
73
+ if (!cwd) return;
74
+ const type = event?.type ?? "";
75
+ if (type === "tool.execute.before" || type === "tool.execute.after") return;
76
+ const sessionID = extractSessionID(event);
77
+ if (!sessionID) return;
78
+ runHook(type, sessionID, cwd, { event });
79
+ };
80
+
81
+ // Dedicated tool hooks — provide structured sessionID and tool name.
82
+ if (EVENTS.has("tool.execute.before")) {
83
+ hooks["tool.execute.before"] = async (input, output) => {
84
+ if (!cwd) return;
85
+ const sessionID =
86
+ getString(input?.sessionID) || getString(input?.sessionId) || getString(input?.session_id);
87
+ if (!sessionID) return;
88
+ runHook("tool.execute.before", sessionID, cwd, { input, output });
89
+ };
90
+ }
91
+ if (EVENTS.has("tool.execute.after")) {
92
+ hooks["tool.execute.after"] = async (input, output) => {
93
+ if (!cwd) return;
94
+ const sessionID =
95
+ getString(input?.sessionID) || getString(input?.sessionId) || getString(input?.session_id);
96
+ if (!sessionID) return;
97
+ runHook("tool.execute.after", sessionID, cwd, { input, output });
98
+ };
99
+ }
100
+
101
+ return hooks;
102
+ };
103
+
104
+ // setup is the OpenCode v2 plugin entrypoint. v2 replaced the returned-Hooks
105
+ // shape with named registration APIs (ctx.tool.hook, ctx.event.subscribe). It
106
+ // dropped the v1 names ctx.directory/ctx.worktree, but the directory itself is
107
+ // still on the context as ctx.location.directory, so that is the base cwd.
108
+ const setup = async (ctx) => {
109
+ // Per-session directories arrive only on the subset of events that carry a
110
+ // location, and the subscription only yields from plugin load onward, so a
111
+ // session predating this plugin instance (server restart, resumed session)
112
+ // never populates the cache. Treat the cache as an override on top of the
113
+ // context directory rather than the only source, and never guess: with no
114
+ // directory at all, send "" so the relay drops the event instead of
115
+ // attributing the session to the host's launch directory.
116
+ const baseCwd =
117
+ getString(ctx?.location?.directory) || getString(ctx?.directory) || getString(ctx?.worktree);
118
+ // session.deleted is an explicit, rare user action, so eviction on it alone
119
+ // would let a host left running for days accumulate one entry per session it
120
+ // ever saw. Cap the map the way runner.go caps its cwd timeline and drop the
121
+ // oldest entry first (Map iterates in insertion order); a session evicted
122
+ // this way falls back to baseCwd, same as one that never carried a location.
123
+ const MAX_TRACKED_SESSIONS = 50;
124
+ const cwdBySession = new Map();
125
+ const rememberCwd = (sessionID, directory) => {
126
+ // Re-insert so a session still in use is treated as the newest entry.
127
+ cwdBySession.delete(sessionID);
128
+ cwdBySession.set(sessionID, directory);
129
+ while (cwdBySession.size > MAX_TRACKED_SESSIONS) {
130
+ cwdBySession.delete(cwdBySession.keys().next().value);
131
+ }
132
+ };
133
+ const resolveCwd = (sessionID) => (sessionID && cwdBySession.get(sessionID)) || baseCwd;
134
+
135
+ if (EVENTS.has("tool.execute.before")) {
136
+ await ctx.tool.hook("execute.before", (event) => {
137
+ const sessionID = getString(event?.sessionID);
138
+ if (!sessionID) return;
139
+ runHook("tool.execute.before", sessionID, resolveCwd(sessionID), { event });
140
+ });
141
+ }
142
+ if (EVENTS.has("tool.execute.after")) {
143
+ await ctx.tool.hook("execute.after", (event) => {
144
+ const sessionID = getString(event?.sessionID);
145
+ if (!sessionID) return;
146
+ runHook("tool.execute.after", sessionID, resolveCwd(sessionID), { event });
147
+ });
148
+ }
149
+
150
+ const controller = new AbortController();
151
+ (async () => {
152
+ try {
153
+ for await (const event of ctx.event.subscribe({ signal: controller.signal })) {
154
+ const sessionID = extractSessionID(event);
155
+ const directory =
156
+ getString(event?.location?.directory) || getString(event?.data?.location?.directory);
157
+ if (sessionID && directory) rememberCwd(sessionID, directory);
158
+
159
+ const type = event?.type ?? "";
160
+ if (type === "tool.execute.before" || type === "tool.execute.after") continue;
161
+ if (V1_ONLY_EVENTS.has(type)) continue;
162
+ // File edits, branch changes and todo updates can arrive without a
163
+ // session; there is no conversation to attribute them to.
164
+ if (!sessionID) continue;
165
+ runHook(type, sessionID, resolveCwd(sessionID), { event }, V2_EVENT_ALIASES.get(type));
166
+ // Evict regardless of whether session.deleted is in the installed
167
+ // EVENTS set, so an explicitly deleted session is released immediately
168
+ // rather than waiting to age out of the cap above.
169
+ if (type === "session.deleted") cwdBySession.delete(sessionID);
170
+ }
171
+ } catch (_) {
172
+ // Subscription tears down when the plugin is disposed; must never throw.
173
+ }
174
+ })();
175
+
176
+ return () => controller.abort();
177
+ };
178
+
179
+ // OpenCode v2 validates the default export as an object before reading setup;
180
+ // callable objects fail that schema. v1.4+ reads server from this same shape.
181
+ // Older v1 hosts cannot share one module shape with v2 and are unsupported by
182
+ // this v2-capable template.
183
+ export default { id: "gk-hooks", server, setup };
184
+ `:`import { spawn } from "node:child_process";
185
+
186
+ const EVENTS = new Set({{.EventsJSON}});
187
+ const EXECUTABLE = {{.ExecJSON}};
188
+ const HOST = {{.HostJSON}};
189
+ const MAX_HOOK_INPUT_BYTES = {{.MaxInputBytesJSON}};
190
+
191
+ function getString(value) {
192
+ return typeof value === "string" && value.length > 0 ? value : "";
193
+ }
194
+
195
+ // extractToolName is the one piece of the relay every host must supply: only
196
+ // the host knows where its payloads carry a tool name. The default below
197
+ // returns null so a host that forgets it still forwards events with
198
+ // tool_name: null, instead of throwing inside runHook's catch-all and
199
+ // silently forwarding nothing at all. Hosts override it with
200
+ // {{define "extractToolName"}} — see hook_pi.js.tmpl and
201
+ // hook_opencode.js.tmpl.
202
+ function extractToolName(payload) {
203
+ return getString(payload?.event?.toolName);
204
+ }
205
+
206
+
207
+
208
+ function sessionIdOf(ctx) {
209
+ return getString(ctx?.sessionManager?.getSessionId?.());
210
+ }
211
+
212
+ export default function (pi) {
213
+ for (const name of EVENTS) {
214
+ pi.on(name, async (event, ctx) => {
215
+ // A reload is not a session ending; forwarding it would kill a live session.
216
+ if (name === "session_shutdown" && event?.reason === "reload") return;
217
+ // No process.cwd() fallback: that is pi's launch directory, which the
218
+ // relay cannot tell apart from a real per-session cwd and would use to
219
+ // attribute the session to the wrong repo. Dropping the event lets the
220
+ // relay's missing-cwd guard stay meaningful.
221
+ const cwd = getString(ctx?.cwd);
222
+ if (!cwd) return;
223
+ runHook(name, sessionIdOf(ctx), cwd, { event });
224
+ });
225
+ }
226
+ }
227
+ `,s={EventsJSON:JSON.stringify(t),ExecJSON:JSON.stringify(n),HostJSON:JSON.stringify(e),MaxInputBytesJSON:`1048576`,V1OnlyEventsJSON:JSON.stringify([`session.updated`,`session.error`]),V2EventAliasesJSON:JSON.stringify({"session.execution.failed":[`session.error`],"session.status":[`session.updated`]})},c=e===`opencode`?`
228
+
229
+ const V1_ONLY_EVENTS`:`
230
+
231
+
232
+
233
+ function sessionIdOf`,l=o.indexOf(c);if(l<0)throw Error(`missing ${e} relay insertion point`);let u=o.slice(0,l)+`
234
+
235
+ function buildHookInput(eventName, sessionID, cwd, payload) {
236
+ const toolName = extractToolName(payload);
237
+ const hookInput = {
238
+ hook_event_name: eventName,
239
+ session_id: sessionID || "",
240
+ cwd,
241
+ tool_name: toolName || null,
242
+ source: null,
243
+ model: null,
244
+ reason: null,
245
+ agent_id: null,
246
+ agent_type: null,
247
+ hook_payload: payload || null,
248
+ };
249
+
250
+ const serializedInput = JSON.stringify(hookInput);
251
+ const payloadBytes = Buffer.byteLength(serializedInput, "utf8");
252
+ if (payloadBytes <= MAX_HOOK_INPUT_BYTES) {
253
+ return serializedInput;
254
+ }
255
+
256
+ return JSON.stringify({
257
+ ...hookInput,
258
+ hook_payload: { truncated: true, originalBytes: payloadBytes },
259
+ });
260
+ }
261
+
262
+ function runHook(eventName, sessionID, cwd, payload, aliases = []) {
263
+ if (!EVENTS.has(eventName) && !aliases.some((alias) => EVENTS.has(alias))) return;
264
+ try {
265
+ const input = buildHookInput(eventName, sessionID, cwd, payload);
266
+ const child = spawn(EXECUTABLE, ["ai", "hook", "run", "--host", HOST], {
267
+ stdio: ["pipe", "ignore", "ignore"],
268
+ windowsHide: true,
269
+ });
270
+ child.on("error", () => {});
271
+ child.stdin.on("error", () => {});
272
+ child.stdin.end(input);
273
+ child.unref();
274
+ } catch (_) {
275
+ // Hooks must never block the host
276
+ }
277
+ }`+o.slice(l);return i+JSON.stringify(a)+`
278
+ `+u.replace(/{{\.(\w+)}}/g,(e,t)=>s[t]??``)}async function l(n,r,i){let s=a(n,r),c=s.slice(0,s.lastIndexOf(`/`));for(let n of await r.fs.readDir(c)??[]){if(n===`gk-hooks.js`||!n.startsWith(`gk-hooks.`))continue;let a=e(c,n);o(await r.fs.readFile(a))&&await t(r.fs,a,i)}}async function u(e,t){let i=a(e.host,t);if(e.blockingEvents?.length)throw new r(`blocking-unsupported`,`${e.host} does not support blocking hooks`,i);let u=await t.fs.readFile(i),d=s(u);if(u!==null&&!o(u))throw new r(`unmanaged-file`,`${i} already exists and was not written by GitKraken`,i);let f=[...new Set([...d?.events??[],...e.events??[]])].sort(),p=e.host===`opencode`?t.openCodeMajor??0:0,m=[];return await n(t.fs,i,c(e.host,f,e.executablePath.replace(/\\/g,`/`),p),m,{mode:384}),await l(e.host,t,m),{host:e.host,path:i,events:[...e.events??[]],blockingEvents:[],files:m}}async function d(e,r,i){let o=a(e,i),u=[],d=await i.fs.readFile(o),f=s(d),p=[],m=[];f&&(p=f.events.filter(e=>(r??f.events).includes(e)),m=f.events.filter(e=>!p.includes(e)).sort(),m.length?await n(i.fs,o,c(e,m,f.executable,f.hostMajor),u,{mode:384}):await t(i.fs,o,u)),await l(e,i,u);let h={host:e,path:o,removedEvents:p,remainingEvents:m,files:u};return p.length||(h.reason=d===null?`file-missing`:`not-installed`),h}export{u as installMarkerHooks,d as uninstallMarkerHooks};
@@ -0,0 +1,18 @@
1
+ import { AgentHostConfig } from "../config.js";
2
+ import { HookHost } from "./events.js";
3
+ import { HookInstallResult, HookUninstallResult } from "./results.js";
4
+
5
+ //#region src/hooks/writers.d.ts
6
+ type JsonHookHost = Extract<HookHost, 'cursor' | 'codex' | 'copilot' | 'antigravity' | 'augment' | 'grok'>;
7
+ interface HookWriteRequest {
8
+ host: HookHost;
9
+ executablePath: string;
10
+ events?: readonly string[];
11
+ blockingEvents?: readonly string[];
12
+ }
13
+ declare function command(executablePath: string, host: HookHost, config: AgentHostConfig, blocking: boolean): string;
14
+ /** Merges only GitKraken-owned hooks, preserving unrelated entries and document keys. */
15
+ declare function installJsonHooks(request: HookWriteRequest, config: AgentHostConfig): Promise<HookInstallResult>;
16
+ declare function uninstallJsonHooks(host: JsonHookHost, config: AgentHostConfig, events?: readonly string[]): Promise<HookUninstallResult>;
17
+ //#endregion
18
+ export { HookWriteRequest, command, installJsonHooks, uninstallJsonHooks };
@@ -0,0 +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 i}from"./events.js";const a={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}};function o(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function s(e){return typeof e==`string`&&e.includes(` ai hook run --host `)}function c(e,t,n,r){return`${n.platform===`win32`&&`windowsShell`in i[t]&&i[t].windowsShell===`powershell`?`& `:``}"${e.replaceAll(`\\`,`/`)}" ai hook run --host ${t}${r?` --blocking`:``}`}function l(e){let t=i[e.host],n=e.events?.length||e.blockingEvents?.length?e.events??[]:t.events.filter(e=>!t.skippedDefaultEvents.includes(e)),a=e.blockingEvents??[];for(let i of[...n,...a])if(!t.events.includes(i))throw new r(`invalid-event`,`${i} is not valid for ${e.host}`);return[n,a]}function u(e,t){return e===`antigravity`?t===`PreToolUse`||t===`PostToolUse`:a[e].matcher}function d(e,t,n){if(!Array.isArray(e)||!e.every(o))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(o))))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 f(e,t){return Array.isArray(e)?e.some(e=>t&&o(e)&&Array.isArray(e.hooks)?e.hooks.some(e=>o(e)&&s(e.command)):o(e)&&s(e.command)):!1}function p(e,t){return Object.keys(t).filter(n=>f(t[n],u(e,n)))}function m(e,t){return t?e.flatMap(e=>{if(!Array.isArray(e.hooks))return[e];let t=e.hooks.filter(e=>!o(e)||!s(e.command));return t.length===e.hooks.length?[e]:t.length?[{...e,hooks:t}]:[]}):e.filter(e=>!s(e.command))}function h(e,t,n,r,a){let o=86400*(`blockingTimeoutMultiplier`in i[n]?Number(i[n].blockingTimeoutMultiplier):1),c={type:`command`,command:r,...a?{timeout:o}:{}};if(!t)return[...m(e,!1),c];let l=m(e,!0),u=l.find(e=>!e.matcher&&Array.isArray(e.hooks)&&e.hooks.length>0);u||(u=n===`augment`?{hooks:[]}:{matcher:``,hooks:[]},l.push(u));let d=Array.isArray(u.hooks)?u.hooks:[];return u.hooks=[...d.filter(e=>!s(e.command)),c],l}function g(e,t){let n;try{n=JSON.parse(e.replace(/^\uFEFF/,``))}catch{throw new r(`invalid-json`,`Invalid JSON at ${t}`,t)}if(!o(n))throw new r(`wrong-shape`,`Invalid hooks document at ${t}`,t);return n}function _(e,t,n){let i=e[t];if(i!=null){if(!o(i))throw new r(`wrong-shape`,`Invalid hooks wrapper at ${n}`,n);return i}}async function v(e,t){if(!(e.host in a))throw new r(`unsupported-host`,`No JSON writer for ${e.host}`);let o=e.host,s=a[o],f=s.path(t),p=await t.fs.readFile(f),v=p?.trim()?g(p,f):{},y={..._(v,s.key,f)};for(let[e,t]of Object.entries(y))d(t,u(o,e),f);let[b,x]=l(e);for(let[n,r]of[[b,!1],[x,!0]])for(let i of n){let n=u(o,i);y[i]=h(y[i]??[],n,o,c(e.executablePath,o,t,r),r)}if(`legacyEvents`in i[o])for(let e of i[o].legacyEvents){let t=y[e];if(t){let n=m(t,u(o,e));n.length?y[e]=n:delete y[e]}}Object.keys(y).length?v[s.key]=y:delete v[s.key],s.version&&v.version==null&&(v.version=1);let S=[];return await n(t.fs,f,`${JSON.stringify(v,null,2)}\n`,S),{host:o,path:f,events:[...b],blockingEvents:[...x],files:S}}async function y(e,r,o){let s=a[e],c=s.path(r),l=[],f=(t,n=[])=>({host:e,path:c,removedEvents:[],remainingEvents:n,files:l,reason:t}),h=await r.fs.readFile(c);if(h===null)return f(`file-missing`);if(!h.trim())return f(`not-installed`);let v=g(h,c),y=_(v,s.key,c);if(!y)return f(`not-installed`);let b={...y};for(let[t,n]of Object.entries(b))d(n,u(e,t),c);let x=[],S=o?.length?o:i[e].events;for(let t of[...S,...`legacyEvents`in i[e]?i[e].legacyEvents:[]]){let n=b[t];if(!n)continue;let r=m(n,u(e,t));JSON.stringify(r)!==JSON.stringify(n)&&(x.push(t),r.length?b[t]=r:delete b[t])}let C=p(e,b);if(!x.length)return f(`not-installed`,C);Object.keys(b).length?v[s.key]=b:delete v[s.key];let w=Object.keys(v).some(e=>e!==s.key&&!(e===`version`&&s.version&&v.version===1));return!Object.keys(b).length&&!w&&!s.neverDelete?await t(r.fs,c,l):await n(r.fs,c,`${JSON.stringify(v,null,2)}\n`,l),{host:e,path:c,removedEvents:x,remainingEvents:C,files:l}}export{c as command,v as installJsonHooks,y as uninstallJsonHooks};
@@ -0,0 +1,18 @@
1
+ import { AgentHostConfig, AgentHostPaths, AgentOrigin, joinPath } from "./config.js";
2
+ import { HookHost, eventManifest } from "./hooks/events.js";
3
+ import { McpConfigEntry, generateConfigEntry } from "./mcp/config-entry.js";
4
+ import { McpInstallRequest, McpInstallResult, McpInstallState, McpInstallStateReason, McpInstallStatus, inspectMcpInstall, installMcp } from "./mcp/installer.js";
5
+ import { McpLayoutOptions, OpenCodeMcpCleanupError, OpenCodeMcpLayout, OpenCodeMcpLayoutUnknownError, OpenCodeMcpPolicy } from "./mcp/opencode-layout.js";
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";
8
+ import { FileChange, FileChangeKind } from "./file-change.js";
9
+ import { HookInstallResult, HookUninstallResult } from "./hooks/results.js";
10
+ import { HookConfigError, HookConfigErrorCode } from "./hooks/errors.js";
11
+ import { HookWriteRequest, installJsonHooks, uninstallJsonHooks } from "./hooks/writers.js";
12
+ import { InstallHooksRequest, installHooks, uninstallHooks } from "./hooks/install.js";
13
+ import { getMcpCliInstallCommand } from "./mcp/cli-install-command.js";
14
+ import { McpUninstallFileResult, McpUninstallRequest, McpUninstallResult, uninstallMcp } from "./mcp/uninstaller.js";
15
+ import { SKILL_MANIFEST_FILE_NAME, SKILL_MANIFEST_SCHEMA_VERSION, SkillManifest } from "./skills/manifest.js";
16
+ import { SkillAgent, allSkillAgents, isSkillAgentSupported, skillDir } from "./skills/skill-dirs.js";
17
+ 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 };
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ import{joinPath as e}from"./config.js";import{OpenCodeMcpCleanupError as t,OpenCodeMcpLayoutUnknownError as n}from"./mcp/opencode-layout.js";import{allClients as r,antigravityLegacyPath as i,clientCommand as a,clientConfigPath as o,clientConfigPaths as s,clientDisplayName as c,clientExecutablePath as l,clientJSONPath as u,clientType as d,clientsForHost as f,detectClient as p,getClientInstallation as m,hostIdentity as h,hostOf as g,isInstallable as _,isLegacy as v,isSupported as y,legacyClientIds as b}from"./mcp/clients.js";import{listAgents as x,mcpNamesForHook as S,resolveHookClient as C}from"./agents.js";import{HookConfigError as w}from"./hooks/errors.js";import{eventManifest as T}from"./hooks/events.js";import{installJsonHooks as E,uninstallJsonHooks as D}from"./hooks/writers.js";import{installHooks as O,uninstallHooks as k}from"./hooks/install.js";import{generateConfigEntry as A}from"./mcp/config-entry.js";import{inspectMcpInstall as j,installMcp as M}from"./mcp/installer.js";import{getMcpCliInstallCommand as N}from"./mcp/cli-install-command.js";import{uninstallMcp as P}from"./mcp/uninstaller.js";import{SKILL_MANIFEST_FILE_NAME as F,SKILL_MANIFEST_SCHEMA_VERSION as I}from"./skills/manifest.js";import{allSkillAgents as L,isSkillAgentSupported as R,skillDir as z}from"./skills/skill-dirs.js";import{SkillConflictError as B,SkillUnsupportedManifestError as V,inspectSkillStatus as H,installSkill as U,uninstallSkill as W}from"./skills/install.js";export{w as HookConfigError,t as OpenCodeMcpCleanupError,n as OpenCodeMcpLayoutUnknownError,F as SKILL_MANIFEST_FILE_NAME,I as SKILL_MANIFEST_SCHEMA_VERSION,B as SkillConflictError,V as SkillUnsupportedManifestError,r as allClients,L as allSkillAgents,i as antigravityLegacyPath,a as clientCommand,o as clientConfigPath,s as clientConfigPaths,c as clientDisplayName,l as clientExecutablePath,u as clientJSONPath,d as clientType,f as clientsForHost,p as detectClient,T as eventManifest,A as generateConfigEntry,m as getClientInstallation,N as getMcpCliInstallCommand,h as hostIdentity,g as hostOf,j as inspectMcpInstall,H as inspectSkillStatus,O as installHooks,E as installJsonHooks,M as installMcp,U as installSkill,_ as isInstallable,v as isLegacy,R as isSkillAgentSupported,y as isSupported,e as joinPath,b as legacyClientIds,x as listAgents,S as mcpNamesForHook,C as resolveHookClient,z as skillDir,k as uninstallHooks,D as uninstallJsonHooks,P as uninstallMcp,W as uninstallSkill};
@@ -0,0 +1,27 @@
1
+ import { AgentHostConfig } from "../config.js";
2
+ import { McpInstallRequest } from "./installer.js";
3
+
4
+ //#region src/mcp/cli-install-command.d.ts
5
+ /**
6
+ * Returns the argv of the client's own CLI shortcut for installing the
7
+ * GitKraken MCP server, or `undefined` when the host should go straight to
8
+ * {@link installMcp}. The host runs the argv and falls back to
9
+ * {@link installMcp} on failure, preserving Go's order (`installVSCode`,
10
+ * `installClaudeCLI`, `installCodex`, …).
11
+ *
12
+ * `undefined` when the client has no shortcut, and also when the shortcut
13
+ * would not do the job:
14
+ * - `request.configPath` is set, because no shortcut can target a specific file.
15
+ * - Cursor: gkcli ignores the outcome of `cursor --add-mcp` and always writes
16
+ * `~/.cursor/mcp.json`, so the direct write is the install.
17
+ * - Codex, when its config still holds the legacy `['mcp_servers.GitKraken']`
18
+ * table: `codex mcp add` would leave it behind, so the direct write must
19
+ * replace it.
20
+ *
21
+ * Excludes gkcli's GitLens deep-link URL scheme (`vscode:mcp/install?…`,
22
+ * `cursor://…`), which is a browser-launch side effect outside this
23
+ * library's scope.
24
+ */
25
+ declare function getMcpCliInstallCommand(request: McpInstallRequest, config: AgentHostConfig): Promise<string[] | undefined>;
26
+ //#endregion
27
+ export { getMcpCliInstallCommand };
@@ -0,0 +1 @@
1
+ import{inspectMcpInstall as e}from"./installer.js";function t(e,t,n){return JSON.stringify({command:t,type:`stdio`,name:e,args:[...n]})}async function n(n,r){if(n.configPath)return;let{client:i,executablePath:a,args:o}=n;switch(i){case`vscode`:return[`code`,`--add-mcp`,t(`GitKraken`,a,o)];case`vscode-insiders`:return[`code-insiders`,`--add-mcp`,t(`GitKraken`,a,o)];case`claude-cli`:return[`claude`,`mcp`,`add`,`GitKraken`,`-s`,`user`,`--`,a,...o];case`trae`:return[`trae`,`--add-mcp`,t(``,a,o)];case`codex`:{let{reason:t}=await e(n,r);return t===`legacy-location`?void 0:[`codex`,`mcp`,`add`,`GitKraken`,a,...o]}default:return}}export{n as getMcpCliInstallCommand};
@@ -0,0 +1,96 @@
1
+ import { AgentHostConfig } from "../config.js";
2
+ import { McpLayoutOptions, OpenCodeMcpLayout } from "./opencode-layout.js";
3
+
4
+ //#region src/mcp/clients.d.ts
5
+ /**
6
+ * The 16 MCP client ids gkcli knows about. `gemini` is legacy: retained only
7
+ * so an uninstall can clear a stale GitKraken entry, and excluded from
8
+ * {@link allClients}.
9
+ */
10
+ type Client = 'vscode' | 'vscode-insiders' | 'claude' | 'claude-cli' | 'antigravity' | 'antigravity-cli' | 'windsurf' | 'cursor' | 'zed' | 'trae' | 'copilot' | 'codex' | 'gemini' | 'kiro' | 'jetbrains-copilot' | 'opencode';
11
+ /**
12
+ * How a client runs. Installing into a GUI client may launch its window (VS
13
+ * Code and Cursor are configured through their own binary); callers that must
14
+ * not disturb the desktop select `'cli'` clients.
15
+ */
16
+ type ClientType = 'cli' | 'gui';
17
+ declare const OPEN_CODE_V1_JSON_PATH = "/mcp/GitKraken";
18
+ declare const OPEN_CODE_V2_JSON_PATH = "/mcp/servers/GitKraken";
19
+ /** Every location an OpenCode GitKraken entry may live at: v2 is written, v1 is only recognised and cleaned up. */
20
+ declare const OPEN_CODE_JSON_PATHS: readonly string[];
21
+ /** The OpenCode pointers that can hold a GitKraken entry in `text`; the v2 one only counts under a native `servers` envelope, not a flat server named `servers`. */
22
+ declare function openCodeEntryPointers(text: string): string[];
23
+ declare function isSupported(client: string): boolean;
24
+ /** Whether `client` is retained only for uninstall cleanup (currently only `gemini`). */
25
+ declare function isLegacy(client: Client): boolean;
26
+ /** Safe default for any client-name input: a supported, non-legacy client. */
27
+ declare function isInstallable(client: Client): boolean;
28
+ declare function clientType(client: Client): ClientType | 'unknown';
29
+ declare function clientDisplayName(client: Client): string;
30
+ declare function clientCommand(client: Client): string | undefined;
31
+ /** Every non-legacy client id, in registry order. */
32
+ declare function allClients(): Client[];
33
+ /** Every legacy client id, e.g. `gemini`. */
34
+ declare function legacyClientIds(): Client[];
35
+ /** The `--host=` value the installer bakes into a client's generated args. Rows sharing a host must emit the same value. */
36
+ declare function hostIdentity(client: Client): string;
37
+ /** The client whose configuration surface `client` shares (itself, for every row but the Antigravity pair). */
38
+ declare function hostOf(client: Client): Client;
39
+ /** Every non-legacy row sharing `host`'s configuration surface, `host` itself included, in registry order. */
40
+ declare function clientsForHost(host: Client): Client[];
41
+ /** The JSON pointer OpenCode reads a GitKraken server from in `layout`. */
42
+ declare function openCodeJSONPath(layout: OpenCodeMcpLayout): string;
43
+ /**
44
+ * The JSON pointer (or, for Codex, dotted TOML table key) for a client's GitKraken entry.
45
+ * OpenCode's returned pointer depends on `options.openCode` (v2 when omitted); `infer-existing` throws
46
+ * {@link OpenCodeMcpLayoutUnknownError}, since the pointer then depends on the config file.
47
+ */
48
+ declare function clientJSONPath(client: Client, options?: McpLayoutOptions): string;
49
+ /** The single config file path this client's GitKraken entry is written to. */
50
+ declare function clientConfigPath(client: Client, config: AgentHostConfig): Promise<string | undefined>;
51
+ /**
52
+ * Every config file a client's GitKraken entry may live in: write-and-delete
53
+ * authority. `uninstallMcp` clears the entry from every path.
54
+ */
55
+ declare function clientConfigPaths(client: Client, config: AgentHostConfig): Promise<string[]>;
56
+ /** The pre-shared-config location Antigravity's IDE used to read; uninstall still covers it for back-compat. */
57
+ declare function antigravityLegacyPath(config: AgentHostConfig): string;
58
+ /**
59
+ * The resolved path to the client's binary on `PATH`, or `undefined`. Go's
60
+ * `exec.LookPath` reads `PATH` and stats candidate directories directly; this
61
+ * library never reads the environment, so the host injects the lookup.
62
+ */
63
+ declare function clientExecutablePath(client: Client, config: AgentHostConfig): string | undefined;
64
+ /** How {@link detectClient} found a client. */
65
+ type ClientDetectionSource = 'executable' | 'config-file' | 'config-dir';
66
+ /** Why {@link detectClient} did not find a client. `shared-host-row` is a row such as `antigravity-cli`, which has no config signal of its own and no binary on `PATH`. */
67
+ type ClientNotDetectedReason = 'not-found' | 'shared-host-row';
68
+ interface ClientDetection {
69
+ client: Client;
70
+ detected: boolean;
71
+ /** Set when `detected`. */
72
+ source?: ClientDetectionSource;
73
+ /** The client's binary on `PATH`, when `resolveExecutable` found it. */
74
+ executablePath?: string;
75
+ /** The config file or directory that matched. */
76
+ path?: string;
77
+ /** Set when not `detected`. */
78
+ reason?: ClientNotDetectedReason;
79
+ /** Every config file or directory probed, in order. */
80
+ checkedPaths: string[];
81
+ }
82
+ /**
83
+ * Whether the client's binary or config directory is present, and how it was
84
+ * found. Mirrors Go's `mcp.Client.IsDetected`.
85
+ */
86
+ declare function detectClient(client: Client, config: AgentHostConfig): Promise<ClientDetection>;
87
+ interface ClientInstallation {
88
+ client: Client;
89
+ installed: boolean;
90
+ /** The config files that carry a GitKraken entry, so a host can tell when the entry sits in a legacy file. */
91
+ paths: string[];
92
+ }
93
+ /** Which of the client's config files already carry a GitKraken entry. Mirrors Go's `mcp.Client.IsInstalled`. */
94
+ declare function getClientInstallation(client: Client, config: AgentHostConfig): Promise<ClientInstallation>;
95
+ //#endregion
96
+ export { Client, ClientDetection, ClientDetectionSource, ClientInstallation, ClientNotDetectedReason, ClientType, OPEN_CODE_JSON_PATHS, OPEN_CODE_V1_JSON_PATH, OPEN_CODE_V2_JSON_PATH, allClients, antigravityLegacyPath, clientCommand, clientConfigPath, clientConfigPaths, clientDisplayName, clientExecutablePath, clientJSONPath, clientType, clientsForHost, detectClient, getClientInstallation, hostIdentity, hostOf, isInstallable, isLegacy, isSupported, legacyClientIds, openCodeEntryPointers, openCodeJSONPath };
@@ -0,0 +1 @@
1
+ import{joinPath as e}from"../config.js";import{findAtPointer as t}from"./jsonc-edit.js";import{openCodeLayoutFor as n,openCodeNativeServers as r}from"./opencode-layout.js";import{parseTomlSafe as i,tomlDottedPath as a,tomlLiteralKey as o}from"./toml-read.js";const s=`/mcp/GitKraken`,c=`/mcp/servers/GitKraken`,l=[c,s];function u(e){return l.filter(t=>t!==`/mcp/servers/GitKraken`||r(e)!==void 0)}function d(t,...n){return e(t.paths.home,...n)}function f(e){return e.platform===`linux`?e.paths.configHome:e.paths.appSupport}function p(t){return e(t.paths.configHome,`opencode`)}const m=[{id:`vscode`,displayName:`VS Code`,clientType:`gui`,command:`code`,jsonPath:`/servers/GitKraken`,pathFn:t=>e(f(t),`Code`,`User`,`mcp.json`),legacy:!1},{id:`vscode-insiders`,displayName:`VS Code Insiders`,clientType:`gui`,command:`code-insiders`,jsonPath:`/servers/GitKraken`,pathFn:t=>e(f(t),`Code - Insiders`,`User`,`mcp.json`),legacy:!1},{id:`claude`,displayName:`Claude Desktop`,clientType:`gui`,command:`claude`,jsonPath:`/mcpServers/GitKraken`,pathFn:t=>e(f(t),`Claude`,`claude_desktop_config.json`),legacy:!1},{id:`claude-cli`,displayName:`Claude Code`,clientType:`cli`,command:`claude`,jsonPath:`/mcpServers/GitKraken`,pathFn:t=>t.paths.claudeConfigDir?e(t.paths.claudeConfigDir,`.claude.json`):d(t,`.claude.json`),legacy:!1},{id:`antigravity`,displayName:`Antigravity`,clientType:`gui`,command:`antigravity`,jsonPath:`/mcpServers/GitKraken`,pathFn:e=>d(e,`.gemini`,`config`,`mcp_config.json`),legacy:!1,host:`antigravity`},{id:`antigravity-cli`,displayName:`Antigravity CLI`,clientType:`cli`,command:`agy`,jsonPath:`/mcpServers/GitKraken`,pathFn:e=>d(e,`.gemini`,`config`,`mcp_config.json`),legacy:!1,host:`antigravity`},{id:`windsurf`,displayName:`Windsurf`,clientType:`gui`,command:`windsurf`,jsonPath:`/mcpServers/GitKraken`,pathFn:e=>d(e,`.codeium`,`windsurf`,`mcp_config.json`),legacy:!1},{id:`cursor`,displayName:`Cursor`,clientType:`gui`,command:`cursor`,jsonPath:`/mcpServers/GitKraken`,pathFn:e=>d(e,`.cursor`,`mcp.json`),legacy:!1},{id:`zed`,displayName:`Zed`,clientType:`gui`,command:`zed`,jsonPath:`/context_servers/GitKraken`,pathFn:t=>t.platform===`win32`?e(t.paths.appSupport,`Zed`,`settings.json`):t.platform===`darwin`?d(t,`.config`,`zed`,`settings.json`):e(t.paths.configHome,`zed`,`settings.json`),legacy:!1},{id:`trae`,displayName:`Trae`,clientType:`gui`,command:`trae`,jsonPath:`/mcpServers/GitKraken`,pathFn:t=>e(f(t),`Trae`,`User`,`mcp.json`),legacy:!1},{id:`copilot`,displayName:`GitHub Copilot CLI`,clientType:`cli`,command:`copilot`,jsonPath:`/mcpServers/GitKraken`,pathFn:e=>d(e,`.copilot`,`mcp-config.json`),legacy:!1},{id:`codex`,displayName:`Codex`,clientType:`cli`,command:`codex`,jsonPath:`mcp_servers.GitKraken`,pathFn:e=>d(e,`.codex`,`config.toml`),legacy:!1},{id:`gemini`,displayName:`Gemini CLI`,clientType:`cli`,command:`gemini`,jsonPath:`/mcpServers/GitKraken`,pathFn:e=>d(e,`.gemini`,`settings.json`),legacy:!0},{id:`kiro`,displayName:`Kiro`,clientType:`gui`,command:`kiro`,jsonPath:`/mcpServers/GitKraken`,pathFn:e=>d(e,`.kiro`,`settings`,`mcp.json`),legacy:!1},{id:`jetbrains-copilot`,displayName:`JetBrains Copilot`,clientType:`gui`,jsonPath:`/servers/GitKraken`,pathFn:t=>t.platform===`win32`?e(t.paths.localAppData??e(t.paths.home,`AppData`,`Local`),`github-copilot`,`intellij`,`mcp.json`):e(t.paths.configHome,`github-copilot`,`intellij`,`mcp.json`),legacy:!1},{id:`opencode`,displayName:`OpenCode`,clientType:`cli`,command:`opencode`,jsonPath:c,pathFn:t=>e(p(t),`opencode.json`),legacy:!1}];function h(e){return m.find(t=>t.id===e)}function g(e){return h(e)!==void 0}function _(e){return h(e)?.legacy===!0}function v(e){let t=h(e);return t!==void 0&&!t.legacy}function y(e){return h(e)?.clientType??`unknown`}function b(e){return h(e)?.displayName??e}function x(e){return h(e)?.command}function S(){return m.filter(e=>!e.legacy).map(e=>e.id)}function C(){return m.filter(e=>e.legacy).map(e=>e.id)}function w(e){return(h(e)?.host??e).toLowerCase()}function T(e){return h(e)?.host??e}function E(e){return m.filter(t=>!t.legacy&&(t.host??t.id)===e).map(e=>e.id)}function D(e){return e===`v1`?s:c}function O(e,t){return e===`opencode`?D(n(t)):h(e)?.jsonPath??``}async function k(t){let n=p(t),r=e(n,`opencode.jsonc`);return await t.fs.readFile(r)===null?e(n,`opencode.json`):r}async function A(e,t){let n=h(e);if(n)return e===`opencode`?k(t):n.pathFn(t)}async function j(e,t){let n=await A(e,t),r=n?[n]:[];return e===`antigravity`&&r.push(d(t,`.gemini`,`antigravity`,`mcp_config.json`)),r}function M(e){return d(e,`.gemini`,`antigravity`,`mcp_config.json`)}function N(e,t){let n=x(e);if(!(!n||!t.resolveExecutable))return t.resolveExecutable(n)}function P(e,t){let n=h(e);return n?.host?E(n.host).some(n=>n!==e&&N(n,t)!==void 0):!1}async function F(e,t){if(!t)return;let n=t.lastIndexOf(`/`),r=n>0?t.slice(0,n):``;if(r)return r===e.paths.home.replace(/\/+$/,``)?{matched:await e.fs.readFile(t)!==null,source:`config-file`,path:t}:{matched:await e.fs.readDir(r)!==null,source:`config-dir`,path:r}}async function I(e,t,n,r){for(let i of t){let t=await F(n,i);if(t&&(r.push(t.path),t.matched))return{client:e,detected:!0,source:t.source,path:t.path,checkedPaths:r}}return{client:e,detected:!1,reason:`not-found`,checkedPaths:r}}async function L(e,t){let n=N(e,t),r=[];if(n!==void 0)return{client:e,detected:!0,source:`executable`,executablePath:n,checkedPaths:r};let i=h(e);if(i?.host&&i.host!==e)return{client:e,detected:!1,reason:`shared-host-row`,checkedPaths:r};if(e===`antigravity`){let n=i?.pathFn(t);return n&&!P(e,t)&&(r.push(n),await t.fs.readFile(n)!==null)?{client:e,detected:!0,source:`config-file`,path:n,checkedPaths:r}:I(e,[M(t)],t,r)}return I(e,await j(e,t),t,r)}async function R(e,t){let n=[];for(let r of await j(e,t))await z(e,r,t)&&n.push(r);return{client:e,installed:n.length>0,paths:n}}async function z(e,n,r){let s=await r.fs.readFile(n);if(s===null||s.trim()===``)return!1;if(e===`codex`){let e=i(s);if(!e)return!1;let t=O(`codex`);return o(e,t)!==void 0||a(e,t)!==void 0}return(e===`opencode`?u(s):[O(e)]).some(e=>t(s,e)!==void 0)}export{l as OPEN_CODE_JSON_PATHS,s as OPEN_CODE_V1_JSON_PATH,c as OPEN_CODE_V2_JSON_PATH,S as allClients,M as antigravityLegacyPath,x as clientCommand,A as clientConfigPath,j as clientConfigPaths,b as clientDisplayName,N as clientExecutablePath,O as clientJSONPath,y as clientType,E as clientsForHost,L as detectClient,R as getClientInstallation,w as hostIdentity,T as hostOf,v as isInstallable,_ as isLegacy,g as isSupported,C as legacyClientIds,u as openCodeEntryPointers,D as openCodeJSONPath};
@@ -0,0 +1,15 @@
1
+ import { McpLayoutOptions } from "./opencode-layout.js";
2
+ import { Client } from "./clients.js";
3
+
4
+ //#region src/mcp/config-entry.d.ts
5
+ /** The shape of an entry `installMcp` writes: keys vary per client family (see {@link generateConfigEntry}). */
6
+ type McpConfigEntry = Record<string, unknown>;
7
+ /**
8
+ * Builds the entry a client's GitKraken MCP server config carries. Port of
9
+ * gkcli's `installer.generateConfigs`/`installConfig`: four shapes dispatched
10
+ * by client family. OpenCode gets its v2 shape unless `options.openCode` selects v1;
11
+ * `infer-existing` throws {@link OpenCodeMcpLayoutUnknownError} because choosing needs the config file.
12
+ */
13
+ declare function generateConfigEntry(client: Client, executablePath: string, args: readonly string[], options?: McpLayoutOptions): McpConfigEntry;
14
+ //#endregion
15
+ export { McpConfigEntry, generateConfigEntry };
@@ -0,0 +1 @@
1
+ import{openCodeLayoutFor as e}from"./opencode-layout.js";function t(t,n,r,i){switch(t){case`zed`:return{source:`custom`,enabled:!0,command:n,args:[...r]};case`copilot`:return{type:`local`,command:n,tools:[`*`],args:[...r]};case`opencode`:return e(i)===`v1`?{type:`local`,command:[n,...r],enabled:!0}:{type:`local`,command:[n,...r],disabled:!1};default:return{type:`stdio`,command:n,args:[...r]}}}export{t as generateConfigEntry};
@@ -0,0 +1,7 @@
1
+ import { McpConfigEntry, generateConfigEntry } from "./config-entry.js";
2
+ import { McpInstallRequest, McpInstallResult, McpInstallState, McpInstallStateReason, McpInstallStatus, inspectMcpInstall, installMcp } from "./installer.js";
3
+ import { McpLayoutOptions, OpenCodeMcpCleanupError, OpenCodeMcpLayout, OpenCodeMcpLayoutUnknownError, OpenCodeMcpPolicy } from "./opencode-layout.js";
4
+ 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 "./clients.js";
5
+ import { getMcpCliInstallCommand } from "./cli-install-command.js";
6
+ import { McpUninstallFileResult, McpUninstallRequest, McpUninstallResult, uninstallMcp } from "./uninstaller.js";
7
+ export { type Client, type ClientDetection, type ClientDetectionSource, type ClientInstallation, type ClientNotDetectedReason, type ClientType, 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, allClients, antigravityLegacyPath, clientCommand, clientConfigPath, clientConfigPaths, clientDisplayName, clientExecutablePath, clientJSONPath, clientType, clientsForHost, detectClient, generateConfigEntry, getClientInstallation, getMcpCliInstallCommand, hostIdentity, hostOf, inspectMcpInstall, installMcp, isInstallable, isLegacy, isSupported, legacyClientIds, uninstallMcp };
@@ -0,0 +1 @@
1
+ import{OpenCodeMcpCleanupError as e,OpenCodeMcpLayoutUnknownError as t}from"./opencode-layout.js";import{allClients as n,antigravityLegacyPath as r,clientCommand as i,clientConfigPath as a,clientConfigPaths as o,clientDisplayName as s,clientExecutablePath as c,clientJSONPath as l,clientType as u,clientsForHost as d,detectClient as f,getClientInstallation as p,hostIdentity as m,hostOf as h,isInstallable as g,isLegacy as _,isSupported as v,legacyClientIds as y}from"./clients.js";import{generateConfigEntry as b}from"./config-entry.js";import{inspectMcpInstall as x,installMcp as S}from"./installer.js";import{getMcpCliInstallCommand as C}from"./cli-install-command.js";import{uninstallMcp as w}from"./uninstaller.js";export{e as OpenCodeMcpCleanupError,t as OpenCodeMcpLayoutUnknownError,n as allClients,r as antigravityLegacyPath,i as clientCommand,a as clientConfigPath,o as clientConfigPaths,s as clientDisplayName,c as clientExecutablePath,l as clientJSONPath,u as clientType,d as clientsForHost,f as detectClient,b as generateConfigEntry,p as getClientInstallation,C as getMcpCliInstallCommand,m as hostIdentity,h as hostOf,x as inspectMcpInstall,S as installMcp,g as isInstallable,_ as isLegacy,v as isSupported,y as legacyClientIds,w as uninstallMcp};