@gitkraken/agent-tools 0.1.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,19 @@
1
+ import { HookHost } from "./events.js";
2
+
3
+ //#region src/hooks/validate.d.ts
4
+ /** Throws `invalid-event` for the first event the host's manifest does not list, with gkcli's wording. */
5
+ declare function validateHookEvents(host: HookHost, events: readonly string[]): void;
6
+ /** Throws `blocking-unsupported` when the host cannot honor a decision (`noBlocking` in the registry) and `blockingEvents` is not empty. */
7
+ declare function assertBlockingSupported(host: HookHost, blockingEvents: readonly string[] | undefined, path?: string): void;
8
+ /** Resolves the events to write: the explicit ones, or the manifest's minus `skippedDefaultEvents` when none are requested. */
9
+ declare function selectHookEvents(request: {
10
+ host: HookHost;
11
+ events?: readonly string[];
12
+ blockingEvents?: readonly string[];
13
+ }): {
14
+ events: string[];
15
+ blockingEvents: string[];
16
+ isDefault: boolean;
17
+ };
18
+ //#endregion
19
+ export { assertBlockingSupported, selectHookEvents, validateHookEvents };
@@ -0,0 +1 @@
1
+ import{HookConfigError as e}from"./errors.js";import{eventManifest as t}from"./events.js";import{hookClientInfo as n}from"./registry.js";function r(n,r){let i=t[n].events;for(let t of r)if(!i.includes(t))throw new e(`invalid-event`,`'${t}' is not a valid event for ${n}. Valid events: ${i.join(`, `)}`)}function i(t,r,i){if(r?.length&&n(t).noBlocking)throw new e(`blocking-unsupported`,`${t} does not support blocking hooks`,i)}function a(e){let n=t[e.host],i=!e.events?.length&&!e.blockingEvents?.length,a=i?n.events.filter(e=>!n.skippedDefaultEvents.includes(e)):[...e.events??[]],o=[...e.blockingEvents??[]];return r(e.host,[...a,...o]),{events:a,blockingEvents:o,isDefault:i}}export{i as assertBlockingSupported,a as selectHookEvents,r as validateHookEvents};
@@ -1,6 +1,6 @@
1
1
  import { AgentHostConfig } from "../config.js";
2
2
  import { HookHost } from "./events.js";
3
- import { HookInstallResult, HookUninstallResult } from "./results.js";
3
+ import { HookInspection, HookInstallResult, HookUninstallResult } from "./results.js";
4
4
 
5
5
  //#region src/hooks/writers.d.ts
6
6
  type JsonHookHost = Extract<HookHost, 'cursor' | 'codex' | 'copilot' | 'antigravity' | 'augment' | 'grok'>;
@@ -10,9 +10,14 @@ interface HookWriteRequest {
10
10
  events?: readonly string[];
11
11
  blockingEvents?: readonly string[];
12
12
  }
13
- declare function command(executablePath: string, host: HookHost, config: AgentHostConfig, blocking: boolean): string;
13
+ declare const BLOCKING_SUFFIX = " --blocking";
14
+ declare const HOOK_RUN_MARKER = " ai hook run --host ";
15
+ /** `event` is appended as `--event` for hosts whose payload omits the event name; a blocking entry ends with `--blocking`. */
16
+ declare function command(executablePath: string, host: HookHost, config: AgentHostConfig, blocking: boolean, event?: string): string;
14
17
  /** Merges only GitKraken-owned hooks, preserving unrelated entries and document keys. */
15
18
  declare function installJsonHooks(request: HookWriteRequest, config: AgentHostConfig): Promise<HookInstallResult>;
16
19
  declare function uninstallJsonHooks(host: JsonHookHost, config: AgentHostConfig, events?: readonly string[]): Promise<HookUninstallResult>;
20
+ /** Read-only: reports the GitKraken events in `host`'s hooks file without ever writing it. */
21
+ declare function inspectJsonHooks(host: HookHost, config: AgentHostConfig): Promise<HookInspection>;
17
22
  //#endregion
