@ghl-ai/aw 0.1.25 → 0.1.26-beta.2
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/commands/init.mjs +1 -1
- package/commands/pull.mjs +24 -3
- package/commands/push.mjs +6 -6
- package/commands/search.mjs +5 -5
- package/constants.mjs +5 -2
- package/git.mjs +4 -7
- package/integrate.mjs +1 -1
- package/link.mjs +25 -30
- package/mcp.mjs +40 -4
- package/package.json +1 -1
- package/registry.mjs +2 -1
package/commands/init.mjs
CHANGED
|
@@ -218,7 +218,7 @@ export async function initCommand(args) {
|
|
|
218
218
|
|
|
219
219
|
// Pull latest (parallel)
|
|
220
220
|
// cfg.include has the renamed namespace (e.g. 'revex/courses'), but the repo
|
|
221
|
-
// only has '
|
|
221
|
+
// only has '.aw_registry/[template]/' — remap non-platform entries back.
|
|
222
222
|
const freshCfg = config.load(GLOBAL_AW_DIR);
|
|
223
223
|
if (freshCfg && freshCfg.include.length > 0) {
|
|
224
224
|
const pullJobs = freshCfg.include.map(p => {
|
package/commands/pull.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// commands/pull.mjs — Pull content from registry
|
|
2
2
|
|
|
3
|
-
import { mkdirSync, existsSync, readdirSync, copyFileSync } from 'node:fs';
|
|
3
|
+
import { mkdirSync, existsSync, readdirSync, copyFileSync, cpSync } from 'node:fs';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
import { homedir } from 'node:os';
|
|
6
6
|
import { execSync } from 'node:child_process';
|
|
@@ -8,6 +8,7 @@ import * as config from '../config.mjs';
|
|
|
8
8
|
import * as fmt from '../fmt.mjs';
|
|
9
9
|
import { chalk } from '../fmt.mjs';
|
|
10
10
|
import { sparseCheckout, sparseCheckoutAsync, cleanup, includeToSparsePaths } from '../git.mjs';
|
|
11
|
+
import { REGISTRY_DIR } from '../constants.mjs';
|
|
11
12
|
import { walkRegistryTree } from '../registry.mjs';
|
|
12
13
|
import { matchesAny } from '../glob.mjs';
|
|
13
14
|
import { computePlan } from '../plan.mjs';
|
|
@@ -111,7 +112,7 @@ export async function pullCommand(args) {
|
|
|
111
112
|
|
|
112
113
|
try {
|
|
113
114
|
const registryDirs = [];
|
|
114
|
-
const regBase = join(tempDir,
|
|
115
|
+
const regBase = join(tempDir, REGISTRY_DIR);
|
|
115
116
|
|
|
116
117
|
if (existsSync(regBase)) {
|
|
117
118
|
for (const name of listDirs(regBase)) {
|
|
@@ -190,6 +191,16 @@ export async function pullCommand(args) {
|
|
|
190
191
|
}
|
|
191
192
|
}
|
|
192
193
|
|
|
194
|
+
// Bulk-copy docs/ directories (not a TYPE_DIR — raw recursive copy)
|
|
195
|
+
for (const rd of registryDirs) {
|
|
196
|
+
const docsDir = join(rd.path, 'docs');
|
|
197
|
+
if (existsSync(docsDir)) {
|
|
198
|
+
const dest = join(workspaceDir, rd.name, 'docs');
|
|
199
|
+
mkdirSync(dest, { recursive: true });
|
|
200
|
+
cpSync(docsDir, dest, { recursive: true });
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
193
204
|
// MCP registration (second-class — skip if not available)
|
|
194
205
|
if (cfg.namespace) {
|
|
195
206
|
registerMcp(cfg.namespace);
|
|
@@ -235,7 +246,7 @@ export async function pullAsync(args) {
|
|
|
235
246
|
|
|
236
247
|
try {
|
|
237
248
|
const registryDirs = [];
|
|
238
|
-
const regBase = join(tempDir,
|
|
249
|
+
const regBase = join(tempDir, REGISTRY_DIR);
|
|
239
250
|
|
|
240
251
|
if (existsSync(regBase)) {
|
|
241
252
|
for (const name of listDirs(regBase)) {
|
|
@@ -282,6 +293,16 @@ export async function pullAsync(args) {
|
|
|
282
293
|
if (existsSync(src)) copyFileSync(src, join(workspaceDir, fname));
|
|
283
294
|
}
|
|
284
295
|
|
|
296
|
+
// Bulk-copy docs/ directories (not a TYPE_DIR — raw recursive copy)
|
|
297
|
+
for (const rd of registryDirs) {
|
|
298
|
+
const docsDir = join(rd.path, 'docs');
|
|
299
|
+
if (existsSync(docsDir)) {
|
|
300
|
+
const dest = join(workspaceDir, rd.name, 'docs');
|
|
301
|
+
mkdirSync(dest, { recursive: true });
|
|
302
|
+
cpSync(docsDir, dest, { recursive: true });
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
285
306
|
return { pattern, actions, conflictCount };
|
|
286
307
|
} finally {
|
|
287
308
|
cleanup(tempDir);
|
package/commands/push.mjs
CHANGED
|
@@ -6,7 +6,7 @@ import { execSync, execFileSync } from 'node:child_process';
|
|
|
6
6
|
import { tmpdir } from 'node:os';
|
|
7
7
|
import * as fmt from '../fmt.mjs';
|
|
8
8
|
import { chalk } from '../fmt.mjs';
|
|
9
|
-
import { REGISTRY_REPO, REGISTRY_BASE_BRANCH } from '../constants.mjs';
|
|
9
|
+
import { REGISTRY_REPO, REGISTRY_BASE_BRANCH, REGISTRY_DIR } from '../constants.mjs';
|
|
10
10
|
import { resolveInput } from '../paths.mjs';
|
|
11
11
|
import { load as loadManifest } from '../manifest.mjs';
|
|
12
12
|
import { hashFile } from '../registry.mjs';
|
|
@@ -98,8 +98,8 @@ export function pushCommand(args) {
|
|
|
98
98
|
|
|
99
99
|
const isDir = statSync(absPath).isDirectory();
|
|
100
100
|
const registryTarget = isDir
|
|
101
|
-
?
|
|
102
|
-
:
|
|
101
|
+
? `${REGISTRY_DIR}/${namespacePath}/${parentDir}/${slug}`
|
|
102
|
+
: `${REGISTRY_DIR}/${namespacePath}/${parentDir}/${slug}.md`;
|
|
103
103
|
|
|
104
104
|
fmt.note(
|
|
105
105
|
[
|
|
@@ -148,13 +148,13 @@ export function pushCommand(args) {
|
|
|
148
148
|
|
|
149
149
|
// Check if this is a new namespace — auto-add CODEOWNERS entry
|
|
150
150
|
let newNamespace = false;
|
|
151
|
-
const nsDir = join(tempDir,
|
|
151
|
+
const nsDir = join(tempDir, REGISTRY_DIR, topNamespace);
|
|
152
152
|
const codeownersPath = join(tempDir, 'CODEOWNERS');
|
|
153
153
|
if (!existsSync(nsDir) || isNewNamespaceInCodeowners(codeownersPath, topNamespace)) {
|
|
154
154
|
newNamespace = true;
|
|
155
155
|
const ghUser = getGitHubUser();
|
|
156
156
|
if (ghUser && existsSync(codeownersPath)) {
|
|
157
|
-
const line =
|
|
157
|
+
const line = `/${REGISTRY_DIR}/${topNamespace}/ @${ghUser}\n`;
|
|
158
158
|
appendFileSync(codeownersPath, line);
|
|
159
159
|
execSync('git add CODEOWNERS', { cwd: tempDir, stdio: 'pipe' });
|
|
160
160
|
}
|
|
@@ -236,5 +236,5 @@ function getGitHubUser() {
|
|
|
236
236
|
function isNewNamespaceInCodeowners(codeownersPath, namespace) {
|
|
237
237
|
if (!existsSync(codeownersPath)) return true;
|
|
238
238
|
const content = readFileSync(codeownersPath, 'utf8');
|
|
239
|
-
return !content.includes(
|
|
239
|
+
return !content.includes(`/${REGISTRY_DIR}/${namespace}/`);
|
|
240
240
|
}
|
package/commands/search.mjs
CHANGED
|
@@ -7,7 +7,7 @@ import { execSync } from 'node:child_process';
|
|
|
7
7
|
import * as config from '../config.mjs';
|
|
8
8
|
import * as fmt from '../fmt.mjs';
|
|
9
9
|
import { chalk } from '../fmt.mjs';
|
|
10
|
-
import { REGISTRY_BASE_BRANCH, REGISTRY_REPO } from '../constants.mjs';
|
|
10
|
+
import { REGISTRY_BASE_BRANCH, REGISTRY_REPO, REGISTRY_DIR } from '../constants.mjs';
|
|
11
11
|
|
|
12
12
|
export function searchCommand(args) {
|
|
13
13
|
const query = (args._positional || []).join(' ').toLowerCase();
|
|
@@ -150,14 +150,14 @@ function searchRemote(repo, query) {
|
|
|
150
150
|
try {
|
|
151
151
|
// git archive fetches a single file from remote — no clone needed
|
|
152
152
|
content = execSync(
|
|
153
|
-
`git archive --remote="${repoUrl}" ${REGISTRY_BASE_BRANCH}
|
|
153
|
+
`git archive --remote="${repoUrl}" ${REGISTRY_BASE_BRANCH} ${REGISTRY_DIR}/registry.json | tar -xO ${REGISTRY_DIR}/registry.json`,
|
|
154
154
|
{ encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }
|
|
155
155
|
);
|
|
156
156
|
} catch {
|
|
157
157
|
// GitHub doesn't support git archive — fallback to gh API
|
|
158
158
|
try {
|
|
159
159
|
const raw = execSync(
|
|
160
|
-
`gh api "repos/${repo}/contents/registry
|
|
160
|
+
`gh api "repos/${repo}/contents/${REGISTRY_DIR}/registry.json?ref=${REGISTRY_BASE_BRANCH}" --jq .content -H "Accept: application/vnd.github.v3+json"`,
|
|
161
161
|
{ encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }
|
|
162
162
|
);
|
|
163
163
|
content = Buffer.from(raw.trim(), 'base64').toString('utf8');
|
|
@@ -167,9 +167,9 @@ function searchRemote(repo, query) {
|
|
|
167
167
|
try {
|
|
168
168
|
execSync(`git clone --filter=blob:none --no-checkout "${repoUrl}" "${tempDir}"`, { stdio: 'pipe' });
|
|
169
169
|
execSync('git sparse-checkout init --cone', { cwd: tempDir, stdio: 'pipe' });
|
|
170
|
-
execSync(
|
|
170
|
+
execSync(`git sparse-checkout set --skip-checks "${REGISTRY_DIR}/registry.json"`, { cwd: tempDir, stdio: 'pipe' });
|
|
171
171
|
execSync(`git checkout ${REGISTRY_BASE_BRANCH}`, { cwd: tempDir, stdio: 'pipe' });
|
|
172
|
-
content = readFileSync(join(tempDir,
|
|
172
|
+
content = readFileSync(join(tempDir, REGISTRY_DIR, 'registry.json'), 'utf8');
|
|
173
173
|
} finally {
|
|
174
174
|
try { execSync(`rm -rf "${tempDir}"`, { stdio: 'pipe' }); } catch {}
|
|
175
175
|
}
|
package/constants.mjs
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
// constants.mjs — Single source of truth for registry settings.
|
|
2
2
|
|
|
3
3
|
/** Base branch for PRs and sync checkout */
|
|
4
|
-
export const REGISTRY_BASE_BRANCH = '
|
|
4
|
+
export const REGISTRY_BASE_BRANCH = 'feat/aw-platform-docs';
|
|
5
5
|
|
|
6
6
|
/** Default registry repository */
|
|
7
|
-
export const REGISTRY_REPO = 'GoHighLevel/
|
|
7
|
+
export const REGISTRY_REPO = 'GoHighLevel/platform-docs';
|
|
8
|
+
|
|
9
|
+
/** Directory inside the registry repo that holds platform/ and [template]/ */
|
|
10
|
+
export const REGISTRY_DIR = '.aw_registry';
|
package/git.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import { mkdtempSync, existsSync } from 'node:fs';
|
|
|
5
5
|
import { join } from 'node:path';
|
|
6
6
|
import { tmpdir } from 'node:os';
|
|
7
7
|
import { promisify } from 'node:util';
|
|
8
|
-
import { REGISTRY_BASE_BRANCH } from './constants.mjs';
|
|
8
|
+
import { REGISTRY_BASE_BRANCH, REGISTRY_DIR } from './constants.mjs';
|
|
9
9
|
|
|
10
10
|
const exec = promisify(execCb);
|
|
11
11
|
|
|
@@ -76,16 +76,13 @@ export function cleanup(tempDir) {
|
|
|
76
76
|
|
|
77
77
|
/**
|
|
78
78
|
* Compute sparse checkout paths from include paths.
|
|
79
|
-
* e.g., ["platform", "dev/agents/debugger"] -> ["
|
|
79
|
+
* e.g., ["platform", "dev/agents/debugger"] -> [".aw_registry/platform", ".aw_registry/dev/agents/debugger"]
|
|
80
80
|
*/
|
|
81
81
|
export function includeToSparsePaths(paths) {
|
|
82
82
|
const result = new Set();
|
|
83
83
|
for (const p of paths) {
|
|
84
|
-
result.add(
|
|
84
|
+
result.add(`${REGISTRY_DIR}/${p}`);
|
|
85
85
|
}
|
|
86
|
-
|
|
87
|
-
result.add('registry/CLAUDE.md');
|
|
88
|
-
result.add('registry/AGENTS.md');
|
|
89
|
-
result.add('registry/AW-PROTOCOL.md');
|
|
86
|
+
result.add(`${REGISTRY_DIR}/AW-PROTOCOL.md`);
|
|
90
87
|
return [...result];
|
|
91
88
|
}
|
package/integrate.mjs
CHANGED
|
@@ -65,7 +65,7 @@ export function copyInstructions(cwd, tempDir, namespace) {
|
|
|
65
65
|
if (existsSync(dest)) continue;
|
|
66
66
|
|
|
67
67
|
if (tempDir) {
|
|
68
|
-
const src = join(tempDir, '
|
|
68
|
+
const src = join(tempDir, '.aw_registry', file);
|
|
69
69
|
if (existsSync(src)) {
|
|
70
70
|
let content = readFileSync(src, 'utf8');
|
|
71
71
|
if (namespace) {
|
package/link.mjs
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
// link.mjs — Create symlinks from IDE dirs → .aw_registry/
|
|
2
2
|
|
|
3
|
-
import { existsSync, lstatSync, mkdirSync, readdirSync, unlinkSync } from 'node:fs';
|
|
3
|
+
import { existsSync, lstatSync, mkdirSync, readdirSync, unlinkSync, symlinkSync } from 'node:fs';
|
|
4
4
|
import { join, relative } from 'node:path';
|
|
5
|
-
import { execSync } from 'node:child_process';
|
|
6
5
|
import { homedir } from 'node:os';
|
|
7
6
|
import * as fmt from './fmt.mjs';
|
|
8
7
|
|
|
8
|
+
function forceSymlink(target, linkPath) {
|
|
9
|
+
try { unlinkSync(linkPath); } catch { /* not there yet */ }
|
|
10
|
+
symlinkSync(target, linkPath);
|
|
11
|
+
}
|
|
12
|
+
|
|
9
13
|
const IDE_DIRS = ['.claude', '.cursor', '.codex'];
|
|
10
14
|
// Per-file symlink types
|
|
11
15
|
const FILE_TYPES = ['agents'];
|
|
12
|
-
const ALL_KNOWN_TYPES = new Set([...FILE_TYPES, 'skills', 'commands', 'evals']);
|
|
16
|
+
const ALL_KNOWN_TYPES = new Set([...FILE_TYPES, 'skills', 'commands', 'evals', 'docs']);
|
|
13
17
|
|
|
14
18
|
/**
|
|
15
19
|
* List namespace directories inside .aw_registry/ (skip dotfiles).
|
|
@@ -135,10 +139,7 @@ export function linkWorkspace(cwd) {
|
|
|
135
139
|
const linkPath = join(linkDir, flat);
|
|
136
140
|
const targetPath = join(typeDirPath, file);
|
|
137
141
|
const relTarget = relative(linkDir, targetPath);
|
|
138
|
-
try {
|
|
139
|
-
execSync(`ln -sfn "${relTarget}" "${linkPath}"`, { stdio: 'pipe' });
|
|
140
|
-
created++;
|
|
141
|
-
} catch { /* best effort */ }
|
|
142
|
+
try { forceSymlink(relTarget, linkPath); created++; } catch { /* best effort */ }
|
|
142
143
|
}
|
|
143
144
|
}
|
|
144
145
|
}
|
|
@@ -155,10 +156,7 @@ export function linkWorkspace(cwd) {
|
|
|
155
156
|
const linkPath = join(linkDir, flat);
|
|
156
157
|
const targetPath = join(skillsDir, skill);
|
|
157
158
|
const relTarget = relative(linkDir, targetPath);
|
|
158
|
-
try {
|
|
159
|
-
execSync(`ln -sfn "${relTarget}" "${linkPath}"`, { stdio: 'pipe' });
|
|
160
|
-
created++;
|
|
161
|
-
} catch { /* best effort */ }
|
|
159
|
+
try { forceSymlink(relTarget, linkPath); created++; } catch { /* best effort */ }
|
|
162
160
|
}
|
|
163
161
|
}
|
|
164
162
|
}
|
|
@@ -178,10 +176,7 @@ export function linkWorkspace(cwd) {
|
|
|
178
176
|
const linkPath = join(linkDir, flat);
|
|
179
177
|
const targetPath = join(subDir, evalName);
|
|
180
178
|
const relTarget = relative(linkDir, targetPath);
|
|
181
|
-
try {
|
|
182
|
-
execSync(`ln -sfn "${relTarget}" "${linkPath}"`, { stdio: 'pipe' });
|
|
183
|
-
created++;
|
|
184
|
-
} catch { /* best effort */ }
|
|
179
|
+
try { forceSymlink(relTarget, linkPath); created++; } catch { /* best effort */ }
|
|
185
180
|
}
|
|
186
181
|
}
|
|
187
182
|
}
|
|
@@ -199,10 +194,7 @@ export function linkWorkspace(cwd) {
|
|
|
199
194
|
const linkPath = join(agentsSkillsDir, flat);
|
|
200
195
|
const targetPath = join(skillsDir, skill);
|
|
201
196
|
const relTarget = relative(agentsSkillsDir, targetPath);
|
|
202
|
-
try {
|
|
203
|
-
execSync(`ln -sfn "${relTarget}" "${linkPath}"`, { stdio: 'pipe' });
|
|
204
|
-
created++;
|
|
205
|
-
} catch { /* best effort */ }
|
|
197
|
+
try { forceSymlink(relTarget, linkPath); created++; } catch { /* best effort */ }
|
|
206
198
|
}
|
|
207
199
|
}
|
|
208
200
|
}
|
|
@@ -220,10 +212,7 @@ export function linkWorkspace(cwd) {
|
|
|
220
212
|
const linkPath = join(linkDir, cmdFileName);
|
|
221
213
|
const targetPath = join(commandsDir, file);
|
|
222
214
|
const relTarget = relative(linkDir, targetPath);
|
|
223
|
-
try {
|
|
224
|
-
execSync(`ln -sfn "${relTarget}" "${linkPath}"`, { stdio: 'pipe' });
|
|
225
|
-
created++;
|
|
226
|
-
} catch { /* best effort */ }
|
|
215
|
+
try { forceSymlink(relTarget, linkPath); created++; } catch { /* best effort */ }
|
|
227
216
|
}
|
|
228
217
|
}
|
|
229
218
|
}
|
|
@@ -235,13 +224,19 @@ export function linkWorkspace(cwd) {
|
|
|
235
224
|
for (const ide of IDE_DIRS) {
|
|
236
225
|
const linkPath = join(cwd, ide, 'AW-PROTOCOL.md');
|
|
237
226
|
const relTarget = relative(join(cwd, ide), protocolSrc);
|
|
238
|
-
try {
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
227
|
+
try { forceSymlink(relTarget, linkPath); created++; } catch { /* best effort */ }
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Docs: single directory symlink per IDE → .aw_registry/platform/docs
|
|
232
|
+
const platformDocsDir = join(awDir, 'platform', 'docs');
|
|
233
|
+
if (existsSync(platformDocsDir)) {
|
|
234
|
+
for (const ide of IDE_DIRS) {
|
|
235
|
+
const linkDir = join(cwd, ide);
|
|
236
|
+
mkdirSync(linkDir, { recursive: true });
|
|
237
|
+
const linkPath = join(linkDir, 'docs');
|
|
238
|
+
const relTarget = relative(linkDir, platformDocsDir);
|
|
239
|
+
try { forceSymlink(relTarget, linkPath); created++; } catch { /* best effort */ }
|
|
245
240
|
}
|
|
246
241
|
}
|
|
247
242
|
|
package/mcp.mjs
CHANGED
|
@@ -35,12 +35,12 @@ export function setupMcp(cwd, namespace) {
|
|
|
35
35
|
|
|
36
36
|
const mcpUrl = paths.ghlMcpUrl;
|
|
37
37
|
|
|
38
|
-
const ghlAiServer = { type: '
|
|
38
|
+
const ghlAiServer = { type: 'http', url: mcpUrl };
|
|
39
39
|
const gitJenkinsServer = paths.gitJenkinsPath
|
|
40
40
|
? { command: 'node', args: [paths.gitJenkinsPath] }
|
|
41
41
|
: null;
|
|
42
42
|
|
|
43
|
-
// ── Claude Code: ~/.claude/settings.json ──
|
|
43
|
+
// ── Claude Code: ~/.claude/settings.json (global) ──
|
|
44
44
|
const claudeSettingsPath = join(HOME, '.claude', 'settings.json');
|
|
45
45
|
if (mergeJsonMcpServer(claudeSettingsPath, 'ghl-ai', ghlAiServer)) {
|
|
46
46
|
updatedFiles.push(claudeSettingsPath);
|
|
@@ -49,7 +49,37 @@ export function setupMcp(cwd, namespace) {
|
|
|
49
49
|
updatedFiles.push(claudeSettingsPath);
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
// ──
|
|
52
|
+
// ── Claude Code: .mcp.json (project root — highest priority) ──
|
|
53
|
+
// Claude Code resolves project .mcp.json before global settings.json,
|
|
54
|
+
// so we must write here to ensure the HTTP URL takes precedence over
|
|
55
|
+
// any stale command-based configs in parent directories.
|
|
56
|
+
const projectMcpPath = join(cwd, '.mcp.json');
|
|
57
|
+
if (mergeJsonMcpServer(projectMcpPath, 'ghl-ai', ghlAiServer)) {
|
|
58
|
+
updatedFiles.push(projectMcpPath);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ── Claude Code: .claude/mcp.json (workspace-level) ──
|
|
62
|
+
// Claude Code also reads .claude/mcp.json for workspace-scoped MCP config.
|
|
63
|
+
// Write here so the MCP server is available regardless of which resolution
|
|
64
|
+
// path Claude Code uses (root .mcp.json or .claude/mcp.json).
|
|
65
|
+
const claudeWorkspaceMcpPath = join(cwd, '.claude', 'mcp.json');
|
|
66
|
+
if (mergeJsonMcpServer(claudeWorkspaceMcpPath, 'ghl-ai', ghlAiServer)) {
|
|
67
|
+
updatedFiles.push(claudeWorkspaceMcpPath);
|
|
68
|
+
}
|
|
69
|
+
if (gitJenkinsServer && mergeJsonMcpServer(claudeWorkspaceMcpPath, 'git-jenkins', gitJenkinsServer)) {
|
|
70
|
+
updatedFiles.push(claudeWorkspaceMcpPath);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ── Cursor: project .cursor/mcp.json (workspace-level) ──
|
|
74
|
+
const cursorProjectMcpPath = join(cwd, '.cursor', 'mcp.json');
|
|
75
|
+
if (mergeJsonMcpServer(cursorProjectMcpPath, 'ghl-ai', ghlAiServer)) {
|
|
76
|
+
updatedFiles.push(cursorProjectMcpPath);
|
|
77
|
+
}
|
|
78
|
+
if (gitJenkinsServer && mergeJsonMcpServer(cursorProjectMcpPath, 'git-jenkins', gitJenkinsServer)) {
|
|
79
|
+
updatedFiles.push(cursorProjectMcpPath);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ── Cursor: ~/.cursor/mcp.json (global) ──
|
|
53
83
|
const cursorMcpPath = join(HOME, '.cursor', 'mcp.json');
|
|
54
84
|
if (mergeJsonMcpServer(cursorMcpPath, 'ghl-ai', ghlAiServer)) {
|
|
55
85
|
updatedFiles.push(cursorMcpPath);
|
|
@@ -94,12 +124,18 @@ function mergeJsonMcpServer(filePath, serverName, serverConfig) {
|
|
|
94
124
|
config.mcpServers = {};
|
|
95
125
|
}
|
|
96
126
|
|
|
97
|
-
// Check if already configured with same
|
|
127
|
+
// Check if already configured with same config
|
|
98
128
|
const existing = config.mcpServers[serverName];
|
|
99
129
|
if (existing && JSON.stringify(existing) === JSON.stringify(serverConfig)) {
|
|
100
130
|
return false;
|
|
101
131
|
}
|
|
102
132
|
|
|
133
|
+
// If we're writing an HTTP URL server, remove any stale command-based config
|
|
134
|
+
// to prevent old local MCP servers from shadowing the remote one.
|
|
135
|
+
if (serverConfig.type === 'http' && existing && existing.command) {
|
|
136
|
+
fmt.logStep(`Replacing stale command-based '${serverName}' with HTTP URL`);
|
|
137
|
+
}
|
|
138
|
+
|
|
103
139
|
config.mcpServers[serverName] = serverConfig;
|
|
104
140
|
|
|
105
141
|
mkdirSync(join(filePath, '..'), { recursive: true });
|
package/package.json
CHANGED
package/registry.mjs
CHANGED
|
@@ -5,6 +5,7 @@ import { join, relative } from 'node:path';
|
|
|
5
5
|
import { createHash } from 'node:crypto';
|
|
6
6
|
|
|
7
7
|
const TYPE_DIRS = new Set(['agents', 'skills', 'commands', 'evals']);
|
|
8
|
+
const SKIP_DIRS = new Set(['docs']);
|
|
8
9
|
|
|
9
10
|
export function sha256(content) {
|
|
10
11
|
return createHash('sha256').update(content).digest('hex');
|
|
@@ -97,7 +98,7 @@ export function walkRegistryTree(baseDir, baseName) {
|
|
|
97
98
|
});
|
|
98
99
|
}
|
|
99
100
|
}
|
|
100
|
-
} else if (entry.isDirectory()) {
|
|
101
|
+
} else if (entry.isDirectory() && !SKIP_DIRS.has(entry.name)) {
|
|
101
102
|
recurse(fullPath, [...pathSegments, entry.name]);
|
|
102
103
|
}
|
|
103
104
|
}
|