@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.
- package/CHANGELOG.md +52 -3
- package/README.md +55 -2
- package/dist/agents.d.ts +11 -2
- package/dist/agents.js +1 -1
- package/dist/config.d.ts +2 -1
- package/dist/hooks/claude.d.ts +3 -2
- package/dist/hooks/claude.js +2 -3
- package/dist/hooks/errors.d.ts +4 -2
- package/dist/hooks/errors.js +1 -1
- package/dist/hooks/events.d.ts +11 -8
- package/dist/hooks/events.js +1 -1
- package/dist/hooks/inspect.d.ts +9 -0
- package/dist/hooks/inspect.js +1 -0
- package/dist/hooks/install.js +1 -1
- package/dist/hooks/registry.d.ts +26 -0
- package/dist/hooks/registry.js +1 -0
- package/dist/hooks/results.d.ts +23 -1
- package/dist/hooks/templates.d.ts +4 -2
- package/dist/hooks/templates.js +20 -9
- package/dist/hooks/validate.d.ts +19 -0
- package/dist/hooks/validate.js +1 -0
- package/dist/hooks/writers.d.ts +8 -3
- package/dist/hooks/writers.js +1 -1
- package/dist/index.d.ts +9 -5
- package/dist/index.js +1 -1
- package/dist/mcp/clients.d.ts +12 -3
- package/dist/mcp/clients.js +1 -1
- package/dist/mcp/config-entry.d.ts +4 -2
- package/dist/mcp/config-entry.js +1 -1
- package/dist/mcp/index.d.ts +3 -2
- package/dist/mcp/index.js +1 -1
- package/dist/mcp/installer.d.ts +70 -9
- package/dist/mcp/installer.js +3 -3
- package/dist/mcp/jsonc-edit.d.ts +10 -4
- package/dist/mcp/jsonc-edit.js +8 -2
- package/dist/mcp/opencode-layout.d.ts +54 -0
- package/dist/mcp/opencode-layout.js +1 -0
- package/dist/mcp/uninstaller.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { HookHost } from "./events.js";
|
|
2
|
+
|
|
3
|
+
//#region src/hooks/validate.d.ts
|
|
4
|
+
/** Throws `invalid-event` for the first event the host's manifest does not list, with gkcli's wording. */
|
|
5
|
+
declare function validateHookEvents(host: HookHost, events: readonly string[]): void;
|
|
6
|
+
/** Throws `blocking-unsupported` when the host cannot honor a decision (`noBlocking` in the registry) and `blockingEvents` is not empty. */
|
|
7
|
+
declare function assertBlockingSupported(host: HookHost, blockingEvents: readonly string[] | undefined, path?: string): void;
|
|
8
|
+
/** Resolves the events to write: the explicit ones, or the manifest's minus `skippedDefaultEvents` when none are requested. */
|
|
9
|
+
declare function selectHookEvents(request: {
|
|
10
|
+
host: HookHost;
|
|
11
|
+
events?: readonly string[];
|
|
12
|
+
blockingEvents?: readonly string[];
|
|
13
|
+
}): {
|
|
14
|
+
events: string[];
|
|
15
|
+
blockingEvents: string[];
|
|
16
|
+
isDefault: boolean;
|
|
17
|
+
};
|
|
18
|
+
//#endregion
|
|
19
|
+
export { assertBlockingSupported, selectHookEvents, validateHookEvents };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{HookConfigError as e}from"./errors.js";import{eventManifest as t}from"./events.js";import{hookClientInfo as n}from"./registry.js";function r(n,r){let i=t[n].events;for(let t of r)if(!i.includes(t))throw new e(`invalid-event`,`'${t}' is not a valid event for ${n}. Valid events: ${i.join(`, `)}`)}function i(t,r,i){if(r?.length&&n(t).noBlocking)throw new e(`blocking-unsupported`,`${t} does not support blocking hooks`,i)}function a(e){let n=t[e.host],i=!e.events?.length&&!e.blockingEvents?.length,a=i?n.events.filter(e=>!n.skippedDefaultEvents.includes(e)):[...e.events??[]],o=[...e.blockingEvents??[]];return r(e.host,[...a,...o]),{events:a,blockingEvents:o,isDefault:i}}export{i as assertBlockingSupported,a as selectHookEvents,r as validateHookEvents};
|
package/dist/hooks/writers.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AgentHostConfig } from "../config.js";
|
|
2
2
|
import { HookHost } from "./events.js";
|
|
3
|
-
import { HookInstallResult, HookUninstallResult } from "./results.js";
|
|
3
|
+
import { HookInspection, HookInstallResult, HookUninstallResult } from "./results.js";
|
|
4
4
|
|
|
5
5
|
//#region src/hooks/writers.d.ts
|
|
6
6
|
type JsonHookHost = Extract<HookHost, 'cursor' | 'codex' | 'copilot' | 'antigravity' | 'augment' | 'grok'>;
|
|
@@ -10,9 +10,14 @@ interface HookWriteRequest {
|
|
|
10
10
|
events?: readonly string[];
|
|
11
11
|
blockingEvents?: readonly string[];
|
|
12
12
|
}
|
|
13
|
-
declare
|
|
13
|
+
declare const BLOCKING_SUFFIX = " --blocking";
|
|
14
|
+
declare const HOOK_RUN_MARKER = " ai hook run --host ";
|
|
15
|
+
/** `event` is appended as `--event` for hosts whose payload omits the event name; a blocking entry ends with `--blocking`. */
|
|
16
|
+
declare function command(executablePath: string, host: HookHost, config: AgentHostConfig, blocking: boolean, event?: string): string;
|
|
14
17
|
/** Merges only GitKraken-owned hooks, preserving unrelated entries and document keys. */
|
|
15
18
|
declare function installJsonHooks(request: HookWriteRequest, config: AgentHostConfig): Promise<HookInstallResult>;
|
|
16
19
|
declare function uninstallJsonHooks(host: JsonHookHost, config: AgentHostConfig, events?: readonly string[]): Promise<HookUninstallResult>;
|
|
20
|
+
/** Read-only: reports the GitKraken events in `host`'s hooks file without ever writing it. */
|
|
21
|
+
declare function inspectJsonHooks(host: HookHost, config: AgentHostConfig): Promise<HookInspection>;
|
|
17
22
|
//#endregion
|
|
18
|
-
export { HookWriteRequest, command, installJsonHooks, uninstallJsonHooks };
|
|
23
|
+
export { BLOCKING_SUFFIX, HOOK_RUN_MARKER, HookWriteRequest, command, inspectJsonHooks, installJsonHooks, uninstallJsonHooks };
|
package/dist/hooks/writers.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{joinPath as e}from"../config.js";import{removeTracked as t,writeFileTracked as n}from"../file-change.js";import{HookConfigError as r}from"./errors.js";import{eventManifest as
|
|
1
|
+
import{joinPath as e}from"../config.js";import{removeTracked as t,writeFileTracked as n}from"../file-change.js";import{HookConfigError as r,hookErrorCode as i}from"./errors.js";import{eventManifest as a}from"./events.js";import{hookClientInfo as o}from"./registry.js";import{assertBlockingSupported as s,selectHookEvents as c}from"./validate.js";const l={cursor:{path:t=>e(t.paths.home,`.cursor`,`hooks.json`),key:`hooks`,version:!0,matcher:!1},codex:{path:t=>e(t.paths.home,`.codex`,`hooks.json`),key:`hooks`,version:!1,matcher:!0},copilot:{path:t=>e(t.paths.home,`.copilot`,`hooks`,`gitkraken-hooks.json`),key:`hooks`,version:!0,matcher:!1},antigravity:{path:t=>e(t.paths.home,`.gemini`,`config`,`hooks.json`),key:`gitkraken`,version:!1,matcher:!1},augment:{path:t=>e(t.paths.home,`.augment`,`settings.json`),key:`hooks`,version:!1,matcher:!0,neverDelete:!0},grok:{path:t=>e(t.paths.grokHome??e(t.paths.home,`.grok`),`hooks`,`gitkraken-hooks.json`),key:`hooks`,version:!1,matcher:!0}},u=` --blocking`,d=` ai hook run --host `;function f(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function p(e){return typeof e==`string`&&e.includes(` ai hook run --host `)}function m(e,t,n,r,i){let s=a[t],c=n.platform===`win32`&&`windowsShell`in s&&s.windowsShell===`powershell`?`& `:``,l=i&&`eventFlag`in s&&s.eventFlag?` --event ${i}`:``,f=o(t).blockingWait,p=r?(f?` --timeout ${f}`:``)+u:``;return`${c}"${e.replaceAll(`\\`,`/`)}"${d}${t}${l}${p}`}function h(e,t){return e===`antigravity`?t===`PreToolUse`||t===`PostToolUse`:l[e].matcher}function g(e,t,n){if(!Array.isArray(e)||!e.every(f))throw new r(`wrong-shape`,`${n} has the wrong event shape`,n);if(t&&e.some(e=>`command`in e||`hooks`in e&&(!Array.isArray(e.hooks)||!e.hooks.every(f))))throw new r(`wrong-shape`,`${n} has the wrong matcher shape`,n);if(!t&&e.some(e=>`hooks`in e))throw new r(`wrong-shape`,`${n} has the wrong flat shape`,n)}function _(e,t){return Array.isArray(e)?e.flatMap(e=>t&&f(e)&&Array.isArray(e.hooks)?e.hooks.map(e=>f(e)?e.command:void 0):[f(e)?e.command:void 0]).filter(e=>p(e)):[]}function v(e,t){return Object.keys(t).filter(n=>_(t[n],h(e,n)).length>0)}function y(e,t){return t?e.flatMap(e=>{if(!Array.isArray(e.hooks))return[e];let t=e.hooks.filter(e=>!f(e)||!p(e.command));return t.length===e.hooks.length?[e]:t.length?[{...e,hooks:t}]:[]}):e.filter(e=>!p(e.command))}function b(e,t,n){return n?86400*(`blockingTimeoutMultiplier`in a[e]?Number(a[e].blockingTimeoutMultiplier):1):e===`codex`&&t===`SessionEnd`?3:void 0}function x(e,t,n,r,i){let a={type:`command`,command:r,...i?{timeout:i}:{}};if(!t)return[...y(e,!1),a];let o=y(e,!0),s=o.find(e=>!e.matcher&&Array.isArray(e.hooks)&&e.hooks.length>0);s||(s=n===`augment`?{hooks:[]}:{matcher:``,hooks:[]},o.push(s));let c=Array.isArray(s.hooks)?s.hooks:[];return s.hooks=[...c.filter(e=>!p(e.command)),a],o}function S(e,t){let n;try{n=JSON.parse(e.replace(/^\uFEFF/,``))}catch{throw new r(`invalid-json`,`Invalid JSON at ${t}`,t)}if(!f(n))throw new r(`wrong-shape`,`Invalid hooks document at ${t}`,t);return n}function C(e,t,n){let i=e[t];if(i!=null){if(!f(i))throw new r(`wrong-shape`,`Invalid hooks wrapper at ${n}`,n);return i}}async function w(e,t){if(!Object.hasOwn(l,e.host))throw new r(`unsupported-host`,`No JSON writer for ${e.host}`);let i=e.host,u=l[i],d=u.path(t),{events:f,blockingEvents:p,isDefault:_}=c(e);s(i,p,d);let v=await t.fs.readFile(d),w=v?.trim()?S(v,d):{},T={...C(w,u.key,d)};for(let[e,t]of Object.entries(T))g(t,h(i,e),d);for(let[n,r]of[[f,!1],[p,!0]])for(let a of n){let n=h(i,a);T[a]=x(T[a]??[],n,i,m(e.executablePath,i,t,r,a),b(i,a,r))}if(`legacyEvents`in a[i])for(let e of a[i].legacyEvents){let t=T[e];if(t){let n=y(t,h(i,e));n.length?T[e]=n:delete T[e]}}if(_)for(let e of o(i).unsafeEvents){let t=T[e];if(!t)continue;let n=y(t,h(i,e));n.length?T[e]=n:delete T[e]}Object.keys(T).length?w[u.key]=T:delete w[u.key],u.version&&w.version==null&&(w.version=1);let E=[];return await n(t.fs,d,`${JSON.stringify(w,null,2)}\n`,E),{host:i,path:d,events:[...f],blockingEvents:[...p],files:E}}async function T(e,r,i){let o=l[e],s=o.path(r),c=[],u=(t,n=[])=>({host:e,path:s,removedEvents:[],remainingEvents:n,files:c,reason:t}),d=await r.fs.readFile(s);if(d===null)return u(`file-missing`);if(!d.trim())return u(`not-installed`);let f=S(d,s),p=C(f,o.key,s);if(!p)return u(`not-installed`);let m={...p};for(let[t,n]of Object.entries(m))g(n,h(e,t),s);let _=[],b=i?.length?i:a[e].events;for(let t of[...b,...`legacyEvents`in a[e]?a[e].legacyEvents:[]]){let n=m[t];if(!n)continue;let r=y(n,h(e,t));JSON.stringify(r)!==JSON.stringify(n)&&(_.push(t),r.length?m[t]=r:delete m[t])}let x=v(e,m);if(!_.length)return u(`not-installed`,x);Object.keys(m).length?f[o.key]=m:delete f[o.key];let w=Object.keys(f).some(e=>e!==o.key&&!(e===`version`&&o.version&&f.version===1));return!Object.keys(m).length&&!w&&!o.neverDelete?await t(r.fs,s,c):await n(r.fs,s,`${JSON.stringify(f,null,2)}\n`,c),{host:e,path:s,removedEvents:_,remainingEvents:x,files:c}}async function E(e,t){let n=e,r=l[n],o=r.path(t),s={host:e,installed:!1,usable:!1,path:o,layout:`none`,events:[],blockingEvents:[],needsMigration:!1};try{let i=await t.fs.readFile(o);if(!i?.trim())return s;let c=C(S(i,o),r.key,o)??{};for(let[e,t]of Object.entries(c))g(t,h(n,e),o);let l=v(n,c),d=`legacyEvents`in a[e]?a[e].legacyEvents:[];return{...s,installed:l.length>0,usable:l.length>0,layout:l.length?`file`:`none`,events:l,blockingEvents:l.filter(e=>_(c[e],h(n,e)).some(e=>e.endsWith(u))),needsMigration:l.some(e=>d.includes(e))}}catch(e){return{...s,error:i(e)}}}export{u as BLOCKING_SUFFIX,d as HOOK_RUN_MARKER,m as command,E as inspectJsonHooks,w as installJsonHooks,T as uninstallJsonHooks};
|
package/dist/index.d.ts
CHANGED
|
@@ -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 {
|
|
11
|
-
import {
|
|
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{
|
|
1
|
+
import{joinPath as e}from"./config.js";import{HookConfigError as t}from"./hooks/errors.js";import{eventManifest as n}from"./hooks/events.js";import{executableForHook as r,hookClientInfo as i,hookHosts as a}from"./hooks/registry.js";import{validateHookEvents as o}from"./hooks/validate.js";import{installJsonHooks as s,uninstallJsonHooks as c}from"./hooks/writers.js";import{inspectHooks as l}from"./hooks/inspect.js";import{OpenCodeMcpCleanupError as u,OpenCodeMcpLayoutUnknownError as d}from"./mcp/opencode-layout.js";import{allClients as f,antigravityLegacyPath as p,clientCommand as m,clientConfigPath as h,clientConfigPaths as g,clientDisplayName as _,clientExecutablePath as v,clientJSONPath as y,clientType as b,clientsForHost as x,detectClient as S,getClientInstallation as C,hostIdentity as w,hostOf as T,isInstallable as E,isLegacy as D,isSupported as O,legacyClientIds as k}from"./mcp/clients.js";import{hookCapabilities as A,listAgents as j,mcpNamesForHook as M,resolveHookClient as N}from"./agents.js";import{installHooks as P,uninstallHooks as F}from"./hooks/install.js";import{generateConfigEntry as I}from"./mcp/config-entry.js";import{inspectMcpInstall as L,installMcp as R}from"./mcp/installer.js";import{getMcpCliInstallCommand as z}from"./mcp/cli-install-command.js";import{uninstallMcp as B}from"./mcp/uninstaller.js";import{SKILL_MANIFEST_FILE_NAME as V,SKILL_MANIFEST_SCHEMA_VERSION as H}from"./skills/manifest.js";import{allSkillAgents as U,isSkillAgentSupported as W,skillDir as G}from"./skills/skill-dirs.js";import{SkillConflictError as K,SkillUnsupportedManifestError as q,inspectSkillStatus as J,installSkill as Y,uninstallSkill as X}from"./skills/install.js";export{t as HookConfigError,u as OpenCodeMcpCleanupError,d as OpenCodeMcpLayoutUnknownError,V as SKILL_MANIFEST_FILE_NAME,H as SKILL_MANIFEST_SCHEMA_VERSION,K as SkillConflictError,q as SkillUnsupportedManifestError,f as allClients,U as allSkillAgents,p as antigravityLegacyPath,m as clientCommand,h as clientConfigPath,g as clientConfigPaths,_ as clientDisplayName,v as clientExecutablePath,y as clientJSONPath,b as clientType,x as clientsForHost,S as detectClient,n as eventManifest,r as executableForHook,I as generateConfigEntry,C as getClientInstallation,z as getMcpCliInstallCommand,A as hookCapabilities,i as hookClientInfo,a as hookHosts,w as hostIdentity,T as hostOf,l as inspectHooks,L as inspectMcpInstall,J as inspectSkillStatus,P as installHooks,s as installJsonHooks,R as installMcp,Y as installSkill,E as isInstallable,D as isLegacy,W as isSkillAgentSupported,O as isSupported,e as joinPath,k as legacyClientIds,j as listAgents,M as mcpNamesForHook,N as resolveHookClient,G as skillDir,F as uninstallHooks,c as uninstallJsonHooks,B as uninstallMcp,X as uninstallSkill,o as validateHookEvents};
|
package/dist/mcp/clients.d.ts
CHANGED
|
@@ -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
|
|
39
|
-
declare function
|
|
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 };
|
package/dist/mcp/clients.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{joinPath as e}from"../config.js";import{findAtPointer as t}from"./jsonc-edit.js";import{
|
|
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
|
|
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 };
|
package/dist/mcp/config-entry.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
function
|
|
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};
|
package/dist/mcp/index.d.ts
CHANGED
|
@@ -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{
|
|
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};
|
package/dist/mcp/installer.d.ts
CHANGED
|
@@ -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'
|
|
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
|
-
/**
|
|
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
|
|
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
|
|
54
|
-
*
|
|
55
|
-
*
|
|
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
|
-
*
|
|
59
|
-
*
|
|
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
|
package/dist/mcp/installer.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{addAtPointer as
|
|
2
|
-
`)?r:`${r}\n`,a=n===void 0?``:
|
|
3
|
-
`)?a:`${a}\n`}`}`}function
|
|
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};
|
package/dist/mcp/jsonc-edit.d.ts
CHANGED
|
@@ -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 };
|
package/dist/mcp/jsonc-edit.js
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
1
|
import{applyEdits as e,findNodeAtLocation as t,getNodeValue as n,modify as r,parseTree as i,visit as a}from"jsonc-parser";const o={tabSize:2,insertSpaces:!0,eol:`
|
|
2
|
-
`},s={allowTrailingComma:!0};function c(e){return e.startsWith(``)?{bom:``,body:e.slice(1)}:{bom:``,body:e}}function l(e){let t=[];return i(c(e).body,t,s),t.length===0}function u(e,t){if(!l(e))throw Error(`invalid JSON in ${t}`)}function d(e
|
|
3
|
-
|
|
2
|
+
`},s={allowTrailingComma:!0};function c(e){return e.startsWith(``)?{bom:``,body:e.slice(1)}:{bom:``,body:e}}function l(e){let t=[];return i(c(e).body,t,s),t.length===0}function u(e,t){if(!l(e))throw Error(`invalid JSON in ${t}`)}function d(e,t){return e.lastIndexOf(`
|
|
3
|
+
`,t-1)+1}function f(e,t){let n=e.indexOf(`
|
|
4
|
+
`,t);return n<0?e.length:n}function p(e,t){let n=f(e,t);return n>t&&e[n-1]===`\r`?n-1:n}function m(e,t,n=`
|
|
5
|
+
`){return JSON.stringify(e,null,2).replace(/\n/g,`${n}${t}`)}function h(t,n,r,i){let a=n.children??[],o=a[a.length-1],s=o.offset+o.length,c=n.offset+n.length-1,l=E(t,s,c),u=t.slice(d(t,o.offset),o.offset),f=p(t,l??s),h=t.includes(`\r
|
|
6
|
+
`)?`\r
|
|
7
|
+
`:`
|
|
8
|
+
`,g=`${JSON.stringify(r)}: `;if(!/^\s*$/.test(u)||f>=c)return e(t,[{offset:s,length:0,content:`, ${g}${JSON.stringify(i)}`}]);let _=`${u}${g}${m(i,u,h)}`;return e(t,l===void 0?[{offset:s,length:0,content:`,`},{offset:f,length:0,content:`${h}${_}`}]:[{offset:f,length:0,content:`${h}${_},`}])}function g(t,r,i){if(JSON.stringify(n(r))===JSON.stringify(i))return t;let a=t.slice(d(t,r.offset),r.offset).match(/^\s*/)?.[0]??``;return e(t,[{offset:r.offset,length:r.length,content:m(i,a)}])}function _(e,n,r){let a=i(e,void 0,s);if(!a)return;let o=x(a,n),c=a,l=0;for(;l<o.length;l++){let e=t(c,[o[l]]);if(!e)break;c=e}if(l===o.length)return g(e,c,r);if(c.type!==`object`||!c.children?.length)return;let u=o.slice(l+1).reduceRight((e,t)=>({[t]:e}),r);return v(h(e,c,String(o[l]),u),n,r)}function v(e,r,a){let o=i(e,void 0,s),c=o?t(o,x(o,r)):void 0;return c&&JSON.stringify(n(c))===JSON.stringify(a)?e:void 0}function y(t,n){let r=n.parent,i=r?.children??[],a=i.indexOf(n),o=n.offset+n.length,s=r?r.offset+r.length-1:t.length,c=E(t,o,a<i.length-1?i[a+1].offset:s),l=[];if(c===void 0&&a>0){let e=i[a-1],r=E(t,e.offset+e.length,n.offset);r!==void 0&&l.push({offset:r,length:1,content:``})}let u=c===void 0?o:c+1,p=d(t,n.offset),m=f(t,u),h=/^\s*$/.test(t.slice(p,n.offset))&&/^\s*(\/\/[^\n]*)?$/.test(t.slice(u,m))&&m<s;return l.push(h?{offset:p,length:m+1-p,content:``}:{offset:n.offset,length:u-n.offset,content:``}),e(t,l.sort((e,t)=>e.offset-t.offset))}function b(e){return e?e.split(`/`).slice(1).map(e=>e.replace(/~1/g,`/`).replace(/~0/g,`~`)):[]}function x(e,n){let r=[],i=e;for(let e of n){let n=i?.type===`array`&&/^(0|[1-9]\d*)$/.test(e)?Number(e):e;r.push(n),i=i?t(i,[n]):void 0}return r}function S(e,r){let a=[],o=i(c(e).body,a,s);if(!o||a.length>0)return;let l=t(o,x(o,b(r)));return l?n(l):void 0}function C(n,a,s,l,d={}){u(n,l);let{bom:f,body:p}=c(n),m=b(a);if(d.preserveSiblings){let e=_(p,m,s);if(e!==void 0)return f+e}let h=p;for(let n=1;n<m.length;n++){let a=i(h),s=a?x(a,m.slice(0,n)):m.slice(0,n);a&&t(a,s)||(h=e(h,r(h,s,{},{formattingOptions:o})))}let g=i(h),v=g?x(g,m):m;return f+e(h,r(h,v,s,{formattingOptions:o}))}function w(n,a,c={}){let l=i(n,void 0,s),u=l?x(l,a):a,d=l?t(l,u):void 0;if(!(a.length===0||!l||!d))return d.parent?.type===`array`&&typeof u.at(-1)==`number`?T(n,d.parent,d):c.preserveSiblings&&d.parent?.type===`property`?y(n,d.parent):D(e(n,r(n,u,void 0,{formattingOptions:o})))}function T(t,n,r){let i=n.children??[],a=i.indexOf(r),o=[{offset:r.offset,length:r.length,content:``}];if(a<i.length-1){let e=E(t,r.offset+r.length,i[a+1].offset);e!==void 0&&o.push({offset:e,length:1,content:``})}else if(a>0){let e=i[a-1],n=E(t,e.offset+e.length,r.offset);n!==void 0&&o.push({offset:n,length:1,content:``})}else{let e=E(t,r.offset+r.length,n.offset+n.length-1);e!==void 0&&o.push({offset:e,length:1,content:``})}return e(t,o.sort((e,t)=>e.offset-t.offset))}function E(e,t,n){for(let r=t;r<n;r++)if(e.startsWith(`//`,r))for(;r<n&&e[r]!==`
|
|
9
|
+
`;)r++;else if(e.startsWith(`/*`,r)){let t=e.indexOf(`*/`,r+2);if(t<0||t>=n)return;r=t+1}else if(e[r]===`,`)return r}function D(e){let t={justOpened:!1},n=()=>{t.justOpened=!1};a(e,{onObjectBegin:()=>{t.justOpened=!0},onSeparator:(e,r)=>{t.justOpened&&e===`,`&&(t.commaOffset??=r),n()},onObjectProperty:n,onObjectEnd:n},s);let{commaOffset:r}=t;return r===void 0?e:e.slice(0,r)+e.slice(r+1)}function O(e,t,n,r={}){u(e,n);let{bom:i,body:a}=c(e),o=w(a,b(t),r);return o===void 0?{text:e,removed:!1}:{text:i+o,removed:!0}}function k(e,t,n,r={}){u(e,n);let{bom:i,body:a}=c(e),o=b(t),s=w(a,o,r);if(s===void 0)return{text:e,removed:!1};let l=o.slice(0,-1);return l.length>0&&A(s,l)&&(s=w(s,l,r)??s),{text:i+s,removed:!0}}function A(e,n){let r=i(e,void 0,s);if(!r)return!1;let a=t(r,x(r,n));return!a||a.type!==`object`?!1:(a.children?.length??0)===0}export{C as addAtPointer,u as assertValidJsonc,S as findAtPointer,l as isValidJsonc,O as removeAtPointer,k as removeAtPointerAndEmptyParent};
|
|
@@ -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};
|