18
- export { HookWriteRequest, command, installJsonHooks, uninstallJsonHooks };
23
+ export { BLOCKING_SUFFIX, HOOK_RUN_MARKER, HookWriteRequest, command, inspectJsonHooks, installJsonHooks, uninstallJsonHooks };
@@ -1 +1 @@
1
- import{joinPath as e}from"../config.js";import{removeTracked as t,writeFileTracked as n}from"../file-change.js";import{HookConfigError as r}from"./errors.js";import{eventManifest as 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};
1
+ import{joinPath as e}from"../config.js";import{removeTracked as t,writeFileTracked as n}from"../file-change.js";import{HookConfigError as r,hookErrorCode as i}from"./errors.js";import{eventManifest as a}from"./events.js";import{hookClientInfo as o}from"./registry.js";import{assertBlockingSupported as s,selectHookEvents as c}from"./validate.js";const l={cursor:{path:t=>e(t.paths.home,`.cursor`,`hooks.json`),key:`hooks`,version:!0,matcher:!1},codex:{path:t=>e(t.paths.home,`.codex`,`hooks.json`),key:`hooks`,version:!1,matcher:!0},copilot:{path:t=>e(t.paths.home,`.copilot`,`hooks`,`gitkraken-hooks.json`),key:`hooks`,version:!0,matcher:!1},antigravity:{path:t=>e(t.paths.home,`.gemini`,`config`,`hooks.json`),key:`gitkraken`,version:!1,matcher:!1},augment:{path:t=>e(t.paths.home,`.augment`,`settings.json`),key:`hooks`,version:!1,matcher:!0,neverDelete:!0},grok:{path:t=>e(t.paths.grokHome??e(t.paths.home,`.grok`),`hooks`,`gitkraken-hooks.json`),key:`hooks`,version:!1,matcher:!0}},u=` --blocking`,d=` ai hook run --host `;function f(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function p(e){return typeof e==`string`&&e.includes(` ai hook run --host `)}function m(e,t,n,r,i){let s=a[t],c=n.platform===`win32`&&`windowsShell`in s&&s.windowsShell===`powershell`?`& `:``,l=i&&`eventFlag`in s&&s.eventFlag?` --event ${i}`:``,f=o(t).blockingWait,p=r?(f?` --timeout ${f}`:``)+u:``;return`${c}"${e.replaceAll(`\\`,`/`)}"${d}${t}${l}${p}`}function h(e,t){return e===`antigravity`?t===`PreToolUse`||t===`PostToolUse`:l[e].matcher}function g(e,t,n){if(!Array.isArray(e)||!e.every(f))throw new r(`wrong-shape`,`${n} has the wrong event shape`,n);if(t&&e.some(e=>`command`in e||`hooks`in e&&(!Array.isArray(e.hooks)||!e.hooks.every(f))))throw new r(`wrong-shape`,`${n} has the wrong matcher shape`,n);if(!t&&e.some(e=>`hooks`in e))throw new r(`wrong-shape`,`${n} has the wrong flat shape`,n)}function _(e,t){return Array.isArray(e)?e.flatMap(e=>t&&f(e)&&Array.isArray(e.hooks)?e.hooks.map(e=>f(e)?e.command:void 0):[f(e)?e.command:void 0]).filter(e=>p(e)):[]}function v(e,t){return Object.keys(t).filter(n=>_(t[n],h(e,n)).length>0)}function y(e,t){return t?e.flatMap(e=>{if(!Array.isArray(e.hooks))return[e];let t=e.hooks.filter(e=>!f(e)||!p(e.command));return t.length===e.hooks.length?[e]:t.length?[{...e,hooks:t}]:[]}):e.filter(e=>!p(e.command))}function b(e,t,n){return n?86400*(`blockingTimeoutMultiplier`in a[e]?Number(a[e].blockingTimeoutMultiplier):1):e===`codex`&&t===`SessionEnd`?3:void 0}function x(e,t,n,r,i){let a={type:`command`,command:r,...i?{timeout:i}:{}};if(!t)return[...y(e,!1),a];let o=y(e,!0),s=o.find(e=>!e.matcher&&Array.isArray(e.hooks)&&e.hooks.length>0);s||(s=n===`augment`?{hooks:[]}:{matcher:``,hooks:[]},o.push(s));let c=Array.isArray(s.hooks)?s.hooks:[];return s.hooks=[...c.filter(e=>!p(e.command)),a],o}function S(e,t){let n;try{n=JSON.parse(e.replace(/^\uFEFF/,``))}catch{throw new r(`invalid-json`,`Invalid JSON at ${t}`,t)}if(!f(n))throw new r(`wrong-shape`,`Invalid hooks document at ${t}`,t);return n}function C(e,t,n){let i=e[t];if(i!=null){if(!f(i))throw new r(`wrong-shape`,`Invalid hooks wrapper at ${n}`,n);return i}}async function w(e,t){if(!Object.hasOwn(l,e.host))throw new r(`unsupported-host`,`No JSON writer for ${e.host}`);let i=e.host,u=l[i],d=u.path(t),{events:f,blockingEvents:p,isDefault:_}=c(e);s(i,p,d);let v=await t.fs.readFile(d),w=v?.trim()?S(v,d):{},T={...C(w,u.key,d)};for(let[e,t]of Object.entries(T))g(t,h(i,e),d);for(let[n,r]of[[f,!1],[p,!0]])for(let a of n){let n=h(i,a);T[a]=x(T[a]??[],n,i,m(e.executablePath,i,t,r,a),b(i,a,r))}if(`legacyEvents`in a[i])for(let e of a[i].legacyEvents){let t=T[e];if(t){let n=y(t,h(i,e));n.length?T[e]=n:delete T[e]}}if(_)for(let e of o(i).unsafeEvents){let t=T[e];if(!t)continue;let n=y(t,h(i,e));n.length?T[e]=n:delete T[e]}Object.keys(T).length?w[u.key]=T:delete w[u.key],u.version&&w.version==null&&(w.version=1);let E=[];return await n(t.fs,d,`${JSON.stringify(w,null,2)}\n`,E),{host:i,path:d,events:[...f],blockingEvents:[...p],files:E}}async function T(e,r,i){let o=l[e],s=o.path(r),c=[],u=(t,n=[])=>({host:e,path:s,removedEvents:[],remainingEvents:n,files:c,reason:t}),d=await r.fs.readFile(s);if(d===null)return u(`file-missing`);if(!d.trim())return u(`not-installed`);let f=S(d,s),p=C(f,o.key,s);if(!p)return u(`not-installed`);let m={...p};for(let[t,n]of Object.entries(m))g(n,h(e,t),s);let _=[],b=i?.length?i:a[e].events;for(let t of[...b,...`legacyEvents`in a[e]?a[e].legacyEvents:[]]){let n=m[t];if(!n)continue;let r=y(n,h(e,t));JSON.stringify(r)!==JSON.stringify(n)&&(_.push(t),r.length?m[t]=r:delete m[t])}let x=v(e,m);if(!_.length)return u(`not-installed`,x);Object.keys(m).length?f[o.key]=m:delete f[o.key];let w=Object.keys(f).some(e=>e!==o.key&&!(e===`version`&&o.version&&f.version===1));return!Object.keys(m).length&&!w&&!o.neverDelete?await t(r.fs,s,c):await n(r.fs,s,`${JSON.stringify(f,null,2)}\n`,c),{host:e,path:s,removedEvents:_,remainingEvents:x,files:c}}async function E(e,t){let n=e,r=l[n],o=r.path(t),s={host:e,installed:!1,usable:!1,path:o,layout:`none`,events:[],blockingEvents:[],needsMigration:!1};try{let i=await t.fs.readFile(o);if(!i?.trim())return s;let c=C(S(i,o),r.key,o)??{};for(let[e,t]of Object.entries(c))g(t,h(n,e),o);let l=v(n,c),d=`legacyEvents`in a[e]?a[e].legacyEvents:[];return{...s,installed:l.length>0,usable:l.length>0,layout:l.length?`file`:`none`,events:l,blockingEvents:l.filter(e=>_(c[e],h(n,e)).some(e=>e.endsWith(u))),needsMigration:l.some(e=>d.includes(e))}}catch(e){return{...s,error:i(e)}}}export{u as BLOCKING_SUFFIX,d as HOOK_RUN_MARKER,m as command,E as inspectJsonHooks,w as installJsonHooks,T as uninstallJsonHooks};
package/dist/index.d.ts CHANGED
@@ -1,17 +1,21 @@
1
1
  import { AgentHostConfig, AgentHostPaths, AgentOrigin, joinPath } from "./config.js";
2
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";
3
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";
4
- import { Agent, HookCapability, listAgents, mcpNamesForHook, resolveHookClient } from "./agents.js";
7
+ import { Agent, HookCapability, ListAgentsOptions, hookCapabilities, listAgents, mcpNamesForHook, resolveHookClient } from "./agents.js";
5
8
  import { FileChange, FileChangeKind } from "./file-change.js";
6
- import { HookInstallResult, HookUninstallResult } from "./hooks/results.js";
7
9
  import { HookConfigError, HookConfigErrorCode } from "./hooks/errors.js";
10
+ import { HookInspection, HookInstallResult, HookLayout, HookUninstallResult } from "./hooks/results.js";
11
+ import { inspectHooks } from "./hooks/inspect.js";
8
12
  import { HookWriteRequest, installJsonHooks, uninstallJsonHooks } from "./hooks/writers.js";
9
13
  import { InstallHooksRequest, installHooks, uninstallHooks } from "./hooks/install.js";
10
- import { McpConfigEntry, generateConfigEntry } from "./mcp/config-entry.js";
11
- import { McpInstallRequest, McpInstallResult, McpInstallState, McpInstallStateReason, McpInstallStatus, inspectMcpInstall, installMcp } from "./mcp/installer.js";
14
+ import { HookClientInfo, executableForHook, hookClientInfo, hookHosts } from "./hooks/registry.js";
15
+ import { validateHookEvents } from "./hooks/validate.js";
12
16
  import { getMcpCliInstallCommand } from "./mcp/cli-install-command.js";
13
17
  import { McpUninstallFileResult, McpUninstallRequest, McpUninstallResult, uninstallMcp } from "./mcp/uninstaller.js";
14
18
  import { SKILL_MANIFEST_FILE_NAME, SKILL_MANIFEST_SCHEMA_VERSION, SkillManifest } from "./skills/manifest.js";
15
19
  import { SkillAgent, allSkillAgents, isSkillAgentSupported, skillDir } from "./skills/skill-dirs.js";
16
20
  import { SkillConflictError, SkillFiles, SkillInstallAction, SkillInstallOptions, SkillInstallResult, SkillOperation, SkillReason, SkillState, SkillStatus, SkillUninstallAction, SkillUninstallOptions, SkillUninstallResult, SkillUnsupportedManifestError, inspectSkillStatus, installSkill, uninstallSkill } from "./skills/install.js";
17
- 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 McpUninstallFileResult, type McpUninstallRequest, type McpUninstallResult, SKILL_MANIFEST_FILE_NAME, SKILL_MANIFEST_SCHEMA_VERSION, type SkillAgent, SkillConflictError, type SkillFiles, type SkillInstallAction, type SkillInstallOptions, type SkillInstallResult, type SkillManifest, type SkillOperation, type SkillReason, type SkillState, type SkillStatus, type SkillUninstallAction, type SkillUninstallOptions, type SkillUninstallResult, SkillUnsupportedManifestError, allClients, allSkillAgents, antigravityLegacyPath, clientCommand, clientConfigPath, clientConfigPaths, clientDisplayName, clientExecutablePath, clientJSONPath, clientType, clientsForHost, detectClient, eventManifest, generateConfigEntry, getClientInstallation, getMcpCliInstallCommand, hostIdentity, hostOf, inspectMcpInstall, inspectSkillStatus, installHooks, installJsonHooks, installMcp, installSkill, isInstallable, isLegacy, isSkillAgentSupported, isSupported, joinPath, legacyClientIds, listAgents, mcpNamesForHook, resolveHookClient, skillDir, uninstallHooks, uninstallJsonHooks, uninstallMcp, uninstallSkill };
21
+ export { type Agent, type AgentHostConfig, type AgentHostPaths, type AgentOrigin, type Client, type ClientDetection, type ClientDetectionSource, type ClientInstallation, type ClientNotDetectedReason, type ClientType, type FileChange, type FileChangeKind, type HookCapability, type HookClientInfo, HookConfigError, type HookConfigErrorCode, type HookHost, type HookInspection, type HookInstallResult, type HookLayout, type HookUninstallResult, type HookWriteRequest, type InstallHooksRequest, type ListAgentsOptions, type McpConfigEntry, type McpInstallRequest, type McpInstallResult, type McpInstallState, type McpInstallStateReason, type McpInstallStatus, type McpLayoutOptions, type McpUninstallFileResult, type McpUninstallRequest, type McpUninstallResult, OpenCodeMcpCleanupError, type OpenCodeMcpLayout, OpenCodeMcpLayoutUnknownError, type OpenCodeMcpPolicy, SKILL_MANIFEST_FILE_NAME, SKILL_MANIFEST_SCHEMA_VERSION, type SkillAgent, SkillConflictError, type SkillFiles, type SkillInstallAction, type SkillInstallOptions, type SkillInstallResult, type SkillManifest, type SkillOperation, type SkillReason, type SkillState, type SkillStatus, type SkillUninstallAction, type SkillUninstallOptions, type SkillUninstallResult, SkillUnsupportedManifestError, allClients, allSkillAgents, antigravityLegacyPath, clientCommand, clientConfigPath, clientConfigPaths, clientDisplayName, clientExecutablePath, clientJSONPath, clientType, clientsForHost, detectClient, eventManifest, executableForHook, generateConfigEntry, getClientInstallation, getMcpCliInstallCommand, hookCapabilities, hookClientInfo, hookHosts, hostIdentity, hostOf, inspectHooks, inspectMcpInstall, inspectSkillStatus, installHooks, installJsonHooks, installMcp, installSkill, isInstallable, isLegacy, isSkillAgentSupported, isSupported, joinPath, legacyClientIds, listAgents, mcpNamesForHook, resolveHookClient, skillDir, uninstallHooks, uninstallJsonHooks, uninstallMcp, uninstallSkill, validateHookEvents };
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{joinPath as e}from"./config.js";import{allClients as t,antigravityLegacyPath as n,clientCommand as r,clientConfigPath as i,clientConfigPaths as a,clientDisplayName as o,clientExecutablePath as s,clientJSONPath as c,clientType as l,clientsForHost as u,detectClient as d,getClientInstallation as f,hostIdentity as p,hostOf as m,isInstallable as h,isLegacy as g,isSupported as _,legacyClientIds as v}from"./mcp/clients.js";import{listAgents as y,mcpNamesForHook as b,resolveHookClient as x}from"./agents.js";import{HookConfigError as S}from"./hooks/errors.js";import{eventManifest as C}from"./hooks/events.js";import{installJsonHooks as w,uninstallJsonHooks as T}from"./hooks/writers.js";import{installHooks as E,uninstallHooks as D}from"./hooks/install.js";import{generateConfigEntry as O}from"./mcp/config-entry.js";import{inspectMcpInstall as k,installMcp as A}from"./mcp/installer.js";import{getMcpCliInstallCommand as j}from"./mcp/cli-install-command.js";import{uninstallMcp as M}from"./mcp/uninstaller.js";import{SKILL_MANIFEST_FILE_NAME as N,SKILL_MANIFEST_SCHEMA_VERSION as P}from"./skills/manifest.js";import{allSkillAgents as F,isSkillAgentSupported as I,skillDir as L}from"./skills/skill-dirs.js";import{SkillConflictError as R,SkillUnsupportedManifestError as z,inspectSkillStatus as B,installSkill as V,uninstallSkill as H}from"./skills/install.js";export{S as HookConfigError,N as SKILL_MANIFEST_FILE_NAME,P as SKILL_MANIFEST_SCHEMA_VERSION,R as SkillConflictError,z as SkillUnsupportedManifestError,t as allClients,F as allSkillAgents,n as antigravityLegacyPath,r as clientCommand,i as clientConfigPath,a as clientConfigPaths,o as clientDisplayName,s as clientExecutablePath,c as clientJSONPath,l as clientType,u as clientsForHost,d as detectClient,C as eventManifest,O as generateConfigEntry,f as getClientInstallation,j as getMcpCliInstallCommand,p as hostIdentity,m as hostOf,k as inspectMcpInstall,B as inspectSkillStatus,E as installHooks,w as installJsonHooks,A as installMcp,V as installSkill,h as isInstallable,g as isLegacy,I as isSkillAgentSupported,_ as isSupported,e as joinPath,v as legacyClientIds,y as listAgents,b as mcpNamesForHook,x as resolveHookClient,L as skillDir,D as uninstallHooks,T as uninstallJsonHooks,M as uninstallMcp,H as uninstallSkill};
1
+ import{joinPath as e}from"./config.js";import{HookConfigError as t}from"./hooks/errors.js";import{eventManifest as n}from"./hooks/events.js";import{executableForHook as r,hookClientInfo as i,hookHosts as a}from"./hooks/registry.js";import{validateHookEvents as o}from"./hooks/validate.js";import{installJsonHooks as s,uninstallJsonHooks as c}from"./hooks/writers.js";import{inspectHooks as l}from"./hooks/inspect.js";import{OpenCodeMcpCleanupError as u,OpenCodeMcpLayoutUnknownError as d}from"./mcp/opencode-layout.js";import{allClients as f,antigravityLegacyPath as p,clientCommand as m,clientConfigPath as h,clientConfigPaths as g,clientDisplayName as _,clientExecutablePath as v,clientJSONPath as y,clientType as b,clientsForHost as x,detectClient as S,getClientInstallation as C,hostIdentity as w,hostOf as T,isInstallable as E,isLegacy as D,isSupported as O,legacyClientIds as k}from"./mcp/clients.js";import{hookCapabilities as A,listAgents as j,mcpNamesForHook as M,resolveHookClient as N}from"./agents.js";import{installHooks as P,uninstallHooks as F}from"./hooks/install.js";import{generateConfigEntry as I}from"./mcp/config-entry.js";import{inspectMcpInstall as L,installMcp as R}from"./mcp/installer.js";import{getMcpCliInstallCommand as z}from"./mcp/cli-install-command.js";import{uninstallMcp as B}from"./mcp/uninstaller.js";import{SKILL_MANIFEST_FILE_NAME as V,SKILL_MANIFEST_SCHEMA_VERSION as H}from"./skills/manifest.js";import{allSkillAgents as U,isSkillAgentSupported as W,skillDir as G}from"./skills/skill-dirs.js";import{SkillConflictError as K,SkillUnsupportedManifestError as q,inspectSkillStatus as J,installSkill as Y,uninstallSkill as X}from"./skills/install.js";export{t as HookConfigError,u as OpenCodeMcpCleanupError,d as OpenCodeMcpLayoutUnknownError,V as SKILL_MANIFEST_FILE_NAME,H as SKILL_MANIFEST_SCHEMA_VERSION,K as SkillConflictError,q as SkillUnsupportedManifestError,f as allClients,U as allSkillAgents,p as antigravityLegacyPath,m as clientCommand,h as clientConfigPath,g as clientConfigPaths,_ as clientDisplayName,v as clientExecutablePath,y as clientJSONPath,b as clientType,x as clientsForHost,S as detectClient,n as eventManifest,r as executableForHook,I as generateConfigEntry,C as getClientInstallation,z as getMcpCliInstallCommand,A as hookCapabilities,i as hookClientInfo,a as hookHosts,w as hostIdentity,T as hostOf,l as inspectHooks,L as inspectMcpInstall,J as inspectSkillStatus,P as installHooks,s as installJsonHooks,R as installMcp,Y as installSkill,E as isInstallable,D as isLegacy,W as isSkillAgentSupported,O as isSupported,e as joinPath,k as legacyClientIds,j as listAgents,M as mcpNamesForHook,N as resolveHookClient,G as skillDir,F as uninstallHooks,c as uninstallJsonHooks,B as uninstallMcp,X as uninstallSkill,o as validateHookEvents};
@@ -1,4 +1,5 @@
1
1
  import { AgentHostConfig } from "../config.js";
