@oisincoveney/pipeline 3.11.8 → 3.11.9
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/dist/claude-user-config.js +28 -0
- package/dist/cli/program.js +8 -2
- package/dist/codex-config.js +67 -0
- package/dist/install-commands/claude-code.js +3 -6
- package/dist/install-commands/shared.js +10 -2
- package/dist/install-commands.js +75 -10
- package/dist/install-hooks.js +1 -5
- package/dist/install-rules.js +7 -9
- package/dist/mcp/gateway.js +61 -7
- package/package.json +1 -1
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { applyJsonEdit, ensureTrailingNewline, formatJson, parseJsonRecord, setIfMissing } from "./json-config-merge.js";
|
|
2
|
+
//#region src/claude-user-config.ts
|
|
3
|
+
function mergeClaudeUserConfig(currentText, projection) {
|
|
4
|
+
if (currentText === void 0) return {
|
|
5
|
+
content: formatJson(projection),
|
|
6
|
+
ok: true
|
|
7
|
+
};
|
|
8
|
+
const parsed = parseJsonRecord(currentText);
|
|
9
|
+
if (!parsed.ok) return parsed;
|
|
10
|
+
return {
|
|
11
|
+
content: ensureTrailingNewline(Object.entries(projection.mcpServers ?? {}).reduce((nextContent, [name, server]) => setIfMissing(nextContent, parsed.value, ["mcpServers", name], server), currentText)),
|
|
12
|
+
ok: true
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
function replaceClaudeUserMcpServers(currentText, projection) {
|
|
16
|
+
if (currentText === void 0) return {
|
|
17
|
+
content: formatJson(projection),
|
|
18
|
+
ok: true
|
|
19
|
+
};
|
|
20
|
+
const parsed = parseJsonRecord(currentText);
|
|
21
|
+
if (!parsed.ok) return parsed;
|
|
22
|
+
return {
|
|
23
|
+
content: ensureTrailingNewline(applyJsonEdit(currentText, ["mcpServers"], projection.mcpServers ?? {})),
|
|
24
|
+
ok: true
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
//#endregion
|
|
28
|
+
export { mergeClaudeUserConfig, replaceClaudeUserMcpServers };
|
package/dist/cli/program.js
CHANGED
|
@@ -304,7 +304,12 @@ function createCliProgram(options = {}) {
|
|
|
304
304
|
const config = loadPipelineConfig(process.env.PIPELINE_TARGET_PATH ?? process.cwd(), { allowMissingLintFileReferences: true });
|
|
305
305
|
console.log(renderGatewayConfig(config));
|
|
306
306
|
});
|
|
307
|
-
gatewayCommand.command("configure-host").description("Rewrite host MCP config to the singleton pipeline gateway").addOption(new Option$1("--host <host>", "host config to update").choices([
|
|
307
|
+
gatewayCommand.command("configure-host").description("Rewrite host MCP config to the singleton pipeline gateway").addOption(new Option$1("--host <host>", "host config to update").choices([
|
|
308
|
+
"all",
|
|
309
|
+
"opencode",
|
|
310
|
+
"claude-code",
|
|
311
|
+
"codex"
|
|
312
|
+
]).default("all").argParser(parseGatewayHost)).addOption(new Option$1("--scope <scope>", "config scope to update").choices(["project", "global"]).default("project").argParser(parseGatewayHostScope)).action((flags) => {
|
|
308
313
|
const cwd = process.env.PIPELINE_TARGET_PATH ?? process.cwd();
|
|
309
314
|
const result = configureGatewayHosts(loadPipelineConfig(cwd, { allowMissingLintFileReferences: true }), {
|
|
310
315
|
cwd,
|
|
@@ -343,7 +348,8 @@ function createCliProgram(options = {}) {
|
|
|
343
348
|
program.command("install-commands").description("Install generated slash-command adapters into per-machine host dirs (~/.claude, ~/.config/opencode, ~/.codex)").addOption(new Option$1("--host <host>", "host command set to install").choices([
|
|
344
349
|
"all",
|
|
345
350
|
"opencode",
|
|
346
|
-
"claude-code"
|
|
351
|
+
"claude-code",
|
|
352
|
+
"codex"
|
|
347
353
|
]).default("all").argParser(parseCommandHost)).option("--dry-run", "show planned changes without writing files").option("--check", "fail if generated command files are missing or stale").option("--force", "overwrite manually edited command files").action(async (flags) => {
|
|
348
354
|
const result = await installCommands({
|
|
349
355
|
...flags,
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { ensureTrailingNewline } from "./json-config-merge.js";
|
|
2
|
+
//#region src/codex-config.ts
|
|
3
|
+
const PIPELINE_GATEWAY_SECTION_HEADERS = ["[mcp_servers.pipeline-gateway]", "[mcp_servers.pipeline-gateway.env_http_headers]"];
|
|
4
|
+
const CODEX_FEATURES_SECTION_HEADER = "[features]";
|
|
5
|
+
const CODEX_HOOKS_FEATURE = "hooks";
|
|
6
|
+
function mergeCodexConfig(currentText, projection) {
|
|
7
|
+
return ensureTrailingNewline([enableCodexHooksFeature(removePipelineGatewaySections(currentText ?? "").trimEnd()).trimEnd(), projection.trimEnd()].filter(Boolean).join("\n\n"));
|
|
8
|
+
}
|
|
9
|
+
function removePipelineGatewaySections(content) {
|
|
10
|
+
return PIPELINE_GATEWAY_SECTION_HEADERS.reduce((nextContent, header) => removeTomlSection(nextContent, header), content);
|
|
11
|
+
}
|
|
12
|
+
function removeTomlSection(content, header) {
|
|
13
|
+
const lines = content.split("\n");
|
|
14
|
+
const kept = [];
|
|
15
|
+
let removing = false;
|
|
16
|
+
for (const line of lines) {
|
|
17
|
+
if (line.trim() === header) {
|
|
18
|
+
removing = true;
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
if (removing && line.startsWith("[") && line.trimEnd().endsWith("]")) removing = false;
|
|
22
|
+
if (!removing) kept.push(line);
|
|
23
|
+
}
|
|
24
|
+
return kept.join("\n");
|
|
25
|
+
}
|
|
26
|
+
function enableCodexHooksFeature(content) {
|
|
27
|
+
return setTomlFeature(content, CODEX_HOOKS_FEATURE, "true");
|
|
28
|
+
}
|
|
29
|
+
function setTomlFeature(content, key, value) {
|
|
30
|
+
const lines = content.split("\n");
|
|
31
|
+
const sectionStart = lines.findIndex((line) => line.trim() === CODEX_FEATURES_SECTION_HEADER);
|
|
32
|
+
if (sectionStart === -1) return [
|
|
33
|
+
content.trimEnd(),
|
|
34
|
+
CODEX_FEATURES_SECTION_HEADER,
|
|
35
|
+
`${key} = ${value}`
|
|
36
|
+
].filter(Boolean).join("\n");
|
|
37
|
+
const sectionEnd = nextTomlSectionIndex(lines, sectionStart + 1);
|
|
38
|
+
const beforeSection = lines.slice(0, sectionStart + 1);
|
|
39
|
+
const featureLines = lines.slice(sectionStart + 1, sectionEnd);
|
|
40
|
+
const afterSection = lines.slice(sectionEnd);
|
|
41
|
+
let replaced = false;
|
|
42
|
+
const mergedFeatureLines = featureLines.flatMap((line) => {
|
|
43
|
+
if (!tomlKeyPattern(key).test(line)) return [line];
|
|
44
|
+
if (replaced) return [];
|
|
45
|
+
replaced = true;
|
|
46
|
+
return [`${key} = ${value}`];
|
|
47
|
+
});
|
|
48
|
+
if (!replaced) mergedFeatureLines.push(`${key} = ${value}`);
|
|
49
|
+
return [
|
|
50
|
+
...beforeSection,
|
|
51
|
+
...mergedFeatureLines,
|
|
52
|
+
...afterSection
|
|
53
|
+
].join("\n");
|
|
54
|
+
}
|
|
55
|
+
function nextTomlSectionIndex(lines, startIndex) {
|
|
56
|
+
const nextIndex = lines.findIndex((line, index) => index >= startIndex && isTomlSectionHeader(line));
|
|
57
|
+
return nextIndex === -1 ? lines.length : nextIndex;
|
|
58
|
+
}
|
|
59
|
+
function isTomlSectionHeader(line) {
|
|
60
|
+
const trimmed = line.trim();
|
|
61
|
+
return trimmed.startsWith("[") && trimmed.endsWith("]");
|
|
62
|
+
}
|
|
63
|
+
function tomlKeyPattern(key) {
|
|
64
|
+
return new RegExp(`^\\s*${key}\\s*=`);
|
|
65
|
+
}
|
|
66
|
+
//#endregion
|
|
67
|
+
export { mergeCodexConfig };
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { renderClaudeGatewayMcpServers } from "../mcp/gateway.js";
|
|
2
1
|
import { opencodeAgentName } from "../runtime/opencode-agent-name.js";
|
|
3
2
|
import { mergeClaudeSettings } from "../claude-settings-config.js";
|
|
4
3
|
import { CLAUDE_PROJECT_CONFIG_PATH, commandIdForHost, compactLines, entrypointDescription, entrypointEntries, instructionsPointer, invocationForHost } from "./shared.js";
|
|
@@ -83,11 +82,9 @@ function agentDefinitions(config) {
|
|
|
83
82
|
};
|
|
84
83
|
});
|
|
85
84
|
}
|
|
86
|
-
function settingsDefinition(
|
|
87
|
-
const settings = { permissions: { allow: ["Bash(moka run *)"] } };
|
|
88
|
-
if (config.mcp_gateway) settings.mcpServers = renderClaudeGatewayMcpServers(config);
|
|
85
|
+
function settingsDefinition() {
|
|
89
86
|
return [{
|
|
90
|
-
content: `${JSON.stringify(
|
|
87
|
+
content: `${JSON.stringify({ permissions: { allow: ["Bash(moka run *)"] } }, null, 2)}\n`,
|
|
91
88
|
host: CLAUDE_CODE_HOST,
|
|
92
89
|
invocation: invocationForHost(CLAUDE_CODE_HOST),
|
|
93
90
|
path: CLAUDE_PROJECT_CONFIG_PATH
|
|
@@ -97,7 +94,7 @@ function claudeCodeDefinitions(config, cwd) {
|
|
|
97
94
|
return [
|
|
98
95
|
...commandDefinitions(config),
|
|
99
96
|
...agentDefinitions(config),
|
|
100
|
-
...settingsDefinition(
|
|
97
|
+
...settingsDefinition(),
|
|
101
98
|
projectAgentsMdDefinition(cwd, CLAUDE_CODE_HOST)
|
|
102
99
|
];
|
|
103
100
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { join } from "node:path";
|
|
1
|
+
import { dirname, join } from "node:path";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
//#region src/install-commands/shared.ts
|
|
4
4
|
const GENERATED_MARKER = "<!-- Generated by @oisincoveney/pipeline. -->";
|
|
@@ -12,12 +12,19 @@ const AGENTS_MD_END = "<!-- @oisincoveney/pipeline:agents:end -->";
|
|
|
12
12
|
const SINGLE_OPENCODE_PLUGIN_ARRAY_RE = /\n {2}"plugin": \[\n {4}("[^"]+")\n {2}\]/;
|
|
13
13
|
const OPENCODE_PROJECT_CONFIG_PATH = ".opencode/opencode.json";
|
|
14
14
|
const CLAUDE_PROJECT_CONFIG_PATH = ".claude/settings.json";
|
|
15
|
+
const CLAUDE_USER_CONFIG_PATH = ".claude.json";
|
|
16
|
+
const CODEX_CONFIG_PATH = ".codex/config.toml";
|
|
15
17
|
const OPENCODE_COMMAND_PREFIX = "moka-";
|
|
16
18
|
const ENTRYPOINT_PATH_PATTERNS = {
|
|
17
19
|
opencode: [/^\.opencode\/commands\/(?:moka-)?([^/]+)\.md$/],
|
|
18
20
|
"claude-code": [/^\.claude\/commands\/(?:moka-)?([^/]+)\.md$/]
|
|
19
21
|
};
|
|
20
22
|
const COMMAND_HOSTS = ["opencode", "claude-code"];
|
|
23
|
+
const INSTALL_HOSTS = [
|
|
24
|
+
"opencode",
|
|
25
|
+
"claude-code",
|
|
26
|
+
"codex"
|
|
27
|
+
];
|
|
21
28
|
/**
|
|
22
29
|
* The per-machine host config dirs. Each honors the same env var the host tool
|
|
23
30
|
* itself uses to relocate its config (so containers/CI can redirect, and so
|
|
@@ -51,6 +58,7 @@ function stripPrefix(value, prefix) {
|
|
|
51
58
|
*/
|
|
52
59
|
function resolveHarnessTarget(relPath) {
|
|
53
60
|
const normalized = relPath.replaceAll("\\", "/");
|
|
61
|
+
if (normalized === ".claude.json") return join(dirname(claudeGlobalConfigDir()), CLAUDE_USER_CONFIG_PATH);
|
|
54
62
|
if (normalized === ".claude" || normalized.startsWith(".claude/")) return join(claudeGlobalConfigDir(), stripPrefix(normalized, ".claude"));
|
|
55
63
|
if (normalized === ".codex" || normalized.startsWith(".codex/")) return join(codexGlobalConfigDir(), stripPrefix(normalized, ".codex"));
|
|
56
64
|
if (normalized === ".gemini" || normalized.startsWith(".gemini/")) return join(geminiGlobalConfigDir(), stripPrefix(normalized, ".gemini"));
|
|
@@ -87,4 +95,4 @@ function commandIdForHost(host, entrypointId) {
|
|
|
87
95
|
return entrypointId;
|
|
88
96
|
}
|
|
89
97
|
//#endregion
|
|
90
|
-
export { AGENTS_MD_END, AGENTS_MD_START, CLAUDE_PROJECT_CONFIG_PATH, COMMAND_HOSTS, ENTRYPOINT_PATH_PATTERNS, GENERATED_MARKER, GENERATED_TS_MARKER, GENERATED_YAML_MARKER, OPENCODE_PROJECT_CONFIG_PATH, OWNER_MARKER_PREFIX, OWNER_TS_MARKER_PREFIX, OWNER_YAML_MARKER_PREFIX, SINGLE_OPENCODE_PLUGIN_ARRAY_RE, commandIdForHost, compactLines, entrypointDescription, entrypointEntries, instructionsPointer, invocationForHost, profileEntries, resolveHarnessTarget };
|
|
98
|
+
export { AGENTS_MD_END, AGENTS_MD_START, CLAUDE_PROJECT_CONFIG_PATH, CLAUDE_USER_CONFIG_PATH, CODEX_CONFIG_PATH, COMMAND_HOSTS, ENTRYPOINT_PATH_PATTERNS, GENERATED_MARKER, GENERATED_TS_MARKER, GENERATED_YAML_MARKER, INSTALL_HOSTS, OPENCODE_PROJECT_CONFIG_PATH, OWNER_MARKER_PREFIX, OWNER_TS_MARKER_PREFIX, OWNER_YAML_MARKER_PREFIX, SINGLE_OPENCODE_PLUGIN_ARRAY_RE, commandIdForHost, compactLines, entrypointDescription, entrypointEntries, instructionsPointer, invocationForHost, profileEntries, resolveHarnessTarget };
|
package/dist/install-commands.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { loadPipelineConfig } from "./config/load.js";
|
|
2
2
|
import "./config.js";
|
|
3
|
-
import {
|
|
3
|
+
import { isRecord } from "./json-config-merge.js";
|
|
4
|
+
import { mergeClaudeUserConfig } from "./claude-user-config.js";
|
|
5
|
+
import { mergeCodexConfig } from "./codex-config.js";
|
|
6
|
+
import { renderClaudeGatewayUserConfig, renderCodexGatewayConfig } from "./mcp/gateway.js";
|
|
7
|
+
import { CLAUDE_USER_CONFIG_PATH, CODEX_CONFIG_PATH, COMMAND_HOSTS, ENTRYPOINT_PATH_PATTERNS, INSTALL_HOSTS, invocationForHost, resolveHarnessTarget } from "./install-commands/shared.js";
|
|
4
8
|
import { opencodeAdapter } from "./install-commands/opencode.js";
|
|
5
9
|
import { claudeCodeAdapter } from "./install-commands/claude-code.js";
|
|
6
10
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
@@ -12,7 +16,7 @@ const ADAPTERS = {
|
|
|
12
16
|
"claude-code": claudeCodeAdapter
|
|
13
17
|
};
|
|
14
18
|
function definitionsFor(host, config, cwd) {
|
|
15
|
-
return dedupeDefinitionsByPath((host
|
|
19
|
+
return dedupeDefinitionsByPath([...selectedCommandHosts(host).flatMap((name) => ADAPTERS[name].definitions(config, cwd)), ...gatewayHostConfigDefinitions(host, config)]);
|
|
16
20
|
}
|
|
17
21
|
function dedupeDefinitionsByPath(definitions) {
|
|
18
22
|
const lastIndexes = /* @__PURE__ */ new Map();
|
|
@@ -21,8 +25,17 @@ function dedupeDefinitionsByPath(definitions) {
|
|
|
21
25
|
});
|
|
22
26
|
return definitions.filter((definition, index) => lastIndexes.get(definition.path) === index);
|
|
23
27
|
}
|
|
24
|
-
function
|
|
25
|
-
return host === "all" ? [...
|
|
28
|
+
function selectedInstallHosts(host) {
|
|
29
|
+
return host === "all" ? [...INSTALL_HOSTS] : [host];
|
|
30
|
+
}
|
|
31
|
+
function isActiveCommandHost(host) {
|
|
32
|
+
return host === "opencode" || host === "claude-code";
|
|
33
|
+
}
|
|
34
|
+
function isInstallHost(host) {
|
|
35
|
+
return INSTALL_HOSTS.some((candidate) => candidate === host);
|
|
36
|
+
}
|
|
37
|
+
function selectedCommandHosts(host) {
|
|
38
|
+
return selectedInstallHosts(host).filter(isActiveCommandHost);
|
|
26
39
|
}
|
|
27
40
|
function resourceRootsFor(host) {
|
|
28
41
|
return ADAPTERS[host].resourceRoots;
|
|
@@ -41,8 +54,8 @@ function generatedHostFor(content) {
|
|
|
41
54
|
return COMMAND_HOSTS.find((host) => content.includes(`<!-- @oisincoveney/pipeline:host=${host} -->`) || content.includes(`// @oisincoveney/pipeline:host=${host}`) || content.includes(`# @oisincoveney/pipeline:host=${host}`));
|
|
42
55
|
}
|
|
43
56
|
async function obsoleteGeneratedItems(host, wantedPaths) {
|
|
44
|
-
const hosts = new Set(
|
|
45
|
-
const roots =
|
|
57
|
+
const hosts = new Set(selectedCommandHosts(host));
|
|
58
|
+
const roots = selectedCommandHosts(host).flatMap((selectedHost) => resourceRootsFor(selectedHost));
|
|
46
59
|
return (await Promise.all(roots.map(async (root) => {
|
|
47
60
|
const absRoot = resolveHarnessTarget(root);
|
|
48
61
|
return (await listFiles(absRoot)).map((absolutePath) => ({
|
|
@@ -73,14 +86,43 @@ function entrypointIdFromGeneratedPath(host, path) {
|
|
|
73
86
|
if (match) return match[1];
|
|
74
87
|
}
|
|
75
88
|
}
|
|
89
|
+
const CONFIG_MERGES = {
|
|
90
|
+
[CLAUDE_USER_CONFIG_PATH]: (existingContent, projectionContent) => {
|
|
91
|
+
const projection = JSON.parse(projectionContent);
|
|
92
|
+
if (!isRecord(projection)) return {
|
|
93
|
+
conflict: true,
|
|
94
|
+
content: projectionContent
|
|
95
|
+
};
|
|
96
|
+
const merged = mergeClaudeUserConfig(existingContent, projection);
|
|
97
|
+
return merged.ok ? {
|
|
98
|
+
conflict: false,
|
|
99
|
+
content: merged.content
|
|
100
|
+
} : {
|
|
101
|
+
conflict: true,
|
|
102
|
+
content: projectionContent
|
|
103
|
+
};
|
|
104
|
+
},
|
|
105
|
+
[CODEX_CONFIG_PATH]: (existingContent, projectionContent) => ({
|
|
106
|
+
conflict: false,
|
|
107
|
+
content: mergeCodexConfig(existingContent, projectionContent)
|
|
108
|
+
})
|
|
109
|
+
};
|
|
76
110
|
function resolveDefinitionContent(definition, target) {
|
|
77
|
-
const
|
|
111
|
+
const configMerge = CONFIG_MERGES[definition.path];
|
|
112
|
+
if (configMerge) return configMerge(existsSync(target) ? readFileSync(target, "utf8") : void 0, definition.content);
|
|
113
|
+
const adapter = adapterForDefinition(definition);
|
|
78
114
|
if (!(adapter?.mergeDefinition && existsSync(target))) return {
|
|
79
115
|
conflict: false,
|
|
80
116
|
content: definition.content
|
|
81
117
|
};
|
|
82
118
|
return applyMergeDefinition(adapter.mergeDefinition.bind(adapter), definition, target);
|
|
83
119
|
}
|
|
120
|
+
function adapterForDefinition(definition) {
|
|
121
|
+
return isActiveDefinitionHost(definition.host) ? ADAPTERS[definition.host] : void 0;
|
|
122
|
+
}
|
|
123
|
+
function isActiveDefinitionHost(host) {
|
|
124
|
+
return host === "opencode" || host === "claude-code";
|
|
125
|
+
}
|
|
84
126
|
function applyMergeDefinition(merge, definition, target) {
|
|
85
127
|
const merged = merge(definition, readFileSync(target, "utf8"));
|
|
86
128
|
if (!merged) return {
|
|
@@ -96,6 +138,27 @@ function applyMergeDefinition(merge, definition, target) {
|
|
|
96
138
|
content: merged.content
|
|
97
139
|
};
|
|
98
140
|
}
|
|
141
|
+
function gatewayHostConfigDefinitions(host, config) {
|
|
142
|
+
if (!config.mcp_gateway) return [];
|
|
143
|
+
return selectedInstallHosts(host).flatMap(gatewayHostConfigDefinition(config));
|
|
144
|
+
}
|
|
145
|
+
function gatewayHostConfigDefinition(config) {
|
|
146
|
+
return (host) => {
|
|
147
|
+
if (host === "claude-code") return [{
|
|
148
|
+
content: renderClaudeGatewayUserConfig(config),
|
|
149
|
+
host,
|
|
150
|
+
invocation: invocationForHost(host),
|
|
151
|
+
path: CLAUDE_USER_CONFIG_PATH
|
|
152
|
+
}];
|
|
153
|
+
if (host === "codex") return [{
|
|
154
|
+
content: renderCodexGatewayConfig(config),
|
|
155
|
+
host,
|
|
156
|
+
invocation: "codex",
|
|
157
|
+
path: CODEX_CONFIG_PATH
|
|
158
|
+
}];
|
|
159
|
+
return [];
|
|
160
|
+
};
|
|
161
|
+
}
|
|
99
162
|
function actionFor(path, content, force, block) {
|
|
100
163
|
if (!existsSync(path)) return "create";
|
|
101
164
|
const current = readFileSync(path, "utf8");
|
|
@@ -120,7 +183,8 @@ function upsertGeneratedBlock(current, content, block) {
|
|
|
120
183
|
return `${current.trimEnd()}${separator}${content}`;
|
|
121
184
|
}
|
|
122
185
|
function adapterForcesDefinition(definition) {
|
|
123
|
-
|
|
186
|
+
if (definition.path in CONFIG_MERGES) return true;
|
|
187
|
+
const fn = adapterForDefinition(definition)?.isAlwaysForced;
|
|
124
188
|
return fn ? fn(definition) : false;
|
|
125
189
|
}
|
|
126
190
|
function installActionForDefinition(definition, target, resolved, force) {
|
|
@@ -212,8 +276,9 @@ async function installCommands(options = {}) {
|
|
|
212
276
|
}
|
|
213
277
|
function parseCommandHost(value) {
|
|
214
278
|
const host = value ?? "all";
|
|
215
|
-
if (host === "all"
|
|
216
|
-
|
|
279
|
+
if (host === "all") return host;
|
|
280
|
+
if (isInstallHost(host)) return host;
|
|
281
|
+
throw new Error(`Unsupported host "${host}". Supported values: all, ${INSTALL_HOSTS.join(", ")}.`);
|
|
217
282
|
}
|
|
218
283
|
function formatInstallCommandsResult(result) {
|
|
219
284
|
return result.items.map((item) => `${item.action} ${item.host}: ${item.path} (${item.invocation})`).join("\n");
|
package/dist/install-hooks.js
CHANGED
|
@@ -125,11 +125,7 @@ function isRecord(value) {
|
|
|
125
125
|
function normalizeManifest(value) {
|
|
126
126
|
const files = {};
|
|
127
127
|
const manifestFiles = isRecord(value) ? value.files : void 0;
|
|
128
|
-
if (!isRecord(manifestFiles)) return
|
|
129
|
-
files,
|
|
130
|
-
repository: DEFAULT_HOOK_INSTALL_SOURCE,
|
|
131
|
-
version: 1
|
|
132
|
-
};
|
|
128
|
+
if (!isRecord(manifestFiles)) return emptyManifest();
|
|
133
129
|
for (const [path, entry] of Object.entries(manifestFiles)) if (isRecord(entry) && typeof entry.hash === "string") files[path] = { hash: entry.hash };
|
|
134
130
|
return {
|
|
135
131
|
files,
|
package/dist/install-rules.js
CHANGED
|
@@ -1,20 +1,16 @@
|
|
|
1
1
|
import { execa } from "execa";
|
|
2
|
-
import {
|
|
2
|
+
import { join } from "node:path";
|
|
3
3
|
import { homedir, tmpdir } from "node:os";
|
|
4
|
-
import { fileURLToPath } from "node:url";
|
|
5
4
|
import { mkdir, mkdtemp, readFile, readdir, rm, writeFile } from "node:fs/promises";
|
|
6
5
|
//#region src/install-rules.ts
|
|
7
6
|
const DEFAULT_RULES_INSTALL_SOURCE = "oisin-ee/rules";
|
|
7
|
+
const RULESYNC_PACKAGE = "rulesync@8.30.1";
|
|
8
8
|
const RULESYNC_TARGETS = [
|
|
9
9
|
"claudecode",
|
|
10
10
|
"codexcli",
|
|
11
11
|
"geminicli",
|
|
12
12
|
"opencode"
|
|
13
13
|
];
|
|
14
|
-
function packageRoot() {
|
|
15
|
-
return join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
16
|
-
}
|
|
17
|
-
const PACKAGE_ROOT = packageRoot();
|
|
18
14
|
async function cloneRulesRepository(targetDir) {
|
|
19
15
|
await execa("gh", [
|
|
20
16
|
"repo",
|
|
@@ -41,11 +37,13 @@ async function withRulesSource(sourceOverride, useSource) {
|
|
|
41
37
|
}
|
|
42
38
|
async function defaultRulesyncRunner(args, opts) {
|
|
43
39
|
try {
|
|
44
|
-
await execa("
|
|
40
|
+
await execa("npx", [
|
|
41
|
+
"--yes",
|
|
42
|
+
RULESYNC_PACKAGE,
|
|
43
|
+
...args
|
|
44
|
+
], {
|
|
45
45
|
cwd: opts.cwd,
|
|
46
46
|
env: opts.env,
|
|
47
|
-
localDir: PACKAGE_ROOT,
|
|
48
|
-
preferLocal: true,
|
|
49
47
|
stdio: "inherit"
|
|
50
48
|
});
|
|
51
49
|
} catch (error) {
|
package/dist/mcp/gateway.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { replaceClaudeUserMcpServers } from "../claude-user-config.js";
|
|
2
|
+
import { mergeCodexConfig } from "../codex-config.js";
|
|
1
3
|
import { PipelineMcpGatewayError } from "./gateway-error.js";
|
|
2
4
|
import { McpGatewayService, McpGatewayServiceLive } from "../runtime/services/mcp-gateway-service.js";
|
|
3
5
|
import { resolveRepoLocalBackendSpecs } from "./repo-local-backends.js";
|
|
@@ -68,15 +70,31 @@ function renderOpenCodeGatewayConfig(config) {
|
|
|
68
70
|
function renderClaudeGatewayMcpServers(config) {
|
|
69
71
|
const gateway = configuredGateway(config);
|
|
70
72
|
return { [PIPELINE_GATEWAY_SERVER_ID]: {
|
|
71
|
-
headers:
|
|
73
|
+
headers: gatewayClaudeHeaders(gateway),
|
|
72
74
|
type: "http",
|
|
73
75
|
url: gatewayUrl(gateway)
|
|
74
76
|
} };
|
|
75
77
|
}
|
|
78
|
+
function renderClaudeGatewayUserConfig(config) {
|
|
79
|
+
return `${JSON.stringify({ mcpServers: renderClaudeGatewayMcpServers(config) }, null, 2)}\n`;
|
|
80
|
+
}
|
|
81
|
+
function renderCodexGatewayConfig(config) {
|
|
82
|
+
const gateway = configuredGateway(config);
|
|
83
|
+
return [
|
|
84
|
+
`[mcp_servers.${PIPELINE_GATEWAY_SERVER_ID}]`,
|
|
85
|
+
`url = ${tomlString(gatewayUrl(gateway))}`,
|
|
86
|
+
"",
|
|
87
|
+
`[mcp_servers.${PIPELINE_GATEWAY_SERVER_ID}.env_http_headers]`,
|
|
88
|
+
`Authorization = ${tomlString(gateway.authorization_env)}`,
|
|
89
|
+
""
|
|
90
|
+
].join("\n");
|
|
91
|
+
}
|
|
76
92
|
function configureGatewayHosts(config, options) {
|
|
77
93
|
return selectedGatewayHosts(options.host).map((host) => {
|
|
78
|
-
const
|
|
79
|
-
const
|
|
94
|
+
const adapter = GATEWAY_HOST_CONFIGS[host];
|
|
95
|
+
const path = adapter.path(options.scope, options.cwd);
|
|
96
|
+
const current = existsSync(path) ? readFileSync(path, "utf8") : void 0;
|
|
97
|
+
const content = adapter.configureContent(config, current);
|
|
80
98
|
const backupPath = backupIfExists(path);
|
|
81
99
|
mkdirSync(dirname(path), { recursive: true });
|
|
82
100
|
writeFileSync(path, content);
|
|
@@ -211,12 +229,42 @@ function callGatewayRpc(gateway, url, body) {
|
|
|
211
229
|
});
|
|
212
230
|
}
|
|
213
231
|
function selectedGatewayHosts(host) {
|
|
214
|
-
return host === "all" ? [
|
|
215
|
-
|
|
216
|
-
|
|
232
|
+
return host === "all" ? [
|
|
233
|
+
"opencode",
|
|
234
|
+
"claude-code",
|
|
235
|
+
"codex"
|
|
236
|
+
] : [host];
|
|
237
|
+
}
|
|
238
|
+
const GATEWAY_HOST_CONFIGS = {
|
|
239
|
+
"claude-code": {
|
|
240
|
+
configureContent: (config, current) => {
|
|
241
|
+
const merged = replaceClaudeUserMcpServers(current, { mcpServers: renderClaudeGatewayMcpServers(config) });
|
|
242
|
+
if (!merged.ok) throw new PipelineMcpGatewayError("Cannot parse Claude Code user config.");
|
|
243
|
+
return merged.content;
|
|
244
|
+
},
|
|
245
|
+
path: claudeGatewayConfigPath
|
|
246
|
+
},
|
|
247
|
+
codex: {
|
|
248
|
+
configureContent: (config, current) => mergeCodexConfig(current, renderCodexGatewayConfig(config)),
|
|
249
|
+
path: codexGatewayConfigPath
|
|
250
|
+
},
|
|
251
|
+
opencode: {
|
|
252
|
+
configureContent: (config) => renderOpenCodeGatewayConfig(config),
|
|
253
|
+
path: opencodeGatewayConfigPath
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
function opencodeGatewayConfigPath(scope, cwd) {
|
|
217
257
|
if (scope === "project") return join(cwd, ".opencode", "opencode.json");
|
|
218
258
|
return join(process.env.OPENCODE_CONFIG_DIR ?? join(process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config"), "opencode"), "opencode.json");
|
|
219
259
|
}
|
|
260
|
+
function claudeGatewayConfigPath(scope, cwd) {
|
|
261
|
+
if (scope === "project") return join(cwd, ".mcp.json");
|
|
262
|
+
return join(dirname(process.env.CLAUDE_CONFIG_DIR ?? join(homedir(), ".claude")), ".claude.json");
|
|
263
|
+
}
|
|
264
|
+
function codexGatewayConfigPath(scope, cwd) {
|
|
265
|
+
if (scope === "project") return join(cwd, ".codex", "config.toml");
|
|
266
|
+
return join(process.env.CODEX_HOME ?? join(homedir(), ".codex"), "config.toml");
|
|
267
|
+
}
|
|
220
268
|
function backupIfExists(path) {
|
|
221
269
|
if (!existsSync(path)) return;
|
|
222
270
|
const backupPath = `${path}.bak-${Date.now()}`;
|
|
@@ -255,6 +303,12 @@ function gatewayAuthorizationHeader(gateway) {
|
|
|
255
303
|
function gatewayOpenCodeHeaders(gateway) {
|
|
256
304
|
return { Authorization: gatewayAuthorizationHeader(gateway) };
|
|
257
305
|
}
|
|
306
|
+
function gatewayClaudeHeaders(gateway) {
|
|
307
|
+
return { Authorization: `\${${gateway.authorization_env}}` };
|
|
308
|
+
}
|
|
309
|
+
function tomlString(value) {
|
|
310
|
+
return JSON.stringify(value);
|
|
311
|
+
}
|
|
258
312
|
function checkThv(cwd) {
|
|
259
313
|
return Effect.gen(function* () {
|
|
260
314
|
yield* (yield* McpGatewayService).runToolHiveVersion(cwd);
|
|
@@ -329,4 +383,4 @@ function legacyContentHit(cwd, path, pattern) {
|
|
|
329
383
|
return pattern.test(readFileSync(fullPath, "utf8")) ? path : void 0;
|
|
330
384
|
}
|
|
331
385
|
//#endregion
|
|
332
|
-
export { configureGatewayHosts, gatewayServerForProfile, localGatewayStatus, reconcileGateway,
|
|
386
|
+
export { configureGatewayHosts, gatewayServerForProfile, localGatewayStatus, reconcileGateway, renderClaudeGatewayUserConfig, renderCodexGatewayConfig, renderGatewayConfig, renderOpenCodeGatewayConfig, runGatewayDoctor, startLocalGateway };
|
package/package.json
CHANGED
|
@@ -127,7 +127,7 @@
|
|
|
127
127
|
"prepack": "bun run build:cli"
|
|
128
128
|
},
|
|
129
129
|
"type": "module",
|
|
130
|
-
"version": "3.11.
|
|
130
|
+
"version": "3.11.9",
|
|
131
131
|
"description": "Config-driven multi-agent pipeline runner for repository work",
|
|
132
132
|
"main": "./dist/index.js",
|
|
133
133
|
"types": "./dist/index.d.ts",
|