@proofofwork-agency/toolpin 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/CONTRIBUTING.md +117 -0
  2. package/LICENSE +183 -0
  3. package/README.md +323 -0
  4. package/SECURITY.md +61 -0
  5. package/action.yml +134 -0
  6. package/dist/canonicalJson.js +38 -0
  7. package/dist/capabilities.js +139 -0
  8. package/dist/ci.js +26 -0
  9. package/dist/cli.js +1843 -0
  10. package/dist/clientSupport.js +76 -0
  11. package/dist/codexToml.js +213 -0
  12. package/dist/config.js +337 -0
  13. package/dist/constants.js +3 -0
  14. package/dist/continueYaml.js +76 -0
  15. package/dist/doctor.js +163 -0
  16. package/dist/install.js +191 -0
  17. package/dist/installed.js +405 -0
  18. package/dist/integrity.js +14 -0
  19. package/dist/inventory.js +169 -0
  20. package/dist/packageIntegrity.js +153 -0
  21. package/dist/plan.js +595 -0
  22. package/dist/policy.js +310 -0
  23. package/dist/registry.js +1610 -0
  24. package/dist/runtimeAdvisory.js +80 -0
  25. package/dist/safeFetch.js +157 -0
  26. package/dist/sarif.js +162 -0
  27. package/dist/scan.js +113 -0
  28. package/dist/search.js +44 -0
  29. package/dist/secrets.js +165 -0
  30. package/dist/signing.js +146 -0
  31. package/dist/tester.js +240 -0
  32. package/dist/trust.js +528 -0
  33. package/dist/tui/app.js +1731 -0
  34. package/dist/tui/command.js +50 -0
  35. package/dist/tui/configSnippet.js +11 -0
  36. package/dist/tui/constants.js +37 -0
  37. package/dist/tui/format.js +31 -0
  38. package/dist/tui/installedState.js +23 -0
  39. package/dist/tui/layout.js +65 -0
  40. package/dist/tui/selectors.js +282 -0
  41. package/dist/tui/types.js +1 -0
  42. package/dist/tui/ui/trust.js +77 -0
  43. package/dist/tui/views/installed.js +82 -0
  44. package/dist/tui/views/panels.js +637 -0
  45. package/dist/tui.js +12 -0
  46. package/dist/types.js +1 -0
  47. package/dist/verificationTrust.js +103 -0
  48. package/dist/verify.js +537 -0
  49. package/dist/version.js +1 -0
  50. package/dist/versions.js +127 -0
  51. package/docs/assets/readme/terminal-demo.svg +174 -0
  52. package/docs/assets/readme/tui-browse-overview.jpg +0 -0
  53. package/docs/assets/readme/tui-config-preview.jpg +0 -0
  54. package/docs/assets/readme/tui-help.jpg +0 -0
  55. package/docs/assets/readme/tui-installed-inventory.jpg +0 -0
  56. package/docs/how-to/catch-drift-in-ci.md +189 -0
  57. package/docs/how-to/custom-registries.md +156 -0
  58. package/docs/how-to/toolpin-curated-registry.md +153 -0
  59. package/package.json +76 -0
  60. package/registry/README.md +92 -0
  61. package/registry/v0/servers +115 -0