2
+ import { McpLayoutOptions, OpenCodeMcpLayout } from "./opencode-layout.js";
2
3
 
3
4
  //#region src/mcp/clients.d.ts
4
5
  /**
@@ -17,6 +18,8 @@ declare const OPEN_CODE_V1_JSON_PATH = "/mcp/GitKraken";
17
18
  declare const OPEN_CODE_V2_JSON_PATH = "/mcp/servers/GitKraken";
18
19
  /** Every location an OpenCode GitKraken entry may live at: v2 is written, v1 is only recognised and cleaned up. */
19
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[];
20
23
  declare function isSupported(client: string): boolean;
21
24
  /** Whether `client` is retained only for uninstall cleanup (currently only `gemini`). */
22
25
  declare function isLegacy(client: Client): boolean;
@@ -35,8 +38,14 @@ declare function hostIdentity(client: Client): string;
35
38
  declare function hostOf(client: Client): Client;
36
39
  /** Every non-legacy row sharing `host`'s configuration surface, `host` itself included, in registry order. */
37
40
  declare function clientsForHost(host: Client): Client[];
38
- /** The JSON pointer (or, for Codex, dotted TOML table key) `installMcp` writes a client's GitKraken entry at. */
39
- declare function clientJSONPath(client: Client): string;
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;
40
49
  /** The single config file path this client's GitKraken entry is written to. */
41
50
  declare function clientConfigPath(client: Client, config: AgentHostConfig): Promise<string | undefined>;
42
51
  /**
@@ -84,4 +93,4 @@ interface ClientInstallation {
84
93
  /** Which of the client's config files already carry a GitKraken entry. Mirrors Go's `mcp.Client.IsInstalled`. */
85
94
  declare function getClientInstallation(client: Client, config: AgentHostConfig): Promise<ClientInstallation>;
86
95
  //#endregion
