@gitkraken/agent-tools 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/README.md +26 -2
- package/dist/config.d.ts +2 -1
- package/dist/index.d.ts +4 -3
- 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 +3 -1
- package/dist/mcp/jsonc-edit.js +1 -1
- 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
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,25 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/) and this
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.2.0] - 2026-10-12
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- OpenCode MCP layout selection (core-tools#164). `McpInstallRequest.openCode` and `generateConfigEntry`/`clientJSONPath` options take an `OpenCodeMcpPolicy`: `{ mode: 'authoritative', layout: 'v1' | 'v2' }` writes and prunes the other layout, `{ mode: 'infer-existing' }` follows the layout the config already uses (v1 when both exist, the entry OpenCode launches) and never prunes. Omitting it keeps the 0.1.1 behavior (v2, v1 pruned). `inspectMcpInstall` results and `installMcp` results report the selected `openCodeLayout`.
|
|
14
|
+
- `inspectMcpInstall` reports `missing` with the new reason `layout-unknown` when `infer-existing` finds no GitKraken entry in either OpenCode layout (including a missing or blank file), the cases where `installMcp` would throw `OpenCodeMcpLayoutUnknownError`. `McpInstallStatus.openCodeDuplicateLocation` is the JSON pointer of the GitKraken entry in the OpenCode layout that was not compared, present whenever both layouts hold one; it is reported only and never pruned by `infer-existing`.
|
|
15
|
+
- `OpenCodeMcpLayoutUnknownError` for `infer-existing` with no GitKraken entry to follow, or in a read-free helper; `OpenCodeMcpCleanupError`, carrying the committed `result`, when the selected entry was written but removing the other layout's entry failed. Cancellation (`AbortError`) is rethrown as-is.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- OpenCode migration is two writes: the selected entry first with the other layout retained, then the removal, after re-reading to confirm the file is unchanged. A v2 write refuses a flat server named `servers` and a non-object `mcp` or `mcp.servers`.
|
|
20
|
+
- `inspectMcpInstall` reports a prunable OpenCode duplicate with the selected entry and location (previously the duplicate's), and an entry only in the other layout as `legacy-location` in either direction.
|
|
21
|
+
- `inspectMcpInstall` reports an OpenCode config that `installMcp` refuses (a non-object root, `mcp` or `mcp.servers`, or a flat server named `servers` under a v2 policy) as `missing` with the new reason `unsafe-config`, instead of `no-entry`.
|
|
22
|
+
- Safe OpenCode migration needs the host `FileSystemPort.writeFile` to leave the previous file intact when it rejects. Custom fields inside a replaced GitKraken entry are not carried across layouts.
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- `uninstallMcp` and `getClientInstallation` no longer treat a `GitKraken` key on a flat OpenCode server named `servers` as a v2 entry, so uninstall leaves it intact.
|
|
27
|
+
|
|
9
28
|
## [0.1.1] - 2026-10-02
|
|
10
29
|
|
|
11
30
|
### Added
|
package/README.md
CHANGED
|
@@ -33,7 +33,7 @@ const config: AgentHostConfig = {
|
|
|
33
33
|
},
|
|
34
34
|
fs: myFileSystemPort, // @gitkraken/shared-tools FileSystemPort
|
|
35
35
|
resolveExecutable: (command) => myPathLookup(command), // optional: a client's binary on PATH
|
|
36
|
-
openCodeMajor: 2, // optional: the installed OpenCode major version (0/undefined = unknown)
|
|
36
|
+
openCodeMajor: 2, // optional: the installed OpenCode major version (0/undefined = unknown); does not select the MCP layout
|
|
37
37
|
};
|
|
38
38
|
```
|
|
39
39
|
|
|
@@ -66,7 +66,31 @@ const removal = await uninstallMcp({ client: 'vscode' }, config);
|
|
|
66
66
|
|
|
67
67
|
`getMcpCliInstallCommand` returns `undefined` when a shortcut cannot do the job: the client has none, a `configPath` is set, the client is Cursor (its config file is always written directly), or Codex's config still holds the legacy `['mcp_servers.GitKraken']` table. `installMcp` and `uninstallMcp` throw `unsupported client` for an unknown id (`installMcp` also for the uninstall-only `gemini`), and throw without writing when a config file does not parse (`inspectMcpInstall` reports `invalid-config` instead). A leading UTF-8 BOM is accepted and kept.
|
|
68
68
|
|
|
69
|
-
Re-running `installMcp` when the entry is already in place writes nothing and reports `change: 'unchanged'`. `inspectMcpInstall` says why an entry is not current (`reason`, and the drifting `key`) and exposes the on-disk `entry`, so a host can carry user-added args forward before reinstalling.
|
|
69
|
+
Re-running `installMcp` when the entry is already in place writes nothing and reports `change: 'unchanged'`. `inspectMcpInstall` says why an entry is not current (`reason`, and the drifting `key`) and exposes the on-disk `entry`, so a host can carry user-added args forward before reinstalling.
|
|
70
|
+
|
|
71
|
+
### OpenCode layouts
|
|
72
|
+
|
|
73
|
+
OpenCode reads MCP servers from `/mcp/<name>` (v1) or `/mcp/servers/<name>` (v2). Omitting `openCode` writes v2 and prunes a v1 entry, as in 0.1.1. A host that probed the installed OpenCode should pass a policy on every OpenCode call (inspect before and after repair, and install). The library never runs the probe or compares versions:
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
// Verified earlier-v1 OpenCode -> v1; verified >= 1.18.24, or a v2 client -> v2.
|
|
77
|
+
const policy = { mode: 'authoritative', layout: 'v1' } as const;
|
|
78
|
+
// Probe failed or unparseable: follow what the config already uses, never prune.
|
|
79
|
+
const unknown = { mode: 'infer-existing' } as const;
|
|
80
|
+
|
|
81
|
+
await installMcp({ ...request, openCode: policy }, config);
|
|
82
|
+
generateConfigEntry('opencode', '/usr/local/bin/gk', ['mcp'], { openCode: policy });
|
|
83
|
+
// { type: 'local', command: ['/usr/local/bin/gk', 'mcp'], enabled: true } (v2 uses `disabled: false`)
|
|
84
|
+
clientJSONPath('opencode', { openCode: policy }); // '/mcp/GitKraken'
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
- `authoritative` writes the selected layout and removes the other layout's GitKraken entry. `infer-existing` follows the layout the config already uses and repairs that entry in place; with both layouts present it selects v1, because OpenCode's compatibility reader retains it over the native entry. It leaves the duplicate alone. It never authorizes pruning, even if you copy the reported `openCodeLayout` elsewhere; pass `infer-existing` unchanged through both inspections and the install.
|
|
88
|
+
- `infer-existing` throws `OpenCodeMcpLayoutUnknownError` when there is no entry to follow, and in the read-free helpers (`generateConfigEntry`, `clientJSONPath`), which cannot read the config. To generate an entry without a probe result, choose a documented fallback yourself, e.g. `{ mode: 'authoritative', layout: 'v1' }`; generating performs no write or cleanup. A non-object GitKraken entry is treated as corruption and refused, not skipped. A malformed policy throws `TypeError`.
|
|
89
|
+
- `inspectMcpInstall` compares the selected layout; an entry found only in the other layout is `drifted` / `legacy-location` with that entry exposed for argv repair, and a current entry with a prunable duplicate is `drifted` / `legacy-location` with the selected entry. With `infer-existing` the compared entry is the v1 one when both layouts are present. `openCodeDuplicateLocation` reports the pointer of the other layout's entry whenever both hold one, in either mode; it is never pruned by `infer-existing`. `infer-existing` with no entry in either layout (including a missing or blank file) is `missing` / `layout-unknown`, the case where `installMcp` would throw `OpenCodeMcpLayoutUnknownError`. Argv merging stays in the host: re-run `inspectMcpInstall` with the effective argv before `installMcp`.
|
|
90
|
+
- A migration writes the selected entry first and the removal second. If the removal fails (or the file changed in between), `installMcp` throws `OpenCodeMcpCleanupError`; `error.result` describes what was committed, with no `removedLocations`. This is not success, since an older reader can still reject a leftover native `servers` object. Run the install again to finish. `AbortError` is rethrown unchanged.
|
|
91
|
+
- Refused without writing: a non-object root, `mcp` or `mcp.servers`; a v2 write where `mcp.servers` is a flat server named `servers` (an object whose `type` or `enabled` is a scalar, array or null). `inspectMcpInstall` reports these same files as `missing` / `unsafe-config`, so a host can tell before installing. Custom fields inside a replaced GitKraken entry are not carried across layouts.
|
|
92
|
+
- Safe migration needs your `FileSystemPort.writeFile` to leave the previous file intact when it rejects (write a temp file, then rename). Your `readFile` must also return exactly the bytes `writeFile` was given (no newline normalization, appended trailing newline, or BOM changes): the migration compares the re-read file against what it wrote before the destructive second write, and a port that does not round-trip turns the migration into `OpenCodeMcpCleanupError` (retrying completes it). The library cannot check this and holds no lock against concurrent writers.
|
|
93
|
+
- `uninstallMcp` ignores the policy and removes both pointers, except that a `GitKraken` key on a flat server named `servers` is not a v2 entry and is left alone; `configPath` confines every read and write to that file.
|
|
70
94
|
|
|
71
95
|
Codex TOML edits preserve surrounding text and comments by splicing table blocks. When an entry needs to change but its valid TOML representation cannot be edited safely this way (for example, a drifted inline or dotted `mcp_servers.GitKraken` assignment), install or uninstall throws an error naming the config path and leaves the file untouched. A semantically current entry remains a no-op even when it uses an unusual representation.
|
|
72
96
|
|
package/dist/config.d.ts
CHANGED
|
@@ -50,7 +50,8 @@ interface AgentHostConfig {
|
|
|
50
50
|
* The installed OpenCode major version, or `undefined`/`0` when unknown —
|
|
51
51
|
* the same sentinel Go's `OpenCodeMajor()` returns. Go probes `opencode
|
|
52
52
|
* --version`; this library never spawns a process, so the host resolves
|
|
53
|
-
* the probe (or reads its own cache) and injects the result.
|
|
53
|
+
* the probe (or reads its own cache) and injects the result. It does not
|
|
54
|
+
* select an MCP layout; pass `McpInstallRequest.openCode` for that.
|
|
54
55
|
*/
|
|
55
56
|
openCodeMajor?: number;
|
|
56
57
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
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
7
|
import { Agent, HookCapability, listAgents, mcpNamesForHook, resolveHookClient } from "./agents.js";
|
|
5
8
|
import { FileChange, FileChangeKind } from "./file-change.js";
|
|
@@ -7,11 +10,9 @@ import { HookInstallResult, HookUninstallResult } from "./hooks/results.js";
|
|
|
7
10
|
import { HookConfigError, HookConfigErrorCode } from "./hooks/errors.js";
|
|
8
11
|
import { HookWriteRequest, installJsonHooks, uninstallJsonHooks } from "./hooks/writers.js";
|
|
9
12
|
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";
|
|
12
13
|
import { getMcpCliInstallCommand } from "./mcp/cli-install-command.js";
|
|
13
14
|
import { McpUninstallFileResult, McpUninstallRequest, McpUninstallResult, uninstallMcp } from "./mcp/uninstaller.js";
|
|
14
15
|
import { SKILL_MANIFEST_FILE_NAME, SKILL_MANIFEST_SCHEMA_VERSION, SkillManifest } from "./skills/manifest.js";
|
|
15
16
|
import { SkillAgent, allSkillAgents, isSkillAgentSupported, skillDir } from "./skills/skill-dirs.js";
|
|
16
17
|
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 };
|
|
18
|
+
export { type Agent, type AgentHostConfig, type AgentHostPaths, type AgentOrigin, type Client, type ClientDetection, type ClientDetectionSource, type ClientInstallation, type ClientNotDetectedReason, type ClientType, type FileChange, type FileChangeKind, type HookCapability, HookConfigError, type HookConfigErrorCode, type HookHost, type HookInstallResult, type HookUninstallResult, type HookWriteRequest, type InstallHooksRequest, type McpConfigEntry, type McpInstallRequest, type McpInstallResult, type McpInstallState, type McpInstallStateReason, type McpInstallStatus, type McpLayoutOptions, type McpUninstallFileResult, type McpUninstallRequest, type McpUninstallResult, OpenCodeMcpCleanupError, type OpenCodeMcpLayout, OpenCodeMcpLayoutUnknownError, type OpenCodeMcpPolicy, SKILL_MANIFEST_FILE_NAME, SKILL_MANIFEST_SCHEMA_VERSION, type SkillAgent, SkillConflictError, type SkillFiles, type SkillInstallAction, type SkillInstallOptions, type SkillInstallResult, type SkillManifest, type SkillOperation, type SkillReason, type SkillState, type SkillStatus, type SkillUninstallAction, type SkillUninstallOptions, type SkillUninstallResult, SkillUnsupportedManifestError, allClients, allSkillAgents, antigravityLegacyPath, clientCommand, clientConfigPath, clientConfigPaths, clientDisplayName, clientExecutablePath, clientJSONPath, clientType, clientsForHost, detectClient, eventManifest, generateConfigEntry, getClientInstallation, getMcpCliInstallCommand, hostIdentity, hostOf, inspectMcpInstall, inspectSkillStatus, installHooks, installJsonHooks, installMcp, installSkill, isInstallable, isLegacy, isSkillAgentSupported, isSupported, joinPath, legacyClientIds, listAgents, mcpNamesForHook, resolveHookClient, skillDir, uninstallHooks, uninstallJsonHooks, uninstallMcp, uninstallSkill };
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{joinPath as e}from"./config.js";import{
|
|
1
|
+
import{joinPath as e}from"./config.js";import{OpenCodeMcpCleanupError as t,OpenCodeMcpLayoutUnknownError as n}from"./mcp/opencode-layout.js";import{allClients as r,antigravityLegacyPath as i,clientCommand as a,clientConfigPath as o,clientConfigPaths as s,clientDisplayName as c,clientExecutablePath as l,clientJSONPath as u,clientType as d,clientsForHost as f,detectClient as p,getClientInstallation as m,hostIdentity as h,hostOf as g,isInstallable as _,isLegacy as v,isSupported as y,legacyClientIds as b}from"./mcp/clients.js";import{listAgents as x,mcpNamesForHook as S,resolveHookClient as C}from"./agents.js";import{HookConfigError as w}from"./hooks/errors.js";import{eventManifest as T}from"./hooks/events.js";import{installJsonHooks as E,uninstallJsonHooks as D}from"./hooks/writers.js";import{installHooks as O,uninstallHooks as k}from"./hooks/install.js";import{generateConfigEntry as A}from"./mcp/config-entry.js";import{inspectMcpInstall as j,installMcp as M}from"./mcp/installer.js";import{getMcpCliInstallCommand as N}from"./mcp/cli-install-command.js";import{uninstallMcp as P}from"./mcp/uninstaller.js";import{SKILL_MANIFEST_FILE_NAME as F,SKILL_MANIFEST_SCHEMA_VERSION as I}from"./skills/manifest.js";import{allSkillAgents as L,isSkillAgentSupported as R,skillDir as z}from"./skills/skill-dirs.js";import{SkillConflictError as B,SkillUnsupportedManifestError as V,inspectSkillStatus as H,installSkill as U,uninstallSkill as W}from"./skills/install.js";export{w as HookConfigError,t as OpenCodeMcpCleanupError,n as OpenCodeMcpLayoutUnknownError,F as SKILL_MANIFEST_FILE_NAME,I as SKILL_MANIFEST_SCHEMA_VERSION,B as SkillConflictError,V as SkillUnsupportedManifestError,r as allClients,L as allSkillAgents,i as antigravityLegacyPath,a as clientCommand,o as clientConfigPath,s as clientConfigPaths,c as clientDisplayName,l as clientExecutablePath,u as clientJSONPath,d as clientType,f as clientsForHost,p as detectClient,T as eventManifest,A as generateConfigEntry,m as getClientInstallation,N as getMcpCliInstallCommand,h as hostIdentity,g as hostOf,j as inspectMcpInstall,H as inspectSkillStatus,O as installHooks,E as installJsonHooks,M as installMcp,U as installSkill,_ as isInstallable,v as isLegacy,R as isSkillAgentSupported,y as isSupported,e as joinPath,b as legacyClientIds,x as listAgents,S as mcpNamesForHook,C as resolveHookClient,z as skillDir,k as uninstallHooks,D as uninstallJsonHooks,P as uninstallMcp,W as uninstallSkill};
|
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 e,
|
|
2
|
-
`)?r:`${r}\n`,a=n===void 0?``:
|
|
3
|
-
`)?a:`${a}\n`}`}`}function
|
|
1
|
+
import{addAtPointer as e,assertValidJsonc as t,findAtPointer as n,isValidJsonc as r,removeAtPointerAndEmptyParent as i}from"./jsonc-edit.js";import{OpenCodeMcpCleanupError as a,OpenCodeMcpLayoutUnknownError as o,openCodeNativeServers as s,openCodeRefusal as c,resolveOpenCodePolicy as l}from"./opencode-layout.js";import{assertValidToml as u,parseTomlSafe as d,tomlDottedPath as f,tomlLiteralKey as p}from"./toml-read.js";import{clientConfigPath as m,clientJSONPath as h,isInstallable as g,openCodeJSONPath as _}from"./clients.js";import{writeFileTracked as v}from"../file-change.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(t,r){let{client:i}=t,a=g(i)?t.configPath??await m(i,r):void 0;if(!a)throw Error(`unsupported client: ${i}`);if(i===`opencode`)return D(t,r,a);let o=y(i,t.executablePath,t.args);if(i===`codex`)return P(t,r,a,o);let s=await r.fs.readFile(a),c=s===null||s.trim()===``?`{}`:s,l=h(i),u=n(c,l),d=s!==null&&I(u)&&H(u,o,{client:i,path:a,location:l}).state===`current`;return{client:i,path:a,change:d?`unchanged`:await v(r.fs,a,e(c,l,o,a),[],{mode:420}),format:`json`,location:l,entry:d?u:o,removedLocations:[]}}async function D(n,r,o){let s=l(n.openCode),u=await r.fs.readFile(o),d=u===null||u.trim()===``?`{}`:u;t(d,o);let f=c(d,k(s),o);if(f!==void 0)throw Error(f);let p=s.mode===`authoritative`?s.layout:O(d,o),m=j(p),h=_(p),g=_(m),b=y(`opencode`,n.executablePath,n.args,{openCode:{mode:`authoritative`,layout:p}}),x=M(d,p),S=u!==null&&I(x)&&H(x,b,{client:`opencode`,path:o,location:h}).state===`current`,C=s.mode===`authoritative`?M(d,m):void 0,w=S?d:e(d,h,b,o),T=C===void 0?void 0:i(w,g,o),E=S?`unchanged`:await v(r.fs,o,w,[],{mode:420}),D=e=>({client:`opencode`,path:o,change:E,format:`json`,location:h,entry:S?x:b,removedLocations:e,openCodeLayout:p});if(T===void 0||!T.removed)return D([]);try{if(await r.fs.readFile(o)!==w)throw Error(`${o} changed while the OpenCode MCP entry was being installed`);let e=await v(r.fs,o,T.text,[],{mode:420});E===`unchanged`&&(E=e)}catch(e){throw A(e)?e:new a(D([]),e)}return D([g])}function O(e,t){let n=N(e);if(!n)throw new o;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`&&s(e)===void 0))return n(e,_(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(e,t,n,r){let i=await t.fs.readFile(n);i!==null&&i.trim()!==``&&u(i,n);let a=h(`codex`),o=i??``,s=d(o),c=f(s,a),l=p(s,a);if(i!==null&&l===void 0&&I(c)&&H(c,r,{client:e.client,path:n,location:a}).state===`current`)return{client:e.client,path:n,change:`unchanged`,format:`toml`,location:a,entry:c,removedLocations:[]};let m,g,_=[];if(l!==void 0){if(!I(l)||c!==void 0&&!I(c))throw Error(`cannot safely migrate Codex MCP entry in ${n}: ambiguous legacy and nested tables`);let e=I(c)&&c.env!==void 0?c.env:l.env;if(e!==void 0&&!I(e))throw Error(`cannot safely migrate Codex MCP entry in ${n}: legacy env is not a table`);if(Object.entries(l).some(([e,t])=>e!==`env`&&I(t))||e&&Object.values(e).some(I))throw Error(`cannot safely migrate Codex MCP entry in ${n}: unsupported legacy subtable`);g=e;let t=S(o,a).text;m=C(c===void 0?t:S(t,a).text,a,F(a,r,e)),_.push(`["${a}"]`)}else m=C(o,a,F(a,r));let y=d(m),w=f(y,a);if(!I(w)||H(w,r,{client:e.client,path:n,location:a}).state!==`current`||p(y,a)!==void 0||l!==void 0&&!b(w.env,g)||!b(x(y),x(s)))throw Error(`cannot safely update Codex MCP entry in ${n}: unsupported TOML representation`);let T=await v(t.fs,n,m,[],{mode:384});return{client:e.client,path:n,change:T,format:`toml`,location:a,entry:w,removedLocations:_}}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:i}=e,a=i===`opencode`?l(e.openCode):void 0,o=a?.mode===`infer-existing`?void 0:a?.layout,s=i===`opencode`?_(o??`v2`):h(i),c=g(i)?e.configPath??await m(i,t):void 0;if(!c)return L({client:i,path:``,location:s},`unsupported-client`);let u={client:i,path:c,location:s,...o&&{openCodeLayout:o}},d=await t.fs.readFile(c);if(d===null)return L(u,a?.mode===`infer-existing`?`layout-unknown`:`file-missing`);if(i===`codex`)return B(d,y(i,e.executablePath,e.args),u);if(d.trim()===``)return L(u,a?.mode===`infer-existing`?`layout-unknown`:`no-entry`);if(!r(d))return L(u,`invalid-config`);if(a)return V(d,e,a,u);let f=n(d,s);if(f===void 0)return L(u,`no-entry`);let p=y(i,e.executablePath,e.args);return I(f)?H(f,p,u):L(u,`entry-not-object`)}function B(e,t,n){if(e.trim()===``)return L(n,`no-entry`);let r=d(e);if(!r)return L(n,`invalid-config`);let i=p(r,n.location);if(i!==void 0)return R(n,i);let a=f(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(c(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:_(n),openCodeLayout:n,...M(e,o)!==void 0&&{openCodeDuplicateLocation:_(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:_(o)},s);let l=s===void 0?r:{...r,openCodeDuplicateLocation:_(o)};if(!I(a))return L(l,`entry-not-object`);let u=H(a,i(n.layout),l);return u.state===`current`&&s!==void 0?R(l,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,8 @@
|
|
|
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;
|
|
4
6
|
/** Reads the value at `pointer` in JSONC `text`, or `undefined` when absent or the document is unparsable. */
|
|
5
7
|
declare function findAtPointer(text: string, pointer: string): unknown;
|
|
6
8
|
/**
|
|
@@ -38,4 +40,4 @@ declare function removeAtPointerAndEmptyParent(text: string, pointer: string, so
|
|
|
38
40
|
removed: boolean;
|
|
39
41
|
};
|
|
40
42
|
//#endregion
|
|
41
|
-
export { addAtPointer, findAtPointer, isValidJsonc, removeAtPointer, removeAtPointerAndEmptyParent };
|
|
43
|
+
export { addAtPointer, assertValidJsonc, findAtPointer, isValidJsonc, removeAtPointer, removeAtPointerAndEmptyParent };
|
package/dist/mcp/jsonc-edit.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
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
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};
|
|
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,u as assertValidJsonc,p as findAtPointer,l as isValidJsonc,y as removeAtPointer,b 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};
|
package/dist/mcp/uninstaller.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{removeAtPointer as e,removeAtPointerAndEmptyParent as t}from"./jsonc-edit.js";import{assertValidToml as n,parseTomlSafe as r,tomlDottedPath as i,tomlLiteralKey as a}from"./toml-read.js";import{
|
|
1
|
+
import{removeAtPointer as e,removeAtPointerAndEmptyParent as t}from"./jsonc-edit.js";import{assertValidToml as n,parseTomlSafe as r,tomlDottedPath as i,tomlLiteralKey as a}from"./toml-read.js";import{clientConfigPaths as o,clientJSONPath as s,isSupported as c,openCodeEntryPointers as l}from"./clients.js";import{writeFileTracked as u}from"../file-change.js";import{tomlSemanticEqual as d,withoutCodexEntry as f}from"./toml-safety.js";import{removeBlock as p,trimTrailingBlankLines as m}from"./toml-splice.js";function h(e,t){return{file:{path:e,change:`unchanged`,removedLocations:[],reason:t}}}async function g(e,t){let{client:n}=e;if(!c(n))throw Error(`unsupported client: ${n}`);let r=e.configPath?[e.configPath]:await o(n,t),i=[];for(let e of r)i.push(await _(n,e,t));let a=[];for(let{file:e,text:n}of i)n!==void 0&&await u(t.fs,e.path,n,[]),a.push(e);return{client:n,removed:a.some(e=>e.removedLocations.length>0),files:a}}async function _(e,t,n){let r=await n.fs.readFile(t);if(r===null)return h(t,`file-missing`);if(r.trim()===``)return h(t,`file-empty`);let{text:i,removedLocations:a}=e===`codex`?y(r,t):v(r,e,t);return a.length===0?h(t,`no-entry`):{file:{path:t,change:`updated`,removedLocations:a},text:i}}function v(n,r,i){let a=r===`opencode`,o=a?l(n):[s(r)],c=n,u=[];for(let n of o){let r=a?t(c,n,i):e(c,n,i);r.removed&&(c=r.text,u.push(n))}return{text:c,removedLocations:u}}function y(e,t){n(e,t);let o=s(`codex`),c=r(e);if(a(c,o)===void 0&&i(c,o)===void 0)return{text:e,removedLocations:[]};let{text:l,removed:u}=p(e,o);if(!u){if(a(c,o)!==void 0||i(c,o)!==void 0)throw Error(`cannot safely remove Codex MCP entry in ${t}: unsupported TOML representation`);return{text:e,removedLocations:[]}}let h=m(l),g=r(h);if(a(g,o)!==void 0||i(g,o)!==void 0)throw Error(`cannot safely remove Codex MCP entry in ${t}: target remains after splice`);if(!g||!d(f(g),f(c)))throw Error(`cannot safely remove Codex MCP entry in ${t}: splice would change another TOML value`);return{text:h,removedLocations:[o]}}export{g as uninstallMcp};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gitkraken/agent-tools",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Host configuration and path helpers for AI coding agent integrations, with an in-memory filesystem for testing.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"author": "Axosoft, LLC dba GitKraken",
|