@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.
- package/CHANGELOG.md +39 -0
- package/LICENSE +19 -0
- package/README.md +163 -2
- package/dist/agents.d.ts +52 -0
- package/dist/agents.js +1 -0
- package/dist/config.d.ts +69 -0
- package/dist/config.js +1 -0
- package/dist/file-change.d.ts +28 -0
- package/dist/file-change.js +1 -0
- package/dist/hooks/claude.d.ts +16 -0
- package/dist/hooks/claude.js +3 -0
- package/dist/hooks/errors.d.ts +10 -0
- package/dist/hooks/errors.js +1 -0
- package/dist/hooks/events.d.ts +73 -0
- package/dist/hooks/events.js +1 -0
- package/dist/hooks/install.d.ts +15 -0
- package/dist/hooks/install.js +1 -0
- package/dist/hooks/results.d.ts +30 -0
- package/dist/hooks/results.js +1 -0
- package/dist/hooks/templates.d.ts +15 -0
- package/dist/hooks/templates.js +278 -0
- package/dist/hooks/writers.d.ts +18 -0
- package/dist/hooks/writers.js +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +1 -0
- package/dist/mcp/cli-install-command.d.ts +27 -0
- package/dist/mcp/cli-install-command.js +1 -0
- package/dist/mcp/clients.d.ts +96 -0
- package/dist/mcp/clients.js +1 -0
- package/dist/mcp/config-entry.d.ts +15 -0
- package/dist/mcp/config-entry.js +1 -0
- package/dist/mcp/index.d.ts +7 -0
- package/dist/mcp/index.js +1 -0
- package/dist/mcp/installer.d.ts +130 -0
- package/dist/mcp/installer.js +3 -0
- package/dist/mcp/jsonc-edit.d.ts +43 -0
- package/dist/mcp/jsonc-edit.js +3 -0
- package/dist/mcp/opencode-layout.d.ts +54 -0
- package/dist/mcp/opencode-layout.js +1 -0
- package/dist/mcp/toml-read.d.ts +11 -0
- package/dist/mcp/toml-read.js +1 -0
- package/dist/mcp/toml-safety.d.ts +7 -0
- package/dist/mcp/toml-safety.js +1 -0
- package/dist/mcp/toml-splice.d.ts +16 -0
- package/dist/mcp/toml-splice.js +11 -0
- package/dist/mcp/uninstaller.d.ts +41 -0
- package/dist/mcp/uninstaller.js +1 -0
- package/dist/skills/index.d.ts +4 -0
- package/dist/skills/index.js +1 -0
- package/dist/skills/install.d.ts +126 -0
- package/dist/skills/install.js +1 -0
- package/dist/skills/manifest.d.ts +56 -0
- package/dist/skills/manifest.js +1 -0
- package/dist/skills/skill-dirs.d.ts +22 -0
- package/dist/skills/skill-dirs.js +1 -0
- package/dist/testing/index.d.ts +2 -0
- package/dist/testing/index.js +1 -0
- package/dist/testing/memory-fs.d.ts +22 -0
- package/dist/testing/memory-fs.js +1 -0
- 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};
|
package/dist/index.d.ts
ADDED
|
@@ -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};
|