@@ -0,0 +1,76 @@
1
+ export const TOOLPIN_CLIENT_SUPPORT_META = "dev.toolpin/clientSupport";
2
+ export function clientSupportBlock(server) {
3
+ const value = server.raw._meta?.[TOOLPIN_CLIENT_SUPPORT_META] ?? server.registryMeta?.[TOOLPIN_CLIENT_SUPPORT_META];
4
+ if (!isRecord(value))
5
+ return undefined;
6
+ const defaultStatus = statusValue(value.default);
7
+ if (!defaultStatus)
8
+ return undefined;
9
+ const clientsValue = isRecord(value.clients) ? value.clients : {};
10
+ const clients = {};
11
+ for (const [client, entry] of Object.entries(clientsValue)) {
12
+ if (!isRecord(entry))
13
+ continue;
14
+ const status = statusValue(entry.status);
15
+ if (!status)
16
+ continue;
17
+ clients[client] = {
18
+ status,
19
+ installMode: typeof entry.installMode === "string" ? entry.installMode : undefined,
20
+ requirements: stringArray(entry.requirements),
21
+ setupCommands: stringArray(entry.setupCommands),
22
+ notes: typeof entry.notes === "string" ? entry.notes : undefined,
23
+ };
24
+ }
25
+ return { default: defaultStatus, clients };
26
+ }
27
+ export function clientSupportFor(server, client) {
28
+ const support = clientSupportBlock(server);
29
+ if (!support)
30
+ return undefined;
31
+ return support.clients[client] ?? { status: support.default };
32
+ }
33
+ export function assertToolPinInstallableForClient(server, client) {
34
+ const support = clientSupportFor(server, client);
35
+ if (!support || support.status === "toolpin-installable")
36
+ return;
37
+ throw new Error(clientSupportSkipReason(server, client, support));
38
+ }
39
+ export function installableClientsForServer(server, clients) {
40
+ const installable = [];
41
+ const skipped = [];
42
+ for (const client of clients) {
43
+ const support = clientSupportFor(server, client);
44
+ if (!support || support.status === "toolpin-installable") {
45
+ installable.push(client);
46
+ continue;
47
+ }
48
+ skipped.push({
49
+ client,
50
+ status: support.status,
51
+ reason: clientSupportSkipReason(server, client, support),
52
+ });
53
+ }
54
+ return { clients: installable, skipped };
55
+ }
56
+ export function clientSupportSkipReason(server, client, support = clientSupportFor(server, client)) {
57
+ if (!support || support.status === "toolpin-installable")
58
+ return `${server.name}@${server.version} is ToolPin-installable for ${client}.`;
59
+ if (support.status === "external-setup") {
60
+ const commands = support.setupCommands?.length ? ` Setup: ${support.setupCommands.join("; ")}` : "";
61
+ const notes = support.notes ? ` ${support.notes}` : "";
62
+ return `${server.name}@${server.version} supports ${client} through external setup, not ToolPin direct install.${notes}${commands}`;
63
+ }
64
+ return `${server.name}@${server.version} is not supported for ${client} by this ToolPin curated entry.`;
65
+ }
66
+ function statusValue(value) {
67
+ return value === "toolpin-installable" || value === "external-setup" || value === "unsupported" ? value : undefined;
68
+ }
69
+ function stringArray(value) {
70
+ if (!Array.isArray(value))
71
+ return undefined;
72
+ return value.filter((entry) => typeof entry === "string" && entry.length > 0);
73
+ }
74
+ function isRecord(value) {
75
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
76
+ }
@@ -0,0 +1,213 @@
1
+ export function codexTomlFromClientConfig(config) {
2
+ const servers = codexServers(config);
3
+ return Object.entries(servers).map(([serverName, serverConfig]) => serverToToml(serverName, serverConfig)).join("\n\n");
4
+ }
5
+ export function mergeCodexToml(existing, incoming) {
6
+ const servers = codexServers(incoming);
7
+ if (Object.keys(servers).length === 0)
8
+ return existing;
9
+ let next = existing;
10
+ for (const serverName of Object.keys(servers)) {
11
+ next = removeServerTables(next, serverName).toml;
12
+ }
13
+ const prefix = next.trimEnd();
14
+ const fragment = codexTomlFromClientConfig(incoming);
15
+ return `${prefix ? `${prefix}\n\n` : ""}${fragment}\n`;
16
+ }
17
+ export function removeCodexServerToml(existing, serverName) {
18
+ const result = removeServerTables(existing, serverName);
19
+ if (!result.removed)
20
+ return existing;
21
+ return result.toml ? `${result.toml}\n` : "";
22
+ }
23
+ export function readCodexServerConfig(existing, serverName) {
24
+ const config = {};
25
+ let currentPath = [];
26
+ let found = false;
27
+ for (const rawLine of existing.split(/\r?\n/)) {
28
+ const line = rawLine.trim();
29
+ if (!line || line.startsWith("#"))
30
+ continue;
31
+ if (line.startsWith("[") && line.endsWith("]")) {
32
+ currentPath = parseTomlPath(line.slice(1, -1).trim());
33
+ if (currentPath[0] === "mcp_servers" && currentPath[1] === serverName)
34
+ found = true;
35
+ continue;
36
+ }
37
+ if (currentPath[0] !== "mcp_servers" || currentPath[1] !== serverName)
38
+ continue;
39
+ const equalIndex = line.indexOf("=");
40
+ if (equalIndex < 0)
41
+ continue;
42
+ const key = parseTomlKey(line.slice(0, equalIndex).trim());
43
+ const value = parseTomlValue(line.slice(equalIndex + 1).trim());
44
+ if (!key || value === undefined)
45
+ continue;
46
+ const nested = currentPath.slice(2);
47
+ let target = config;
48
+ for (const segment of nested) {
49
+ const existingValue = target[segment];
50
+ if (!isPlainObject(existingValue))
51
+ target[segment] = {};
52
+ target = target[segment];
53
+ }
54
+ target[key] = value;
55
+ }
56
+ return found ? config : undefined;
57
+ }
58
+ function codexServers(config) {
59
+ const root = asRecord(config);
60
+ const servers = asRecord(root.mcp_servers);
61
+ return Object.fromEntries(Object.entries(servers).flatMap(([serverName, value]) => {
62
+ const serverConfig = asRecord(value);
63
+ return Object.keys(serverConfig).length > 0 ? [[serverName, serverConfig]] : [];
64
+ }));
65
+ }
66
+ function serverToToml(serverName, config) {
67
+ const table = `mcp_servers.${tomlKey(serverName)}`;
68
+ const scalarLines = [];
69
+ const nestedTables = [];
70
+ for (const [key, value] of Object.entries(config)) {
71
+ if (value === undefined)
72
+ continue;
73
+ if (isPlainObject(value)) {
74
+ const entries = Object.entries(value).filter((entry) => isTomlValue(entry[1]));
75
+ if (entries.length > 0) {
76
+ nestedTables.push([`[${table}.${tomlKey(key)}]`, ...entries.map(([childKey, childValue]) => `${tomlKey(childKey)} = ${tomlValue(childValue)}`)].join("\n"));
77
+ }
78
+ }
79
+ else if (isTomlValue(value)) {
80
+ scalarLines.push(`${tomlKey(key)} = ${tomlValue(value)}`);
81
+ }
82
+ }
83
+ return [`[${table}]`, ...scalarLines, ...nestedTables].join("\n");
84
+ }
85
+ function removeServerTables(existing, serverName) {
86
+ // Codex's documented writer uses [mcp_servers.<name>] headers; preserve unrelated TOML forms.
87
+ const lines = existing.split(/\r?\n/);
88
+ const kept = [];
89
+ let skipping = false;
90
+ let removed = false;
91
+ for (const line of lines) {
92
+ const header = line.trim();
93
+ if (header.startsWith("[") && header.endsWith("]")) {
94
+ skipping = isServerTableHeader(header, serverName);
95
+ if (skipping)
96
+ removed = true;
97
+ if (!skipping)
98
+ kept.push(line);
99
+ continue;
100
+ }
101
+ if (!skipping)
102
+ kept.push(line);
103
+ }
104
+ return { toml: kept.join("\n").trimEnd(), removed };
105
+ }
106
+ function isServerTableHeader(header, serverName) {
107
+ const tableName = header.slice(1, -1).trim();
108
+ const quoted = tomlKey(serverName);
109
+ const keys = [quoted];
110
+ if (isBareKey(serverName))
111
+ keys.push(serverName);
112
+ return keys.some((key) => {
113
+ const prefix = `mcp_servers.${key}`;
114
+ return tableName === prefix || tableName.startsWith(`${prefix}.`);
115
+ });
116
+ }
117
+ function isTomlValue(value) {
118
+ return typeof value === "string" || typeof value === "number" || typeof value === "boolean" || (Array.isArray(value) && value.every((entry) => typeof entry === "string"));
119
+ }
120
+ function tomlValue(value) {
121
+ if (typeof value === "string")
122
+ return JSON.stringify(value);
123
+ if (typeof value === "number")
124
+ return Number.isFinite(value) ? String(value) : JSON.stringify(String(value));
125
+ if (typeof value === "boolean")
126
+ return String(value);
127
+ return `[${value.map((entry) => JSON.stringify(entry)).join(", ")}]`;
128
+ }
129
+ function tomlKey(key) {
130
+ return isBareKey(key) ? key : JSON.stringify(key);
131
+ }
132
+ function parseTomlPath(value) {
133
+ const keys = [];
134
+ let current = "";
135
+ let quoted = false;
136
+ let escaped = false;
137
+ for (const char of value) {
138
+ if (quoted) {
139
+ current += char;
140
+ if (escaped) {
141
+ escaped = false;
142
+ }
143
+ else if (char === "\\") {
144
+ escaped = true;
145
+ }
146
+ else if (char === '"') {
147
+ quoted = false;
148
+ }
149
+ continue;
150
+ }
151
+ if (char === ".") {
152
+ keys.push(parseTomlKey(current.trim()) ?? current.trim());
153
+ current = "";
154
+ }
155
+ else {
156
+ current += char;
157
+ if (char === '"')
158
+ quoted = true;
159
+ }
160
+ }
161
+ if (current.trim())
162
+ keys.push(parseTomlKey(current.trim()) ?? current.trim());
163
+ return keys;
164
+ }
165
+ function parseTomlKey(value) {
166
+ if (!value)
167
+ return undefined;
168
+ if (value.startsWith('"') && value.endsWith('"')) {
169
+ try {
170
+ const parsed = JSON.parse(value);
171
+ return typeof parsed === "string" ? parsed : undefined;
172
+ }
173
+ catch {
174
+ return undefined;
175
+ }
176
+ }
177
+ return value;
178
+ }
179
+ function parseTomlValue(value) {
180
+ if (value.startsWith('"')) {
181
+ try {
182
+ const parsed = JSON.parse(value);
183
+ return typeof parsed === "string" ? parsed : undefined;
184
+ }
185
+ catch {
186
+ return undefined;
187
+ }
188
+ }
189
+ if (value.startsWith("[") && value.endsWith("]")) {
190
+ try {
191
+ const parsed = JSON.parse(value);
192
+ return Array.isArray(parsed) && parsed.every((entry) => typeof entry === "string") ? parsed : undefined;
193
+ }
194
+ catch {
195
+ return undefined;
196
+ }
197
+ }
198
+ if (value === "true")
199
+ return true;
200
+ if (value === "false")
201
+ return false;
202
+ const number = Number(value);
203
+ return Number.isFinite(number) ? number : undefined;
204
+ }
205
+ function isBareKey(key) {
206
+ return /^[A-Za-z0-9_-]+$/.test(key);
207
+ }
208
+ function isPlainObject(value) {
209
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
210
+ }
211
+ function asRecord(value) {
212
+ return isPlainObject(value) ? value : {};
213
+ }
package/dist/config.js ADDED
@@ -0,0 +1,337 @@
1
+ import { assertToolPinInstallableForClient } from "./clientSupport.js";
2
+ export const ALL_CLIENTS = [
3
+ "claude",
4
+ "cursor",
5
+ "vscode",
6
+ "codex",
7
+ "opencode",
8
+ "windsurf",
9
+ "cline",
10
+ "continue",
11
+ "gemini",
12
+ "zed",
13
+ "roo",
14
+ "generic",
15
+ ];
16
+ export const PROJECT_CLIENTS = ["claude", "cursor", "vscode", "codex", "opencode", "gemini", "roo"];
17
+ export const GLOBAL_CLIENTS = ["cursor", "vscode", "codex", "opencode", "windsurf", "cline", "continue", "gemini"];
18
+ export function exportClientConfig(server, client) {
19
+ assertToolPinInstallableForClient(server, client);
20
+ const notes = [];
21
+ const launch = selectLaunchTarget(server);
22
+ if (!launch) {
23
+ throw new Error(`No package or remote launch target found for ${server.name}@${server.version}`);
24
+ }
25
+ if (launch.kind === "remote") {
26
+ notes.push("Remote server selected; no local runtime install is required.");
27
+ return {
28
+ client,
29
+ serverName: server.name,
30
+ config: wrapClientConfig(client, server.name, {
31
+ type: launch.remote.type,
32
+ url: launch.remote.url,
33
+ headers: headersToInputs(launch.remote, client),
34
+ }),
35
+ notes,
36
+ };
37
+ }
38
+ const localConfig = packageToLocalConfig(launch.pkg, notes, client);
39
+ return {
40
+ client,
41
+ serverName: server.name,
42
+ config: wrapClientConfig(client, server.name, localConfig),
43
+ notes,
44
+ };
45
+ }
46
+ export function selectLaunchTarget(server) {
47
+ const remotes = server.raw.remotes ?? [];
48
+ const packages = server.raw.packages ?? [];
49
+ const streamableRemote = remotes.find((remote) => remote.type === "streamable-http");
50
+ if (streamableRemote)
51
+ return { kind: "remote", remote: streamableRemote };
52
+ if (remotes[0])
53
+ return { kind: "remote", remote: remotes[0] };
54
+ const preferredPackage = packages.find((pkg) => pkg.registryType === "oci") ??
55
+ packages.find((pkg) => pkg.registryType === "mcpb") ??
56
+ packages[0];
57
+ return preferredPackage ? { kind: "package", pkg: preferredPackage } : undefined;
58
+ }
59
+ function packageToLocalConfig(pkg, notes, client) {
60
+ const env = environmentToPlaceholders(pkg, client);
61
+ const explicitCommand = typeof pkg.command === "string" ? pkg.command : undefined;
62
+ const explicitArgs = Array.isArray(pkg.args) ? pkg.args.filter((arg) => typeof arg === "string") : undefined;
63
+ if (explicitCommand) {
64
+ notes.push(runtimeRequirementNote(pkg));
65
+ return { command: explicitCommand, args: explicitArgs ?? [], env };
66
+ }
67
+ switch (pkg.registryType) {
68
+ case "npm": {
69
+ const spec = pkg.version ? `${pkg.identifier}@${pkg.version}` : pkg.identifier;
70
+ if (pkg.runtimeHint === "bun") {
71
+ notes.push("Requires Bun and bunx on PATH.");
72
+ return { command: "bunx", args: [spec, ...packageArguments(pkg)], env };
73
+ }
74
+ notes.push("Requires Node.js and npm/npx on PATH.");
75
+ return { command: "npx", args: ["-y", spec, ...packageArguments(pkg)], env };
76
+ }
77
+ case "pypi": {
78
+ const spec = pkg.version ? `${pkg.identifier}==${pkg.version}` : pkg.identifier;
79
+ notes.push("Requires uv/uvx on PATH.");
80
+ return { command: "uvx", args: [spec], env };
81
+ }
82
+ case "nuget": {
83
+ const spec = pkg.version ? `${pkg.identifier}@${pkg.version}` : pkg.identifier;
84
+ notes.push("Requires .NET SDK dnx support on PATH.");
85
+ return { command: "dnx", args: [spec], env };
86
+ }
87
+ case "cargo": {
88
+ notes.push("Requires a prior `cargo install` and compiled binary on PATH.");
89
+ return { command: pkg.identifier, args: [], env };
90
+ }
91
+ case "oci": {
92
+ notes.push("Requires Docker-compatible runtime. Review mounts and network policy before running.");
93
+ return { command: "docker", args: ["run", "--rm", "-i", ...dockerEnvArgs(pkg), pkg.identifier], env };
94
+ }
95
+ case "mcpb": {
96
+ notes.push("MCPB bundle requires a compatible MCPB installer/runtime.");
97
+ return { command: "mcpb", args: ["run", pkg.identifier], env };
98
+ }
99
+ default:
100
+ notes.push(`Unknown registry type ${pkg.registryType}; generated a placeholder command.`);
101
+ return { command: pkg.identifier, args: packageArguments(pkg), env };
102
+ }
103
+ }
104
+ function packageArguments(pkg) {
105
+ return Array.isArray(pkg.packageArguments) ? pkg.packageArguments.filter((arg) => typeof arg === "string") : [];
106
+ }
107
+ function runtimeRequirementNote(pkg) {
108
+ if (pkg.runtimeHint === "bun")
109
+ return "Requires Bun on PATH.";
110
+ if (pkg.runtimeHint === "docker")
111
+ return "Requires Docker-compatible runtime. Review mounts and network policy before running.";
112
+ if (pkg.runtimeHint)
113
+ return `Requires ${pkg.runtimeHint} on PATH.`;
114
+ return "Uses the command declared by registry metadata; review generated arguments before running.";
115
+ }
116
+ function dockerEnvArgs(pkg) {
117
+ const names = [...new Set((pkg.environmentVariables ?? []).map((variable) => variable.name).filter(Boolean))];
118
+ return names.flatMap((name) => ["-e", name]);
119
+ }
120
+ function environmentToPlaceholders(pkg, client) {
121
+ const env = {};
122
+ for (const variable of pkg.environmentVariables ?? []) {
123
+ env[variable.name] = variable.default ?? placeholderFor(client, variable.name);
124
+ }
125
+ return env;
126
+ }
127
+ function headersToInputs(remote, client) {
128
+ if (!remote.headers?.length)
129
+ return undefined;
130
+ return Object.fromEntries(remote.headers.map((header) => [header.name, placeholderFor(client, header.name)]));
131
+ }
132
+ function wrapClientConfig(client, serverName, config) {
133
+ switch (client) {
134
+ case "vscode":
135
+ return { servers: { [serverName]: config } };
136
+ case "codex":
137
+ return { mcp_servers: { [serverName]: toCodexMcp(config) } };
138
+ case "opencode":
139
+ return { $schema: "https://opencode.ai/config.json", mcp: { [serverName]: toOpenCodeMcp(config) } };
140
+ case "windsurf":
141
+ return { mcpServers: { [serverName]: toWindsurfMcp(config) } };
142
+ case "cline":
143
+ return { mcpServers: { [serverName]: toClineMcp(config) } };
144
+ case "gemini":
145
+ return { mcpServers: { [serverName]: toGeminiMcp(config) } };
146
+ case "continue":
147
+ return { name: "ToolPin Config", version: "1.0.0", schema: "v1", mcpServers: [toContinueMcp(serverName, config)] };
148
+ case "zed":
149
+ return { context_servers: { [serverName]: toZedMcp(config) } };
150
+ case "roo":
151
+ return { mcpServers: { [serverName]: toRooMcp(config) } };
152
+ case "claude":
153
+ case "cursor":
154
+ case "generic":
155
+ default:
156
+ return { mcpServers: { [serverName]: config } };
157
+ }
158
+ }
159
+ export function clientConfigRootKey(client) {
160
+ switch (client) {
161
+ case "opencode":
162
+ return "mcp";
163
+ case "vscode":
164
+ return "servers";
165
+ case "codex":
166
+ return "mcp_servers";
167
+ case "zed":
168
+ return "context_servers";
169
+ case "claude":
170
+ case "cursor":
171
+ case "windsurf":
172
+ case "cline":
173
+ case "gemini":
174
+ case "continue":
175
+ case "roo":
176
+ case "generic":
177
+ default:
178
+ return "mcpServers";
179
+ }
180
+ }
181
+ export function clientsForScope(scope) {
182
+ return scope === "project" ? PROJECT_CLIENTS : GLOBAL_CLIENTS;
183
+ }
184
+ export function isClientName(value) {
185
+ return ALL_CLIENTS.includes(String(value));
186
+ }
187
+ function toCodexMcp(config) {
188
+ if (typeof config.url === "string") {
189
+ const headers = config.headers;
190
+ return {
191
+ url: config.url,
192
+ ...(headers && typeof headers === "object" && !Array.isArray(headers) ? { http_headers: headers } : {}),
193
+ };
194
+ }
195
+ return {
196
+ command: config.command,
197
+ args: config.args,
198
+ env: config.env,
199
+ };
200
+ }
201
+ function toOpenCodeMcp(config) {
202
+ if (typeof config.url === "string") {
203
+ return {
204
+ type: "remote",
205
+ url: config.url,
206
+ enabled: true,
207
+ headers: config.headers,
208
+ };
209
+ }
210
+ const command = typeof config.command === "string" ? config.command : undefined;
211
+ const args = Array.isArray(config.args) ? config.args.filter((arg) => typeof arg === "string") : [];
212
+ return {
213
+ type: "local",
214
+ command: command ? [command, ...args] : args,
215
+ enabled: true,
216
+ environment: config.env,
217
+ };
218
+ }
219
+ function toWindsurfMcp(config) {
220
+ if (typeof config.url === "string") {
221
+ return omitUndefined({
222
+ serverUrl: config.url,
223
+ headers: config.headers,
224
+ });
225
+ }
226
+ return omitUndefined({
227
+ command: config.command,
228
+ args: config.args,
229
+ env: config.env,
230
+ });
231
+ }
232
+ function toClineMcp(config) {
233
+ if (typeof config.url === "string") {
234
+ return omitUndefined({
235
+ type: config.type === "streamable-http" ? "streamableHttp" : config.type,
236
+ url: config.url,
237
+ headers: config.headers,
238
+ disabled: false,
239
+ autoApprove: [],
240
+ });
241
+ }
242
+ return omitUndefined({
243
+ command: config.command,
244
+ args: config.args,
245
+ env: config.env,
246
+ disabled: false,
247
+ autoApprove: [],
248
+ });
249
+ }
250
+ function toGeminiMcp(config) {
251
+ if (typeof config.url === "string") {
252
+ return omitUndefined({
253
+ ...(config.type === "streamable-http" ? { httpUrl: config.url } : { url: config.url }),
254
+ headers: config.headers,
255
+ });
256
+ }
257
+ return omitUndefined({
258
+ command: config.command,
259
+ args: config.args,
260
+ env: config.env,
261
+ });
262
+ }
263
+ function toContinueMcp(serverName, config) {
264
+ if (typeof config.url === "string") {
265
+ return omitUndefined({
266
+ name: serverName,
267
+ type: config.type,
268
+ url: config.url,
269
+ requestOptions: headersRequestOptions(config.headers),
270
+ });
271
+ }
272
+ return omitUndefined({
273
+ name: serverName,
274
+ command: config.command,
275
+ args: config.args,
276
+ env: config.env,
277
+ });
278
+ }
279
+ function toZedMcp(config) {
280
+ if (typeof config.url === "string") {
281
+ return omitUndefined({
282
+ url: config.url,
283
+ headers: config.headers,
284
+ });
285
+ }
286
+ return omitUndefined({
287
+ command: config.command,
288
+ args: config.args,
289
+ env: config.env,
290
+ });
291
+ }
292
+ function toRooMcp(config) {
293
+ if (typeof config.url === "string") {
294
+ return omitUndefined({
295
+ type: config.type,
296
+ url: config.url,
297
+ headers: config.headers,
298
+ disabled: false,
299
+ });
300
+ }
301
+ return omitUndefined({
302
+ command: config.command,
303
+ args: config.args,
304
+ env: config.env,
305
+ disabled: false,
306
+ });
307
+ }
308
+ function placeholderFor(client, name) {
309
+ switch (client) {
310
+ case "windsurf":
311
+ return `\${env:${name}}`;
312
+ case "gemini":
313
+ return `\${${name}}`;
314
+ case "continue":
315
+ return `\${{ secrets.${name} }}`;
316
+ case "roo":
317
+ return `<${name}>`;
318
+ case "cline":
319
+ case "zed":
320
+ case "claude":
321
+ case "cursor":
322
+ case "vscode":
323
+ case "codex":
324
+ case "opencode":
325
+ case "generic":
326
+ default:
327
+ return `<${name}>`;
328
+ }
329
+ }
330
+ function omitUndefined(value) {
331
+ return Object.fromEntries(Object.entries(value).filter(([, child]) => child !== undefined));
332
+ }
333
+ function headersRequestOptions(headers) {
334
+ if (!headers || typeof headers !== "object" || Array.isArray(headers) || Object.keys(headers).length === 0)
335
+ return undefined;
336
+ return { headers };
337
+ }
@@ -0,0 +1,3 @@
1
+ export const DEFAULT_LOCKFILE_PATH = "mcp-lock.json";
2
+ export const DEFAULT_SIGNATURE_PATH = "mcp-lock.sig";
3
+ export const DEFAULT_POLICY_PATH = ".toolpin/policy.json";