87
- 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 };
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 };
@@ -1 +1 @@
1
- import{joinPath as e}from"../config.js";import{findAtPointer as t}from"./jsonc-edit.js";import{parseTomlSafe as n,tomlDottedPath as r,tomlLiteralKey as i}from"./toml-read.js";const a=`/mcp/GitKraken`,o=`/mcp/servers/GitKraken`,s=[o,a];function c(t,...n){return e(t.paths.home,...n)}function l(e){return e.platform===`linux`?e.paths.configHome:e.paths.appSupport}function u(t){return e(t.paths.configHome,`opencode`)}const d=[{id:`vscode`,displayName:`VS Code`,clientType:`gui`,command:`code`,jsonPath:`/servers/GitKraken`,pathFn:t=>e(l(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(l(t),`Code - Insiders`,`User`,`mcp.json`),legacy:!1},{id:`claude`,displayName:`Claude Desktop`,clientType:`gui`,command:`claude`,jsonPath:`/mcpServers/GitKraken`,pathFn:t=>e(l(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`):c(t,`.claude.json`),legacy:!1},{id:`antigravity`,displayName:`Antigravity`,clientType:`gui`,command:`antigravity`,jsonPath:`/mcpServers/GitKraken`,pathFn:e=>c(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=>c(e,`.gemini`,`config`,`mcp_config.json`),legacy:!1,host:`antigravity`},{id:`windsurf`,displayName:`Windsurf`,clientType:`gui`,command:`windsurf`,jsonPath:`/mcpServers/GitKraken`,pathFn:e=>c(e,`.codeium`,`windsurf`,`mcp_config.json`),legacy:!1},{id:`cursor`,displayName:`Cursor`,clientType:`gui`,command:`cursor`,jsonPath:`/mcpServers/GitKraken`,pathFn:e=>c(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`?c(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(l(t),`Trae`,`User`,`mcp.json`),legacy:!1},{id:`copilot`,displayName:`GitHub Copilot CLI`,clientType:`cli`,command:`copilot`,jsonPath:`/mcpServers/GitKraken`,pathFn:e=>c(e,`.copilot`,`mcp-config.json`),legacy:!1},{id:`codex`,displayName:`Codex`,clientType:`cli`,command:`codex`,jsonPath:`mcp_servers.GitKraken`,pathFn:e=>c(e,`.codex`,`config.toml`),legacy:!1},{id:`gemini`,displayName:`Gemini CLI`,clientType:`cli`,command:`gemini`,jsonPath:`/mcpServers/GitKraken`,pathFn:e=>c(e,`.gemini`,`settings.json`),legacy:!0},{id:`kiro`,displayName:`Kiro`,clientType:`gui`,command:`kiro`,jsonPath:`/mcpServers/GitKraken`,pathFn:e=>c(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:o,pathFn:t=>e(u(t),`opencode.json`),legacy:!1}];function f(e){return d.find(t=>t.id===e)}function p(e){return f(e)!==void 0}function m(e){return f(e)?.legacy===!0}function h(e){let t=f(e);return t!==void 0&&!t.legacy}function g(e){return f(e)?.clientType??`unknown`}function _(e){return f(e)?.displayName??e}function v(e){return f(e)?.command}function y(){return d.filter(e=>!e.legacy).map(e=>e.id)}function b(){return d.filter(e=>e.legacy).map(e=>e.id)}function x(e){return(f(e)?.host??e).toLowerCase()}function S(e){return f(e)?.host??e}function C(e){return d.filter(t=>!t.legacy&&(t.host??t.id)===e).map(e=>e.id)}function w(e){return f(e)?.jsonPath??``}async function T(t){let n=u(t),r=e(n,`opencode.jsonc`);return await t.fs.readFile(r)===null?e(n,`opencode.json`):r}async function E(e,t){let n=f(e);if(n)return e===`opencode`?T(t):n.pathFn(t)}async function D(e,t){let n=await E(e,t),r=n?[n]:[];return e===`antigravity`&&r.push(c(t,`.gemini`,`antigravity`,`mcp_config.json`)),r}function O(e){return c(e,`.gemini`,`antigravity`,`mcp_config.json`)}function k(e,t){let n=v(e);if(!(!n||!t.resolveExecutable))return t.resolveExecutable(n)}function A(e,t){let n=f(e);return n?.host?C(n.host).some(n=>n!==e&&k(n,t)!==void 0):!1}async function j(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 M(e,t,n,r){for(let i of t){let t=await j(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 N(e,t){let n=k(e,t),r=[];if(n!==void 0)return{client:e,detected:!0,source:`executable`,executablePath:n,checkedPaths:r};let i=f(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&&!A(e,t)&&(r.push(n),await t.fs.readFile(n)!==null)?{client:e,detected:!0,source:`config-file`,path:n,checkedPaths:r}:M(e,[O(t)],t,r)}return M(e,await D(e,t),t,r)}async function P(e,t){let n=[];for(let r of await D(e,t))await F(e,r,t)&&n.push(r);return{client:e,installed:n.length>0,paths:n}}async function F(e,a,o){let c=await o.fs.readFile(a);if(c===null||c.trim()===``)return!1;if(e===`codex`){let e=n(c);if(!e)return!1;let t=w(`codex`);return i(e,t)!==void 0||r(e,t)!==void 0}return(e===`opencode`?s:[w(e)]).some(e=>t(c,e)!==void 0)}export{s as OPEN_CODE_JSON_PATHS,a as OPEN_CODE_V1_JSON_PATH,o as OPEN_CODE_V2_JSON_PATH,y as allClients,O as antigravityLegacyPath,v as clientCommand,E as clientConfigPath,D as clientConfigPaths,_ as clientDisplayName,k as clientExecutablePath,w as clientJSONPath,g as clientType,C as clientsForHost,N as detectClient,P as getClientInstallation,x as hostIdentity,S as hostOf,h as isInstallable,m as isLegacy,p as isSupported,b as legacyClientIds};
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};
@@ -1,3 +1,4 @@
1
+ import { McpLayoutOptions } from "./opencode-layout.js";
1
2
  import { Client } from "./clients.js";
2
3
 
3
4
  //#region src/mcp/config-entry.d.ts
@@ -6,8 +7,9 @@ type McpConfigEntry = Record<string, unknown>;
6
7
  /**
7
8
  * Builds the entry a client's GitKraken MCP server config carries. Port of
8
9
  * gkcli's `installer.generateConfigs`/`installConfig`: four shapes dispatched
9
- * by client family. OpenCode always gets its v2 shape.
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.
10
12
  */
11
- declare function generateConfigEntry(client: Client, executablePath: string, args: readonly string[]): McpConfigEntry;
13
+ declare function generateConfigEntry(client: Client, executablePath: string, args: readonly string[], options?: McpLayoutOptions): McpConfigEntry;
12
14
  //#endregion
13
15
  export { McpConfigEntry, generateConfigEntry };
@@ -1 +1 @@
1
- function e(e,t,n){switch(e){case`zed`:return{source:`custom`,enabled:!0,command:t,args:[...n]};case`copilot`:return{type:`local`,command:t,tools:[`*`],args:[...n]};case`opencode`:return{type:`local`,command:[t,...n],disabled:!1};default:return{type:`stdio`,command:t,args:[...n]}}}export{e as generateConfigEntry};
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};
@@ -1,6 +1,7 @@
1
- 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";
2
1
  import { McpConfigEntry, generateConfigEntry } from "./config-entry.js";
3
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";
4
5
  import { getMcpCliInstallCommand } from "./cli-install-command.js";
5
6
  import { McpUninstallFileResult, McpUninstallRequest, McpUninstallResult, uninstallMcp } from "./uninstaller.js";
6
- 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 McpUninstallFileResult, type McpUninstallRequest, type McpUninstallResult, allClients, antigravityLegacyPath, clientCommand, clientConfigPath, clientConfigPaths, clientDisplayName, clientExecutablePath, clientJSONPath, clientType, clientsForHost, detectClient, generateConfigEntry, getClientInstallation, getMcpCliInstallCommand, hostIdentity, hostOf, inspectMcpInstall, installMcp, isInstallable, isLegacy, isSupported, legacyClientIds, uninstallMcp };
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 };
package/dist/mcp/index.js CHANGED
@@ -1 +1 @@
1
- import{allClients as e,antigravityLegacyPath as t,clientCommand as n,clientConfigPath as r,clientConfigPaths as i,clientDisplayName as a,clientExecutablePath as o,clientJSONPath as s,clientType as c,clientsForHost as l,detectClient as u,getClientInstallation as d,hostIdentity as f,hostOf as p,isInstallable as m,isLegacy as h,isSupported as g,legacyClientIds as _}from"./clients.js";import{generateConfigEntry as v}from"./config-entry.js";import{inspectMcpInstall as y,installMcp as b}from"./installer.js";import{getMcpCliInstallCommand as x}from"./cli-install-command.js";import{uninstallMcp as S}from"./uninstaller.js";export{e as allClients,t as antigravityLegacyPath,n as clientCommand,r as clientConfigPath,i as clientConfigPaths,a as clientDisplayName,o as clientExecutablePath,s as clientJSONPath,c as clientType,l as clientsForHost,u as detectClient,v as generateConfigEntry,d as getClientInstallation,x as getMcpCliInstallCommand,f as hostIdentity,p as hostOf,y as inspectMcpInstall,b as installMcp,m as isInstallable,h as isLegacy,g as isSupported,_ as legacyClientIds,S as uninstallMcp};
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};
@@ -1,6 +1,7 @@
1
1
  import { AgentHostConfig } from "../config.js";
2
- import { Client } from "./clients.js";
3
2
  import { McpConfigEntry } from "./config-entry.js";
3
+ import { OpenCodeMcpLayout, OpenCodeMcpPolicy } from "./opencode-layout.js";
4
+ import { Client } from "./clients.js";
4
5
 
5
6
  //#region src/mcp/installer.d.ts
6
7
  interface McpInstallRequest {
@@ -13,11 +14,31 @@ interface McpInstallRequest {
13
14
  * host aim at a specific file, such as a workspace-scoped config.
14
15
  */
15
16
  configPath?: string;
17
+ /**
18
+ * Which OpenCode MCP layout to use; ignored for other clients. Omitted keeps the 0.1.1 behavior: v2 with the
19
+ * v1 entry pruned. A host that probed OpenCode should always pass a policy; see {@link OpenCodeMcpPolicy}.
20
+ */
21
+ openCode?: OpenCodeMcpPolicy;
16
22
  }
17
23
  /** How the client's config compares to what `installMcp` would write. */
18
24
  type McpInstallState = 'missing' | 'drifted' | 'current';
19
25
  /** Why {@link inspectMcpInstall} reported its state. Absent when the state is `current`. */
20
- type McpInstallStateReason = /** `missing`: the client's config file does not exist. */'file-missing' /** `missing`: the config file does not parse, so no entry can be read. */ | 'invalid-config' /** `missing`: the config has no GitKraken entry. */ | 'no-entry' /** `missing`: the GitKraken key exists but is not an object. */ | 'entry-not-object' /** `missing`: the client id is unknown or uninstall-only, so it has no config to inspect. */ | 'unsupported-client' /** `drifted`: the entry sits where an older install wrote it (Codex's literal-key table, OpenCode's v1 shape). */ | 'legacy-location' /** `drifted`: the entry lacks `command` or `args`. */ | 'pinned-key-missing' /** `drifted`: a key `installMcp` writes holds a different value. */ | 'value-mismatch';
26
+ type McpInstallStateReason = /** `missing`: the client's config file does not exist. */'file-missing' /** `missing`: the config file does not parse, so no entry can be read. */ | 'invalid-config' /** `missing`: the config has no GitKraken entry. */ | 'no-entry'
27
+ /**
28
+ * `missing`: `infer-existing` found no GitKraken entry in either OpenCode layout, so `installMcp` cannot choose
29
+ * one and would throw {@link OpenCodeMcpLayoutUnknownError}; the host must supply an authoritative layout.
30
+ */
31
+ | 'layout-unknown'
32
+ /**
33
+ * `missing`: the GitKraken key exists but is not an object. Under `infer-existing` this is also fatal to
34
+ * `installMcp`, which throws instead of overwriting it; an authoritative install overwrites it.
35
+ */
36
+ | 'entry-not-object' /** `missing`: the OpenCode config's root, `mcp` or `mcp.servers` cannot take the entry without displacing other data, so `installMcp` refuses it. */ | 'unsafe-config' /** `missing`: the client id is unknown or uninstall-only, so it has no config to inspect. */ | 'unsupported-client'
37
+ /**
38
+ * `drifted`: the entry sits where an older install wrote it (Codex's literal-key table, OpenCode's other
39
+ * layout), or the selected OpenCode entry is current but a prunable duplicate remains in the other layout.
40
+ */
41
+ | 'legacy-location' /** `drifted`: the entry lacks `command` or `args`. */ | 'pinned-key-missing' /** `drifted`: a key `installMcp` writes holds a different value. */ | 'value-mismatch';
21
42
  interface McpInstallStatus {
22
43
  client: Client;
23
44
  state: McpInstallState;
@@ -28,8 +49,22 @@ interface McpInstallStatus {
28
49
  reason?: McpInstallStateReason;
29
50
  /** The key that differs, for `pinned-key-missing` and `value-mismatch`. */
30
51
  key?: string;
31
- /** The entry on disk, when one exists. A host can read it to carry user-added args forward before reinstalling. */
52
+ /**
53
+ * The entry on disk, when one exists. A host can read it to carry user-added args forward before reinstalling.
54
+ * For a prunable OpenCode duplicate this is the selected entry, not the duplicate.
55
+ */
32
56
  entry?: Record<string, unknown>;
57
+ /**
58
+ * The OpenCode layout the inspection compared against; absent for other clients and when
59
+ * `infer-existing` found no entry (`layout-unknown`). Selection evidence only: it never authorizes pruning.
60
+ */
61
+ openCodeLayout?: OpenCodeMcpLayout;
62
+ /**
63
+ * The JSON pointer of the GitKraken entry in the OpenCode layout that was not compared, present whenever both
64
+ * layouts hold one. Reported only: `infer-existing` never prunes it. OpenCode can emit a conflict diagnostic
65
+ * when the two differ.
66
+ */
67
+ openCodeDuplicateLocation?: string;
33
68
  }
34
69
  interface McpInstallResult {
35
70
  client: Client;
@@ -41,8 +76,13 @@ interface McpInstallResult {
41
76
  location: string;
42
77
  /** The entry written, or already present when `change` is `unchanged`. */
43
78
  entry: McpConfigEntry;
44
- /** Stale locations pruned from the file, such as OpenCode's v1 pointer; empty otherwise. */
79
+ /** Stale locations pruned from the file, such as OpenCode's other-layout pointer; empty otherwise. */
45
80
  removedLocations: string[];
81
+ /**
82
+ * The OpenCode layout written or found; absent for other clients. For an inferred layout this is selection
83
+ * evidence only and must not be copied into an authoritative policy.
84
+ */
85
+ openCodeLayout?: OpenCodeMcpLayout;
46
86
  }
47
87
  /**
48
88
  * Writes the GitKraken entry in the shape each client reads. The shapes are
@@ -50,19 +90,40 @@ interface McpInstallResult {
50
90
  * the other's. A re-run that finds the entry already in place writes nothing.
51
91
  * Port of gkcli's installer.
52
92
  *
53
- * OpenCode gets its v2 shape and any v1 entry is pruned, which a v1-only
54
- * OpenCode will no longer read. Run the client's own CLI shortcut first when
55
- * it has one (see {@link getMcpCliInstallCommand}); this is the direct
93
+ * OpenCode gets the layout `request.openCode` selects, v2 when omitted. An
94
+ * `authoritative` layout is written first and the other layout's GitKraken
95
+ * entry is removed in a second write; `infer-existing` follows the layout the
96
+ * config already uses, v1 when both are present because OpenCode launches that
97
+ * entry, and prunes nothing. If that removal fails, the selected entry stays
98
+ * and {@link OpenCodeMcpCleanupError} reports it; running the install again
99
+ * finishes the cleanup. Run the client's own CLI shortcut first
100
+ * when it has one (see {@link getMcpCliInstallCommand}); this is the direct
56
101
  * config-file write.
57
102
  *
58
- * Throws, without writing, for a client that is unknown or uninstall-only and
59
- * for an existing config file that does not parse.
103
+ * Safe OpenCode migration needs the host's `FileSystemPort.writeFile` to leave
104
+ * the previous file intact when it rejects (e.g. write a temp file, then
105
+ * rename); the library cannot verify that and adds no cross-process lock.
106
+ *
107
+ * Throws, without writing, for a client that is unknown or uninstall-only, for
108
+ * an existing config file that does not parse, for an OpenCode config whose
109
+ * `mcp` containers cannot take the entry without displacing other data, and
110
+ * for `infer-existing` with no GitKraken entry ({@link OpenCodeMcpLayoutUnknownError}).
60
111
  */
61
112
  declare function installMcp(request: McpInstallRequest, config: AgentHostConfig): Promise<McpInstallResult>;
62
113
  /**
63
114
  * Reports whether the client's config already holds an entry equivalent to
64
115
  * the one `installMcp` would write, and if not, why. Mirrors Go's
65
116
  * `installer.State`.
117
+ *
118
+ * For OpenCode with an `authoritative` (or omitted) policy, the selected
119
+ * layout is compared; when only the other layout holds an entry, or the
120
+ * selected entry is current but a prunable duplicate remains, the state is
121
+ * `drifted` with reason `legacy-location`. With `infer-existing`, only the
122
+ * entry the config already uses is compared. With both layouts present that is the v1 entry, which OpenCode's
123
+ * compatibility reader retains over the native one, and the other entry is only reported in
124
+ * `openCodeDuplicateLocation`. No entry, a missing file or an empty file reports `layout-unknown`, because
125
+ * `installMcp` would throw {@link OpenCodeMcpLayoutUnknownError}; there is no `openCodeLayout` and `location`
126
+ * is the v2 pointer.
66
127
  */
67
128
  declare function inspectMcpInstall(request: McpInstallRequest, config: AgentHostConfig): Promise<McpInstallStatus>;
68
129
  //#endregion
@@ -1,3 +1,3 @@
1
- import{addAtPointer as e,findAtPointer as t,isValidJsonc as n,removeAtPointerAndEmptyParent as r}from"./jsonc-edit.js";import{assertValidToml as i,parseTomlSafe as a,tomlDottedPath as o,tomlLiteralKey as s}from"./toml-read.js";import{OPEN_CODE_V1_JSON_PATH as c,clientConfigPath as l,clientJSONPath as u,isInstallable as d}from"./clients.js";import{writeFileTracked as f}from"../file-change.js";import{generateConfigEntry as p}from"./config-entry.js";import{tomlSemanticEqual as m,withoutCodexEntry as h}from"./toml-safety.js";import{removeBlock as g,replaceOrAppendBlock as _}from"./toml-splice.js";import{stringify as v}from"smol-toml";const y=[`command`,`args`];async function b(n,i){let{client:a}=n,o=d(a)?n.configPath??await l(a,i):void 0;if(!o)throw Error(`unsupported client: ${a}`);let s=p(a,n.executablePath,n.args);if(a===`codex`)return x(n,i,o,s);let m=await i.fs.readFile(o),h=m===null||m.trim()===``?`{}`:m,g=u(a),_=t(h,g),v=m!==null&&C(_)&&k(_,s,{client:a,path:o,location:g}).state===`current`,y=v?h:e(h,g,s,o),b=[];if(a===`opencode`){let e=r(v?h:y,c,o);e.removed&&(y=e.text,b.push(c))}return{client:a,path:o,change:v&&b.length===0?`unchanged`:await f(i.fs,o,y,[],{mode:420}),format:`json`,location:g,entry:v?_:s,removedLocations:b}}async function x(e,t,n,r){let c=await t.fs.readFile(n);c!==null&&c.trim()!==``&&i(c,n);let l=u(`codex`),d=c??``,p=a(d),v=o(p,l),y=s(p,l);if(c!==null&&y===void 0&&C(v)&&k(v,r,{client:e.client,path:n,location:l}).state===`current`)return{client:e.client,path:n,change:`unchanged`,format:`toml`,location:l,entry:v,removedLocations:[]};let b,x,w=[];if(y!==void 0){if(!C(y)||v!==void 0&&!C(v))throw Error(`cannot safely migrate Codex MCP entry in ${n}: ambiguous legacy and nested tables`);let e=C(v)&&v.env!==void 0?v.env:y.env;if(e!==void 0&&!C(e))throw Error(`cannot safely migrate Codex MCP entry in ${n}: legacy env is not a table`);if(Object.entries(y).some(([e,t])=>e!==`env`&&C(t))||e&&Object.values(e).some(C))throw Error(`cannot safely migrate Codex MCP entry in ${n}: unsupported legacy subtable`);x=e;let t=g(d,l).text;b=_(v===void 0?t:g(t,l).text,l,S(l,r,e)),w.push(`["${l}"]`)}else b=_(d,l,S(l,r));let T=a(b),E=o(T,l);if(!C(E)||k(E,r,{client:e.client,path:n,location:l}).state!==`current`||s(T,l)!==void 0||y!==void 0&&!m(E.env,x)||!m(h(T),h(p)))throw Error(`cannot safely update Codex MCP entry in ${n}: unsupported TOML representation`);let D=await f(t.fs,n,b,[],{mode:384});return{client:e.client,path:n,change:D,format:`toml`,location:l,entry:E,removedLocations:w}}function S(e,t,n){let r=v(t),i=r.endsWith(`
2
- `)?r:`${r}\n`,a=n===void 0?``:v(n);return`[${e}]\n${i}${n===void 0?``:`\n[${e}.env]\n${a.endsWith(`
3
- `)?a:`${a}\n`}`}`}function C(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function w(e,t){return{...e,state:`missing`,reason:t}}function T(e,t){return{...e,state:`drifted`,reason:`legacy-location`,...C(t)&&{entry:t}}}async function E(e,r){let{client:i}=e,a=u(i),o=d(i)?e.configPath??await l(i,r):void 0;if(!o)return w({client:i,path:``,location:a},`unsupported-client`);let s={client:i,path:o,location:a},c=await r.fs.readFile(o);if(c===null)return w(s,`file-missing`);let f=p(i,e.executablePath,e.args);if(i===`codex`)return D(c,f,s);if(c.trim()===``)return w(s,`no-entry`);if(!n(c))return w(s,`invalid-config`);if(i===`opencode`)return O(c,f,s);let m=t(c,a);return m===void 0?w(s,`no-entry`):C(m)?k(m,f,s):w(s,`entry-not-object`)}function D(e,t,n){if(e.trim()===``)return w(n,`no-entry`);let r=a(e);if(!r)return w(n,`invalid-config`);let i=s(r,n.location);if(i!==void 0)return T(n,i);let c=o(r,n.location);return c===void 0?w(n,`no-entry`):C(c)?k(c,t,n):w(n,`entry-not-object`)}function O(e,n,r){let i=t(e,r.location),a=t(e,c),o={...r,location:c};if(i===void 0)return a===void 0?w(r,`no-entry`):T(o,a);if(!C(i))return w(r,`entry-not-object`);let s=k(i,n,r);return s.state===`current`&&a!==void 0?T(o,a):s}function k(e,t,n){for(let[r,i]of Object.entries(t)){if(!(r in e)){if(y.includes(r))return{...n,state:`drifted`,reason:`pinned-key-missing`,key:r,entry:e};continue}if(!A(e[r],i))return{...n,state:`drifted`,reason:`value-mismatch`,key:r,entry:e}}return{...n,state:`current`,entry:e}}function A(e,t){if(e===t)return!0;if(typeof e!=typeof t||e===null||t===null||typeof e!=`object`)return!1;if(Array.isArray(e)||Array.isArray(t))return!Array.isArray(e)||!Array.isArray(t)||e.length!==t.length?!1:e.every((e,n)=>A(e,t[n]));let n=Object.keys(e),r=Object.keys(t);return n.length===r.length?n.every(n=>A(e[n],t[n])):!1}export{E as inspectMcpInstall,b as installMcp};
1
+ import{writeFileTracked as e}from"../file-change.js";import{addAtPointer as t,assertValidJsonc as n,findAtPointer as r,isValidJsonc as i,removeAtPointerAndEmptyParent as a}from"./jsonc-edit.js";import{OpenCodeMcpCleanupError as o,OpenCodeMcpLayoutUnknownError as s,openCodeNativeServers as c,openCodeRefusal as l,resolveOpenCodePolicy as u}from"./opencode-layout.js";import{assertValidToml as d,parseTomlSafe as f,tomlDottedPath as p,tomlLiteralKey as m}from"./toml-read.js";import{clientConfigPath as h,clientJSONPath as g,isInstallable as _,openCodeJSONPath as v}from"./clients.js";import{generateConfigEntry as y}from"./config-entry.js";import{tomlSemanticEqual as b,withoutCodexEntry as x}from"./toml-safety.js";import{removeBlock as S,replaceOrAppendBlock as C}from"./toml-splice.js";import{stringify as w}from"smol-toml";const T=[`command`,`args`];async function E(n,i){let{client:a}=n,o=_(a)?n.configPath??await h(a,i):void 0;if(!o)throw Error(`unsupported client: ${a}`);if(a===`opencode`)return D(n,i,o);let s=y(a,n.executablePath,n.args);if(a===`codex`)return P(n,i,o,s);let c=await i.fs.readFile(o),l=c===null||c.trim()===``?`{}`:c,u=g(a),d=r(l,u),f=c!==null&&I(d)&&H(d,s,{client:a,path:o,location:u}).state===`current`;return{client:a,path:o,change:f?`unchanged`:await e(i.fs,o,t(l,u,s,o),[],{mode:420}),format:`json`,location:u,entry:f?d:s,removedLocations:[]}}async function D(r,i,s){let c=u(r.openCode),d=await i.fs.readFile(s),f=d===null||d.trim()===``?`{}`:d;n(f,s);let p=l(f,k(c),s);if(p!==void 0)throw Error(p);let m=c.mode===`authoritative`?c.layout:O(f,s),h=j(m),g=v(m),_=v(h),b=y(`opencode`,r.executablePath,r.args,{openCode:{mode:`authoritative`,layout:m}}),x=M(f,m),S=d!==null&&I(x)&&H(x,b,{client:`opencode`,path:s,location:g}).state===`current`,C=c.mode===`authoritative`?M(f,h):void 0,w=S?f:t(f,g,b,s),T=C===void 0?void 0:a(w,_,s),E=S?`unchanged`:await e(i.fs,s,w,[],{mode:420}),D=e=>({client:`opencode`,path:s,change:E,format:`json`,location:g,entry:S?x:b,removedLocations:e,openCodeLayout:m});if(T===void 0||!T.removed)return D([]);try{if(await i.fs.readFile(s)!==w)throw Error(`${s} changed while the OpenCode MCP entry was being installed`);let t=await e(i.fs,s,T.text,[],{mode:420});E===`unchanged`&&(E=t)}catch(e){throw A(e)?e:new o(D([]),e)}return D([_])}function O(e,t){let n=N(e);if(!n)throw new s;if(!I(n.found))throw Error(`cannot safely update OpenCode MCP entry in ${t}: the GitKraken entry is not an object`);return n.layout}function k(e){return e.mode===`authoritative`?e.layout:void 0}function A(e){return e instanceof Error&&e.name===`AbortError`}function j(e){return e===`v1`?`v2`:`v1`}function M(e,t){if(!(t===`v2`&&c(e)===void 0))return r(e,v(t))}function N(e){for(let t of[`v1`,`v2`]){let n=M(e,t);if(n!==void 0)return{layout:t,found:n}}}async function P(t,n,r,i){let a=await n.fs.readFile(r);a!==null&&a.trim()!==``&&d(a,r);let o=g(`codex`),s=a??``,c=f(s),l=p(c,o),u=m(c,o);if(a!==null&&u===void 0&&I(l)&&H(l,i,{client:t.client,path:r,location:o}).state===`current`)return{client:t.client,path:r,change:`unchanged`,format:`toml`,location:o,entry:l,removedLocations:[]};let h,_,v=[];if(u!==void 0){if(!I(u)||l!==void 0&&!I(l))throw Error(`cannot safely migrate Codex MCP entry in ${r}: ambiguous legacy and nested tables`);let e=I(l)&&l.env!==void 0?l.env:u.env;if(e!==void 0&&!I(e))throw Error(`cannot safely migrate Codex MCP entry in ${r}: legacy env is not a table`);if(Object.entries(u).some(([e,t])=>e!==`env`&&I(t))||e&&Object.values(e).some(I))throw Error(`cannot safely migrate Codex MCP entry in ${r}: unsupported legacy subtable`);_=e;let t=S(s,o).text;h=C(l===void 0?t:S(t,o).text,o,F(o,i,e)),v.push(`["${o}"]`)}else h=C(s,o,F(o,i));let y=f(h),w=p(y,o);if(!I(w)||H(w,i,{client:t.client,path:r,location:o}).state!==`current`||m(y,o)!==void 0||u!==void 0&&!b(w.env,_)||!b(x(y),x(c)))throw Error(`cannot safely update Codex MCP entry in ${r}: unsupported TOML representation`);let T=await e(n.fs,r,h,[],{mode:384});return{client:t.client,path:r,change:T,format:`toml`,location:o,entry:w,removedLocations:v}}function F(e,t,n){let r=w(t),i=r.endsWith(`
2
+ `)?r:`${r}\n`,a=n===void 0?``:w(n);return`[${e}]\n${i}${n===void 0?``:`\n[${e}.env]\n${a.endsWith(`
3
+ `)?a:`${a}\n`}`}`}function I(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function L(e,t){return{...e,state:`missing`,reason:t}}function R(e,t){return{...e,state:`drifted`,reason:`legacy-location`,...I(t)&&{entry:t}}}async function z(e,t){let{client:n}=e,a=n===`opencode`?u(e.openCode):void 0,o=a?.mode===`infer-existing`?void 0:a?.layout,s=n===`opencode`?v(o??`v2`):g(n),c=_(n)?e.configPath??await h(n,t):void 0;if(!c)return L({client:n,path:``,location:s},`unsupported-client`);let l={client:n,path:c,location:s,...o&&{openCodeLayout:o}},d=await t.fs.readFile(c);if(d===null)return L(l,a?.mode===`infer-existing`?`layout-unknown`:`file-missing`);if(n===`codex`)return B(d,y(n,e.executablePath,e.args),l);if(d.trim()===``)return L(l,a?.mode===`infer-existing`?`layout-unknown`:`no-entry`);if(!i(d))return L(l,`invalid-config`);if(a)return V(d,e,a,l);let f=r(d,s);if(f===void 0)return L(l,`no-entry`);let p=y(n,e.executablePath,e.args);return I(f)?H(f,p,l):L(l,`entry-not-object`)}function B(e,t,n){if(e.trim()===``)return L(n,`no-entry`);let r=f(e);if(!r)return L(n,`invalid-config`);let i=m(r,n.location);if(i!==void 0)return R(n,i);let a=p(r,n.location);return a===void 0?L(n,`no-entry`):I(a)?H(a,t,n):L(n,`entry-not-object`)}function V(e,t,n,r){let i=e=>y(`opencode`,t.executablePath,t.args,{openCode:{mode:`authoritative`,layout:e}});if(l(e,k(n),r.path)!==void 0)return L(r,`unsafe-config`);if(n.mode===`infer-existing`){let t=N(e);if(!t)return L(r,`layout-unknown`);let{layout:n,found:a}=t,o=j(n),s={...r,location:v(n),openCodeLayout:n,...M(e,o)!==void 0&&{openCodeDuplicateLocation:v(o)}};return I(a)?H(a,i(n),s):L(s,`entry-not-object`)}let a=M(e,n.layout),o=j(n.layout),s=M(e,o);if(a===void 0)return s===void 0?L(r,`no-entry`):R({...r,location:v(o)},s);let c=s===void 0?r:{...r,openCodeDuplicateLocation:v(o)};if(!I(a))return L(c,`entry-not-object`);let u=H(a,i(n.layout),c);return u.state===`current`&&s!==void 0?R(c,a):u}function H(e,t,n){for(let[r,i]of Object.entries(t)){if(!(r in e)){if(T.includes(r))return{...n,state:`drifted`,reason:`pinned-key-missing`,key:r,entry:e};continue}if(!U(e[r],i))return{...n,state:`drifted`,reason:`value-mismatch`,key:r,entry:e}}return{...n,state:`current`,entry:e}}function U(e,t){if(e===t)return!0;if(typeof e!=typeof t||e===null||t===null||typeof e!=`object`)return!1;if(Array.isArray(e)||Array.isArray(t))return!Array.isArray(e)||!Array.isArray(t)||e.length!==t.length?!1:e.every((e,n)=>U(e,t[n]));let n=Object.keys(e),r=Object.keys(t);return n.length===r.length?n.every(n=>U(e[n],t[n])):!1}export{z as inspectMcpInstall,E as installMcp};
@@ -1,6 +1,12 @@
1
1
  //#region src/mcp/jsonc-edit.d.ts
2
2
  /** Whether `text` is valid JSONC: comments, a trailing comma and a leading UTF-8 BOM are all fine. */
3
3
  declare function isValidJsonc(text: string): boolean;
4
+ /** Throws when `text` isn't valid JSONC; never touches `text`. */
5
+ declare function assertValidJsonc(text: string, path: string): void;
6
+ interface EditOptions {
7
+ /** Edit only the targeted member's text; the formatting of its siblings is left byte-for-byte. */
8
+ preserveSiblings?: boolean;
9
+ }
4
10
  /** Reads the value at `pointer` in JSONC `text`, or `undefined` when absent or the document is unparsable. */
5
11
  declare function findAtPointer(text: string, pointer: string): unknown;
6
12
  /**
@@ -11,7 +17,7 @@ declare function findAtPointer(text: string, pointer: string): unknown;
11
17
  * is touched. `sourcePath` names `text`'s file for the error thrown when it
12
18
  * isn't valid JSONC; no edit is made in that case.
13
19
  */
14
- declare function addAtPointer(text: string, pointer: string, value: unknown, sourcePath: string): string;
20
+ declare function addAtPointer(text: string, pointer: string, value: unknown, sourcePath: string, options?: EditOptions): string;
15
21
  /**
16
22
  * Removes the value at `pointer`, leaving its parent container in place even
17
23
  * if that empties it. Mirrors gkcli's `uninstaller.writeConfig`, the shape
@@ -20,7 +26,7 @@ declare function addAtPointer(text: string, pointer: string, value: unknown, sou
20
26
  *
21
27
  * Returns `removed: false` when there was nothing at `pointer` to begin with.
22
28
  */
23
- declare function removeAtPointer(text: string, pointer: string, sourcePath: string): {
29
+ declare function removeAtPointer(text: string, pointer: string, sourcePath: string, options?: EditOptions): {
24
30
  text: string;
25
31
  removed: boolean;
26
32
  };
@@ -33,9 +39,9 @@ declare function removeAtPointer(text: string, pointer: string, sourcePath: stri
33
39
  *
34
40
  * Returns `removed: false` when there was nothing at `pointer` to begin with.
35
41
  */
36
- declare function removeAtPointerAndEmptyParent(text: string, pointer: string, sourcePath: string): {
42
+ declare function removeAtPointerAndEmptyParent(text: string, pointer: string, sourcePath: string, options?: EditOptions): {
37
43
  text: string;
38
44
  removed: boolean;
39
45
  };
40
46
  //#endregion
41
- export { addAtPointer, findAtPointer, isValidJsonc, removeAtPointer, removeAtPointerAndEmptyParent };
47
+ export { EditOptions, addAtPointer, assertValidJsonc, findAtPointer, isValidJsonc, removeAtPointer, removeAtPointerAndEmptyParent };
@@ -1,3 +1,9 @@
1
1
  import{applyEdits as e,findNodeAtLocation as t,getNodeValue as n,modify as r,parseTree as i,visit as a}from"jsonc-parser";const o={tabSize:2,insertSpaces:!0,eol:`
2
- `},s={allowTrailingComma:!0};function c(e){return e.startsWith(``)?{bom:``,body:e.slice(1)}:{bom:``,body:e}}function l(e){let t=[];return i(c(e).body,t,s),t.length===0}function u(e,t){if(!l(e))throw Error(`invalid JSON in ${t}`)}function d(e){return e?e.split(`/`).slice(1).map(e=>e.replace(/~1/g,`/`).replace(/~0/g,`~`)):[]}function f(e,n){let r=[],i=e;for(let e of n){let n=i?.type===`array`&&/^(0|[1-9]\d*)$/.test(e)?Number(e):e;r.push(n),i=i?t(i,[n]):void 0}return r}function p(e,r){let a=[],o=i(c(e).body,a,s);if(!o||a.length>0)return;let l=t(o,f(o,d(r)));return l?n(l):void 0}function m(n,a,s,l){u(n,l);let{bom:p,body:m}=c(n),h=d(a),g=m;for(let n=1;n<h.length;n++){let a=i(g),s=a?f(a,h.slice(0,n)):h.slice(0,n);a&&t(a,s)||(g=e(g,r(g,s,{},{formattingOptions:o})))}let _=i(g),v=_?f(_,h):h;return p+e(g,r(g,v,s,{formattingOptions:o}))}function h(n,a){let c=i(n,void 0,s),l=c?f(c,a):a,u=c?t(c,l):void 0;if(!(a.length===0||!c||!u))return u.parent?.type===`array`&&typeof l.at(-1)==`number`?g(n,u.parent,u):v(e(n,r(n,l,void 0,{formattingOptions:o})))}function g(t,n,r){let i=n.children??[],a=i.indexOf(r),o=[{offset:r.offset,length:r.length,content:``}];if(a<i.length-1){let e=_(t,r.offset+r.length,i[a+1].offset);e!==void 0&&o.push({offset:e,length:1,content:``})}else if(a>0){let e=i[a-1],n=_(t,e.offset+e.length,r.offset);n!==void 0&&o.push({offset:n,length:1,content:``})}else{let e=_(t,r.offset+r.length,n.offset+n.length-1);e!==void 0&&o.push({offset:e,length:1,content:``})}return e(t,o.sort((e,t)=>e.offset-t.offset))}function _(e,t,n){for(let r=t;r<n;r++)if(e.startsWith(`//`,r))for(;r<n&&e[r]!==`
3
- `;)r++;else if(e.startsWith(`/*`,r)){let t=e.indexOf(`*/`,r+2);if(t<0||t>=n)return;r=t+1}else if(e[r]===`,`)return r}function v(e){let t={justOpened:!1},n=()=>{t.justOpened=!1};a(e,{onObjectBegin:()=>{t.justOpened=!0},onSeparator:(e,r)=>{t.justOpened&&e===`,`&&(t.commaOffset??=r),n()},onObjectProperty:n,onObjectEnd:n},s);let{commaOffset:r}=t;return r===void 0?e:e.slice(0,r)+e.slice(r+1)}function y(e,t,n){u(e,n);let{bom:r,body:i}=c(e),a=h(i,d(t));return a===void 0?{text:e,removed:!1}:{text:r+a,removed:!0}}function b(e,t,n){u(e,n);let{bom:r,body:i}=c(e),a=d(t),o=h(i,a);if(o===void 0)return{text:e,removed:!1};let s=a.slice(0,-1);return s.length>0&&x(o,s)&&(o=h(o,s)??o),{text:r+o,removed:!0}}function x(e,n){let r=i(e,void 0,s);if(!r)return!1;let a=t(r,f(r,n));return!a||a.type!==`object`?!1:(a.children?.length??0)===0}export{m as addAtPointer,p as findAtPointer,l as isValidJsonc,y as removeAtPointer,b as removeAtPointerAndEmptyParent};
2
+ `},s={allowTrailingComma:!0};function c(e){return e.startsWith(``)?{bom:``,body:e.slice(1)}:{bom:``,body:e}}function l(e){let t=[];return i(c(e).body,t,s),t.length===0}function u(e,t){if(!l(e))throw Error(`invalid JSON in ${t}`)}function d(e,t){return e.lastIndexOf(`
3
+ `,t-1)+1}function f(e,t){let n=e.indexOf(`
4
+ `,t);return n<0?e.length:n}function p(e,t){let n=f(e,t);return n>t&&e[n-1]===`\r`?n-1:n}function m(e,t,n=`
5
+ `){return JSON.stringify(e,null,2).replace(/\n/g,`${n}${t}`)}function h(t,n,r,i){let a=n.children??[],o=a[a.length-1],s=o.offset+o.length,c=n.offset+n.length-1,l=E(t,s,c),u=t.slice(d(t,o.offset),o.offset),f=p(t,l??s),h=t.includes(`\r
6
+ `)?`\r
7
+ `:`
8
+ `,g=`${JSON.stringify(r)}: `;if(!/^\s*$/.test(u)||f>=c)return e(t,[{offset:s,length:0,content:`, ${g}${JSON.stringify(i)}`}]);let _=`${u}${g}${m(i,u,h)}`;return e(t,l===void 0?[{offset:s,length:0,content:`,`},{offset:f,length:0,content:`${h}${_}`}]:[{offset:f,length:0,content:`${h}${_},`}])}function g(t,r,i){if(JSON.stringify(n(r))===JSON.stringify(i))return t;let a=t.slice(d(t,r.offset),r.offset).match(/^\s*/)?.[0]??``;return e(t,[{offset:r.offset,length:r.length,content:m(i,a)}])}function _(e,n,r){let a=i(e,void 0,s);if(!a)return;let o=x(a,n),c=a,l=0;for(;l<o.length;l++){let e=t(c,[o[l]]);if(!e)break;c=e}if(l===o.length)return g(e,c,r);if(c.type!==`object`||!c.children?.length)return;let u=o.slice(l+1).reduceRight((e,t)=>({[t]:e}),r);return v(h(e,c,String(o[l]),u),n,r)}function v(e,r,a){let o=i(e,void 0,s),c=o?t(o,x(o,r)):void 0;return c&&JSON.stringify(n(c))===JSON.stringify(a)?e:void 0}function y(t,n){let r=n.parent,i=r?.children??[],a=i.indexOf(n),o=n.offset+n.length,s=r?r.offset+r.length-1:t.length,c=E(t,o,a<i.length-1?i[a+1].offset:s),l=[];if(c===void 0&&a>0){let e=i[a-1],r=E(t,e.offset+e.length,n.offset);r!==void 0&&l.push({offset:r,length:1,content:``})}let u=c===void 0?o:c+1,p=d(t,n.offset),m=f(t,u),h=/^\s*$/.test(t.slice(p,n.offset))&&/^\s*(\/\/[^\n]*)?$/.test(t.slice(u,m))&&m<s;return l.push(h?{offset:p,length:m+1-p,content:``}:{offset:n.offset,length:u-n.offset,content:``}),e(t,l.sort((e,t)=>e.offset-t.offset))}function b(e){return e?e.split(`/`).slice(1).map(e=>e.replace(/~1/g,`/`).replace(/~0/g,`~`)):[]}function x(e,n){let r=[],i=e;for(let e of n){let n=i?.type===`array`&&/^(0|[1-9]\d*)$/.test(e)?Number(e):e;r.push(n),i=i?t(i,[n]):void 0}return r}function S(e,r){let a=[],o=i(c(e).body,a,s);if(!o||a.length>0)return;let l=t(o,x(o,b(r)));return l?n(l):void 0}function C(n,a,s,l,d={}){u(n,l);let{bom:f,body:p}=c(n),m=b(a);if(d.preserveSiblings){let e=_(p,m,s);if(e!==void 0)return f+e}let h=p;for(let n=1;n<m.length;n++){let a=i(h),s=a?x(a,m.slice(0,n)):m.slice(0,n);a&&t(a,s)||(h=e(h,r(h,s,{},{formattingOptions:o})))}let g=i(h),v=g?x(g,m):m;return f+e(h,r(h,v,s,{formattingOptions:o}))}function w(n,a,c={}){let l=i(n,void 0,s),u=l?x(l,a):a,d=l?t(l,u):void 0;if(!(a.length===0||!l||!d))return d.parent?.type===`array`&&typeof u.at(-1)==`number`?T(n,d.parent,d):c.preserveSiblings&&d.parent?.type===`property`?y(n,d.parent):D(e(n,r(n,u,void 0,{formattingOptions:o})))}function T(t,n,r){let i=n.children??[],a=i.indexOf(r),o=[{offset:r.offset,length:r.length,content:``}];if(a<i.length-1){let e=E(t,r.offset+r.length,i[a+1].offset);e!==void 0&&o.push({offset:e,length:1,content:``})}else if(a>0){let e=i[a-1],n=E(t,e.offset+e.length,r.offset);n!==void 0&&o.push({offset:n,length:1,content:``})}else{let e=E(t,r.offset+r.length,n.offset+n.length-1);e!==void 0&&o.push({offset:e,length:1,content:``})}return e(t,o.sort((e,t)=>e.offset-t.offset))}function E(e,t,n){for(let r=t;r<n;r++)if(e.startsWith(`//`,r))for(;r<n&&e[r]!==`
9
+ `;)r++;else if(e.startsWith(`/*`,r)){let t=e.indexOf(`*/`,r+2);if(t<0||t>=n)return;r=t+1}else if(e[r]===`,`)return r}function D(e){let t={justOpened:!1},n=()=>{t.justOpened=!1};a(e,{onObjectBegin:()=>{t.justOpened=!0},onSeparator:(e,r)=>{t.justOpened&&e===`,`&&(t.commaOffset??=r),n()},onObjectProperty:n,onObjectEnd:n},s);let{commaOffset:r}=t;return r===void 0?e:e.slice(0,r)+e.slice(r+1)}function O(e,t,n,r={}){u(e,n);let{bom:i,body:a}=c(e),o=w(a,b(t),r);return o===void 0?{text:e,removed:!1}:{text:i+o,removed:!0}}function k(e,t,n,r={}){u(e,n);let{bom:i,body:a}=c(e),o=b(t),s=w(a,o,r);if(s===void 0)return{text:e,removed:!1};let l=o.slice(0,-1);return l.length>0&&A(s,l)&&(s=w(s,l,r)??s),{text:i+s,removed:!0}}function A(e,n){let r=i(e,void 0,s);if(!r)return!1;let a=t(r,x(r,n));return!a||a.type!==`object`?!1:(a.children?.length??0)===0}export{C as addAtPointer,u as assertValidJsonc,S as findAtPointer,l as isValidJsonc,O as removeAtPointer,k as removeAtPointerAndEmptyParent};
@@ -0,0 +1,54 @@
1
+ import { McpInstallResult } from "./installer.js";
2
+
3
+ //#region src/mcp/opencode-layout.d.ts
4
+ /** Where OpenCode reads MCP servers: `/mcp/<name>` (v1) or `/mcp/servers/<name>` (v2). */
5
+ type OpenCodeMcpLayout = 'v1' | 'v2';
6
+ /**
7
+ * How a host tells this library which OpenCode MCP layout to use.
8
+ *
9
+ * - `authoritative`: the host verified the installed OpenCode and asserts the layout. Install writes it and
10
+ * prunes the other layout's GitKraken entry.
11
+ * - `infer-existing`: the host could not tell. Install and inspection follow the layout the config already
12
+ * uses and never prune. With both layouts present they follow v1, the entry OpenCode's compatibility reader
13
+ * retains over the native one. It needs the config file, so read-free helpers reject it.
14
+ *
15
+ * Omitting the policy keeps the native-v2 behavior of 0.1.1: v2 generation and inspection, v1 pruning.
16
+ */
17
+ type OpenCodeMcpPolicy = {
18
+ mode: 'authoritative';
19
+ layout: OpenCodeMcpLayout;
20
+ } | {
21
+ mode: 'infer-existing';
22
+ };
23
+ interface McpLayoutOptions {
24
+ /** Ignored for every client but OpenCode. */
25
+ openCode?: OpenCodeMcpPolicy;
26
+ }
27
+ /** `infer-existing` found no GitKraken entry to follow, or was passed to a helper that cannot read the config. */
28
+ declare class OpenCodeMcpLayoutUnknownError extends Error {
29
+ constructor();
30
+ }
31
+ /**
32
+ * The selected OpenCode entry is in place, but removing the other layout's entry did not complete. Not a
33
+ * success: an older reader can still reject a leftover native `servers` envelope, and a v1-compatible reader
34
+ * can pick the older duplicate. Re-running the install finishes the cleanup.
35
+ */
36
+ declare class OpenCodeMcpCleanupError extends Error {
37
+ /** What was committed before the cleanup failed: the selected location and entry, and no removed locations. */
38
+ readonly result: McpInstallResult;
39
+ constructor(result: McpInstallResult, cause: unknown);
40
+ }
41
+ /** Applies the omitted-policy default and rejects a value that is not an {@link OpenCodeMcpPolicy}. */
42
+ declare function resolveOpenCodePolicy(policy: OpenCodeMcpPolicy | undefined): OpenCodeMcpPolicy;
43
+ /** The layout a read-free helper emits for `options`; `infer-existing` cannot be resolved without reading the config. */
44
+ declare function openCodeLayoutFor(options: McpLayoutOptions | undefined): OpenCodeMcpLayout;
45
+ /** The v2 `servers` envelope, or `undefined` when `/mcp/servers` is absent or is a flat v1 server that happens to be named `servers`. */
46
+ declare function openCodeNativeServers(text: string): Record<string, unknown> | undefined;
47
+ /**
48
+ * Why writing the GitKraken entry in `layout` would displace other data in the OpenCode config `text`, or
49
+ * `undefined` when it is safe. Shared by install, which throws it, and inspect, which reports it. An undefined
50
+ * `layout` (`infer-existing`) checks only the root and `mcp`: an inferred v2 entry already sits in a native envelope.
51
+ */
52
+ declare function openCodeRefusal(text: string, layout: OpenCodeMcpLayout | undefined, path: string): string | undefined;
53
+ //#endregion
54
+ export { McpLayoutOptions, OpenCodeMcpCleanupError, OpenCodeMcpLayout, OpenCodeMcpLayoutUnknownError, OpenCodeMcpPolicy, openCodeLayoutFor, openCodeNativeServers, openCodeRefusal, resolveOpenCodePolicy };
@@ -0,0 +1 @@
1
+ import{findAtPointer as e}from"./jsonc-edit.js";var t=class extends Error{constructor(){super(`cannot resolve the OpenCode MCP layout for infer-existing: there is no GitKraken entry to follow, or the caller cannot read the config; pass an authoritative layout instead`),this.name=`OpenCodeMcpLayoutUnknownError`}},n=class extends Error{result;constructor(e,t){super(`OpenCode MCP entry was written but alternate-layout cleanup did not complete; run the install again`,{cause:t}),this.name=`OpenCodeMcpCleanupError`,this.result=e}};function r(e){if(e===void 0)return{mode:`authoritative`,layout:`v2`};let t=e;if(t?.mode===`infer-existing`&&!(`layout`in t))return{mode:`infer-existing`};if(t?.mode===`authoritative`&&(t.layout===`v1`||t.layout===`v2`))return{mode:`authoritative`,layout:t.layout};throw TypeError(`invalid OpenCode MCP policy`)}function i(e){let n=r(e?.openCode);if(n.mode===`infer-existing`)throw new t;return n.layout}const a=`/mcp/servers`;function o(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function s(e){return[`type`,`enabled`].some(t=>Object.hasOwn(e,t)&&(e[t]===null||typeof e[t]!=`object`||Array.isArray(e[t])))}function c(t){let n=e(t,a);return o(n)&&!s(n)?n:void 0}function l(t,n,r){let i=e(t,`/mcp`);if(!o(e(t,``))||i!==void 0&&!o(i))return`cannot safely update OpenCode MCP entry in ${r}: the config or its "mcp" is not an object`;if(n!==`v2`)return;let c=e(t,a);if(c!==void 0&&!o(c))return`cannot safely update OpenCode MCP entry in ${r}: "mcp.servers" is not an object`;if(o(c)&&s(c))return`cannot safely write the OpenCode v2 MCP layout in ${r}: "mcp.servers" is a server definition`}export{n as OpenCodeMcpCleanupError,t as OpenCodeMcpLayoutUnknownError,i as openCodeLayoutFor,c as openCodeNativeServers,l as openCodeRefusal,r as resolveOpenCodePolicy};