@patchen0518/agentbrew 1.2.5 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-registry.d.ts +23 -0
- package/dist/agent-registry.js +101 -0
- package/dist/agent-registry.js.map +1 -0
- package/dist/cli.js +5 -17
- package/dist/cli.js.map +1 -1
- package/dist/registry.js +29 -0
- package/dist/registry.js.map +1 -1
- package/dist/sync-instructions.d.ts +50 -0
- package/dist/sync-instructions.js +214 -0
- package/dist/sync-instructions.js.map +1 -0
- package/dist/sync-mcp.d.ts +31 -0
- package/dist/sync-mcp.js +317 -0
- package/dist/sync-mcp.js.map +1 -0
- package/dist/sync-state.d.ts +15 -0
- package/dist/sync-state.js +45 -0
- package/dist/sync-state.js.map +1 -0
- package/dist/sync.d.ts +10 -116
- package/dist/sync.js +123 -687
- package/dist/sync.js.map +1 -1
- package/package.json +1 -1
package/dist/sync.js
CHANGED
|
@@ -36,8 +36,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
36
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.INSTRUCTIONS_FILE = exports.MARKER_END = exports.MARKER_START = void 0;
|
|
39
|
+
exports.unsyncInstructions = exports.syncInstructions = exports.removeFromFile = exports.injectIntoFile = exports.buildInjectedSection = exports.getInstructionsPath = exports.getDefaultTargets = exports.INSTRUCTIONS_FILE = exports.MARKER_END = exports.MARKER_START = exports.unsyncMcpServerFromKiro = exports.syncMcpServerToKiro = exports.unsyncMcpServerFromCodex = exports.syncMcpServerToCodex = exports.unsyncMcpServerFromCursor = exports.syncMcpServerToCursor = void 0;
|
|
40
40
|
exports.extractSkillEntries = extractSkillEntries;
|
|
41
|
+
exports.syncSkillsToAgent = syncSkillsToAgent;
|
|
42
|
+
exports.unsyncSkillsFromAgent = unsyncSkillsFromAgent;
|
|
41
43
|
exports.syncSkillsToClaudeCode = syncSkillsToClaudeCode;
|
|
42
44
|
exports.unsyncSkillsFromClaudeCode = unsyncSkillsFromClaudeCode;
|
|
43
45
|
exports.syncSkillsToGeminiCLI = syncSkillsToGeminiCLI;
|
|
@@ -46,30 +48,18 @@ exports.syncSkillsToWindsurf = syncSkillsToWindsurf;
|
|
|
46
48
|
exports.unsyncSkillsFromWindsurf = unsyncSkillsFromWindsurf;
|
|
47
49
|
exports.syncSkillsToAntigravityCLI = syncSkillsToAntigravityCLI;
|
|
48
50
|
exports.unsyncSkillsFromAntigravityCLI = unsyncSkillsFromAntigravityCLI;
|
|
51
|
+
exports.syncSkillsToKiro = syncSkillsToKiro;
|
|
52
|
+
exports.unsyncSkillsFromKiro = unsyncSkillsFromKiro;
|
|
49
53
|
exports.cleanOrphanSkills = cleanOrphanSkills;
|
|
50
|
-
exports.syncMcpServerToCursor = syncMcpServerToCursor;
|
|
51
|
-
exports.unsyncMcpServerFromCursor = unsyncMcpServerFromCursor;
|
|
52
|
-
exports.syncMcpServerToCodex = syncMcpServerToCodex;
|
|
53
|
-
exports.unsyncMcpServerFromCodex = unsyncMcpServerFromCodex;
|
|
54
54
|
exports.syncSkillsToCursor = syncSkillsToCursor;
|
|
55
55
|
exports.unsyncSkillsFromCursor = unsyncSkillsFromCursor;
|
|
56
|
-
exports.syncSkillsToKiro = syncSkillsToKiro;
|
|
57
|
-
exports.unsyncSkillsFromKiro = unsyncSkillsFromKiro;
|
|
58
|
-
exports.syncMcpServerToKiro = syncMcpServerToKiro;
|
|
59
|
-
exports.unsyncMcpServerFromKiro = unsyncMcpServerFromKiro;
|
|
60
|
-
exports.getDefaultTargets = getDefaultTargets;
|
|
61
|
-
exports.getInstructionsPath = getInstructionsPath;
|
|
62
|
-
exports.buildInjectedSection = buildInjectedSection;
|
|
63
|
-
exports.injectIntoFile = injectIntoFile;
|
|
64
|
-
exports.removeFromFile = removeFromFile;
|
|
65
|
-
exports.syncInstructions = syncInstructions;
|
|
66
|
-
exports.unsyncInstructions = unsyncInstructions;
|
|
67
56
|
const fs_1 = __importDefault(require("fs"));
|
|
68
57
|
const os_1 = __importDefault(require("os"));
|
|
69
58
|
const path_1 = __importDefault(require("path"));
|
|
70
59
|
const toml = __importStar(require("smol-toml"));
|
|
71
|
-
const
|
|
72
|
-
const
|
|
60
|
+
const sync_state_1 = require("./sync-state");
|
|
61
|
+
const agent_registry_1 = require("./agent-registry");
|
|
62
|
+
const logger_1 = require("./logger");
|
|
73
63
|
/**
|
|
74
64
|
* Extracts SkillEntry objects from discovered packages by scanning for SKILL.md prompts.
|
|
75
65
|
*/
|
|
@@ -91,40 +81,7 @@ function extractSkillEntries(packages) {
|
|
|
91
81
|
}
|
|
92
82
|
return skills;
|
|
93
83
|
}
|
|
94
|
-
const SYNCED_SKILLS_FILE = 'synced-skills.json';
|
|
95
|
-
const AGENTBREW_EXTENSION_NAME = 'agentbrew';
|
|
96
84
|
const CURSOR_SKILLS_INDEX_FILE = 'agentbrew-skills-index.md';
|
|
97
|
-
function getSyncedSkillsPath(brewRoot) {
|
|
98
|
-
return path_1.default.join(brewRoot ?? (0, config_1.getBrewRoot)(), SYNCED_SKILLS_FILE);
|
|
99
|
-
}
|
|
100
|
-
function loadSyncedState(brewRoot) {
|
|
101
|
-
try {
|
|
102
|
-
const raw = JSON.parse(fs_1.default.readFileSync(getSyncedSkillsPath(brewRoot), 'utf-8'));
|
|
103
|
-
// Migrate old flat format: { skills: [...] } → { claude: [...] }
|
|
104
|
-
if (raw.skills && !raw.claude) {
|
|
105
|
-
return { claude: raw.skills, gemini: [], windsurf: [], cursor: false, cursorMcp: false, antigravity: [], codexMcp: false, kiro: [], kiroMcp: false };
|
|
106
|
-
}
|
|
107
|
-
return {
|
|
108
|
-
claude: raw.claude ?? [],
|
|
109
|
-
gemini: raw.gemini ?? [],
|
|
110
|
-
windsurf: raw.windsurf ?? [],
|
|
111
|
-
cursor: raw.cursor ?? false,
|
|
112
|
-
cursorMcp: raw.cursorMcp ?? false,
|
|
113
|
-
antigravity: raw.antigravity ?? [],
|
|
114
|
-
codexMcp: raw.codexMcp ?? false,
|
|
115
|
-
kiro: raw.kiro ?? [],
|
|
116
|
-
kiroMcp: raw.kiroMcp ?? false,
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
catch {
|
|
120
|
-
return { claude: [], gemini: [], windsurf: [], cursor: false, cursorMcp: false, antigravity: [], codexMcp: false, kiro: [], kiroMcp: false };
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
function saveSyncedState(state, brewRoot) {
|
|
124
|
-
const p = getSyncedSkillsPath(brewRoot);
|
|
125
|
-
fs_1.default.mkdirSync(path_1.default.dirname(p), { recursive: true });
|
|
126
|
-
fs_1.default.writeFileSync(p, JSON.stringify(state, null, 2), 'utf-8');
|
|
127
|
-
}
|
|
128
85
|
function symlinkSkills(skills, targetDir, state, agentKey, brewRoot) {
|
|
129
86
|
const results = [];
|
|
130
87
|
const newEntries = [];
|
|
@@ -140,13 +97,19 @@ function symlinkSkills(skills, targetDir, state, agentKey, brewRoot) {
|
|
|
140
97
|
fs_1.default.lstatSync(entryPath);
|
|
141
98
|
exists = true;
|
|
142
99
|
}
|
|
143
|
-
catch {
|
|
100
|
+
catch (e) {
|
|
101
|
+
if (e.code !== 'ENOENT')
|
|
102
|
+
logger_1.Logger.warn(`Unexpected error checking ${entryName}: ${e.message}`);
|
|
103
|
+
}
|
|
144
104
|
if (exists) {
|
|
145
105
|
let currentTarget = null;
|
|
146
106
|
try {
|
|
147
107
|
currentTarget = fs_1.default.readlinkSync(entryPath);
|
|
148
108
|
}
|
|
149
|
-
catch {
|
|
109
|
+
catch (e) {
|
|
110
|
+
if (e.code !== 'ENOENT')
|
|
111
|
+
logger_1.Logger.warn(`Unexpected error checking ${entryName}: ${e.message}`);
|
|
112
|
+
}
|
|
150
113
|
if (currentTarget === null) {
|
|
151
114
|
// Not a symlink — not created by AgentBrew, leave it alone
|
|
152
115
|
results.push({ entryName, status: 'skipped', note: 'Path exists and is not a symlink' });
|
|
@@ -161,7 +124,9 @@ function symlinkSkills(skills, targetDir, state, agentKey, brewRoot) {
|
|
|
161
124
|
try {
|
|
162
125
|
fs_1.default.rmSync(entryPath, { force: true });
|
|
163
126
|
}
|
|
164
|
-
catch {
|
|
127
|
+
catch (e) {
|
|
128
|
+
logger_1.Logger.warn(`Could not remove ${entryPath}: ${e.message}`);
|
|
129
|
+
}
|
|
165
130
|
}
|
|
166
131
|
try {
|
|
167
132
|
fs_1.default.symlinkSync(skill.skillDir, entryPath);
|
|
@@ -173,7 +138,7 @@ function symlinkSkills(skills, targetDir, state, agentKey, brewRoot) {
|
|
|
173
138
|
}
|
|
174
139
|
}
|
|
175
140
|
state[agentKey] = [...new Set([...state[agentKey], ...newEntries])];
|
|
176
|
-
saveSyncedState(state, brewRoot);
|
|
141
|
+
(0, sync_state_1.saveSyncedState)(state, brewRoot);
|
|
177
142
|
return results;
|
|
178
143
|
}
|
|
179
144
|
function removeTrackedSymlinks(tracked, skillsDir) {
|
|
@@ -185,7 +150,10 @@ function removeTrackedSymlinks(tracked, skillsDir) {
|
|
|
185
150
|
fs_1.default.lstatSync(entryPath);
|
|
186
151
|
exists = true;
|
|
187
152
|
}
|
|
188
|
-
catch {
|
|
153
|
+
catch (e) {
|
|
154
|
+
if (e.code !== 'ENOENT')
|
|
155
|
+
logger_1.Logger.warn(`Unexpected error checking ${entryName}: ${e.message}`);
|
|
156
|
+
}
|
|
189
157
|
if (!exists) {
|
|
190
158
|
results.push({ entryName, status: 'skipped', note: 'Not found' });
|
|
191
159
|
continue;
|
|
@@ -200,157 +168,68 @@ function removeTrackedSymlinks(tracked, skillsDir) {
|
|
|
200
168
|
}
|
|
201
169
|
return results;
|
|
202
170
|
}
|
|
203
|
-
// ─── Claude Code ────────────────────────────────────────────────────────────
|
|
204
171
|
/**
|
|
205
|
-
*
|
|
206
|
-
* so Claude Code can discover them as invocable skills.
|
|
172
|
+
* Generic skill sync for any agent in AGENT_SKILL_REGISTRY.
|
|
207
173
|
*/
|
|
208
|
-
function
|
|
209
|
-
const
|
|
210
|
-
if (!fs_1.default.existsSync(
|
|
174
|
+
function syncSkillsToAgent(agent, skills, brewRoot) {
|
|
175
|
+
const rootDir = agent.agentRootDir?.();
|
|
176
|
+
if (rootDir && !fs_1.default.existsSync(rootDir))
|
|
211
177
|
return [];
|
|
212
|
-
const skillsDir =
|
|
178
|
+
const skillsDir = agent.skillsDir();
|
|
213
179
|
fs_1.default.mkdirSync(skillsDir, { recursive: true });
|
|
214
|
-
const state = loadSyncedState(brewRoot);
|
|
215
|
-
|
|
180
|
+
const state = (0, sync_state_1.loadSyncedState)(brewRoot);
|
|
181
|
+
const results = symlinkSkills(skills, skillsDir, state, agent.key, brewRoot);
|
|
182
|
+
if (results.some(r => r.status === 'linked' || r.status === 'already_exists')) {
|
|
183
|
+
agent.onAfterSync?.();
|
|
184
|
+
}
|
|
185
|
+
return results;
|
|
216
186
|
}
|
|
217
187
|
/**
|
|
218
|
-
*
|
|
188
|
+
* Generic skill unsync for any agent in AGENT_SKILL_REGISTRY.
|
|
219
189
|
*/
|
|
220
|
-
function
|
|
221
|
-
const skillsDir =
|
|
222
|
-
const state = loadSyncedState(brewRoot);
|
|
223
|
-
const results = removeTrackedSymlinks(state.
|
|
224
|
-
state.
|
|
225
|
-
saveSyncedState(state, brewRoot);
|
|
190
|
+
function unsyncSkillsFromAgent(agent, brewRoot) {
|
|
191
|
+
const skillsDir = agent.skillsDir();
|
|
192
|
+
const state = (0, sync_state_1.loadSyncedState)(brewRoot);
|
|
193
|
+
const results = removeTrackedSymlinks(state[agent.key], skillsDir);
|
|
194
|
+
state[agent.key] = [];
|
|
195
|
+
(0, sync_state_1.saveSyncedState)(state, brewRoot);
|
|
196
|
+
agent.onAfterUnsync?.();
|
|
226
197
|
return results;
|
|
227
198
|
}
|
|
228
|
-
// ───
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
* ~/.gemini/extensions/agentbrew/ and symlinking each skill's directory into
|
|
232
|
-
* ~/.gemini/extensions/agentbrew/skills/<pkgName>-<skillName>.
|
|
233
|
-
*/
|
|
234
|
-
function syncSkillsToGeminiCLI(skills, brewRoot) {
|
|
235
|
-
const geminiDir = path_1.default.join(os_1.default.homedir(), '.gemini');
|
|
236
|
-
if (!fs_1.default.existsSync(geminiDir))
|
|
237
|
-
return [];
|
|
238
|
-
const extensionDir = path_1.default.join(geminiDir, 'extensions', AGENTBREW_EXTENSION_NAME);
|
|
239
|
-
const skillsDir = path_1.default.join(extensionDir, 'skills');
|
|
240
|
-
fs_1.default.mkdirSync(skillsDir, { recursive: true });
|
|
241
|
-
// Write extension manifest (we own this file)
|
|
242
|
-
const manifestPath = path_1.default.join(extensionDir, 'gemini-extension.json');
|
|
243
|
-
fs_1.default.writeFileSync(manifestPath, JSON.stringify({ name: AGENTBREW_EXTENSION_NAME, version: package_json_1.default.version }, null, 2), 'utf-8');
|
|
244
|
-
// Enable extension in extension-enablement.json
|
|
245
|
-
_enableGeminiExtension(geminiDir);
|
|
246
|
-
const state = loadSyncedState(brewRoot);
|
|
247
|
-
return symlinkSkills(skills, skillsDir, state, 'gemini', brewRoot);
|
|
199
|
+
// ─── Claude Code ────────────────────────────────────────────────────────────
|
|
200
|
+
function syncSkillsToClaudeCode(skills, brewRoot) {
|
|
201
|
+
return syncSkillsToAgent(agent_registry_1.AGENT_SKILL_REGISTRY.find(a => a.key === 'claude'), skills, brewRoot);
|
|
248
202
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
* agentbrew extension entry from extension-enablement.json.
|
|
252
|
-
*/
|
|
253
|
-
function unsyncSkillsFromGeminiCLI(brewRoot) {
|
|
254
|
-
const geminiDir = path_1.default.join(os_1.default.homedir(), '.gemini');
|
|
255
|
-
const extensionDir = path_1.default.join(geminiDir, 'extensions', AGENTBREW_EXTENSION_NAME);
|
|
256
|
-
const skillsDir = path_1.default.join(extensionDir, 'skills');
|
|
257
|
-
const state = loadSyncedState(brewRoot);
|
|
258
|
-
const results = removeTrackedSymlinks(state.gemini, skillsDir);
|
|
259
|
-
// Clean up extension dir (best-effort; ignores non-empty)
|
|
260
|
-
try {
|
|
261
|
-
fs_1.default.rmSync(path_1.default.join(extensionDir, 'gemini-extension.json'), { force: true });
|
|
262
|
-
}
|
|
263
|
-
catch { }
|
|
264
|
-
try {
|
|
265
|
-
fs_1.default.rmdirSync(skillsDir);
|
|
266
|
-
}
|
|
267
|
-
catch { }
|
|
268
|
-
try {
|
|
269
|
-
fs_1.default.rmdirSync(extensionDir);
|
|
270
|
-
}
|
|
271
|
-
catch { }
|
|
272
|
-
_disableGeminiExtension(geminiDir);
|
|
273
|
-
state.gemini = [];
|
|
274
|
-
saveSyncedState(state, brewRoot);
|
|
275
|
-
return results;
|
|
203
|
+
function unsyncSkillsFromClaudeCode(brewRoot) {
|
|
204
|
+
return unsyncSkillsFromAgent(agent_registry_1.AGENT_SKILL_REGISTRY.find(a => a.key === 'claude'), brewRoot);
|
|
276
205
|
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
try {
|
|
281
|
-
data = JSON.parse(fs_1.default.readFileSync(enablementPath, 'utf-8'));
|
|
282
|
-
}
|
|
283
|
-
catch { }
|
|
284
|
-
if (!data[AGENTBREW_EXTENSION_NAME]) {
|
|
285
|
-
data[AGENTBREW_EXTENSION_NAME] = { overrides: [`${os_1.default.homedir()}/*`] };
|
|
286
|
-
fs_1.default.writeFileSync(enablementPath, JSON.stringify(data, null, 2), 'utf-8');
|
|
287
|
-
}
|
|
206
|
+
// ─── Gemini CLI ───────────────────────────────────────────────────────────────
|
|
207
|
+
function syncSkillsToGeminiCLI(skills, brewRoot) {
|
|
208
|
+
return syncSkillsToAgent(agent_registry_1.AGENT_SKILL_REGISTRY.find(a => a.key === 'gemini'), skills, brewRoot);
|
|
288
209
|
}
|
|
289
|
-
function
|
|
290
|
-
|
|
291
|
-
try {
|
|
292
|
-
const data = JSON.parse(fs_1.default.readFileSync(enablementPath, 'utf-8'));
|
|
293
|
-
if (AGENTBREW_EXTENSION_NAME in data) {
|
|
294
|
-
delete data[AGENTBREW_EXTENSION_NAME];
|
|
295
|
-
if (Object.keys(data).length === 0) {
|
|
296
|
-
fs_1.default.rmSync(enablementPath, { force: true });
|
|
297
|
-
}
|
|
298
|
-
else {
|
|
299
|
-
fs_1.default.writeFileSync(enablementPath, JSON.stringify(data, null, 2), 'utf-8');
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
catch { }
|
|
210
|
+
function unsyncSkillsFromGeminiCLI(brewRoot) {
|
|
211
|
+
return unsyncSkillsFromAgent(agent_registry_1.AGENT_SKILL_REGISTRY.find(a => a.key === 'gemini'), brewRoot);
|
|
304
212
|
}
|
|
305
|
-
// ─── Windsurf
|
|
306
|
-
/**
|
|
307
|
-
* Symlinks each skill directory into ~/.codeium/windsurf/skills/<pkgName>-<skillName>
|
|
308
|
-
* so Windsurf can discover them.
|
|
309
|
-
*/
|
|
213
|
+
// ─── Windsurf ─────────────────────────────────────────────────────────────────
|
|
310
214
|
function syncSkillsToWindsurf(skills, brewRoot) {
|
|
311
|
-
|
|
312
|
-
if (!fs_1.default.existsSync(windsurfDir))
|
|
313
|
-
return [];
|
|
314
|
-
const skillsDir = path_1.default.join(windsurfDir, 'skills');
|
|
315
|
-
fs_1.default.mkdirSync(skillsDir, { recursive: true });
|
|
316
|
-
const state = loadSyncedState(brewRoot);
|
|
317
|
-
return symlinkSkills(skills, skillsDir, state, 'windsurf', brewRoot);
|
|
215
|
+
return syncSkillsToAgent(agent_registry_1.AGENT_SKILL_REGISTRY.find(a => a.key === 'windsurf'), skills, brewRoot);
|
|
318
216
|
}
|
|
319
|
-
/**
|
|
320
|
-
* Removes all Windsurf skill symlinks previously created by syncSkillsToWindsurf.
|
|
321
|
-
*/
|
|
322
217
|
function unsyncSkillsFromWindsurf(brewRoot) {
|
|
323
|
-
|
|
324
|
-
const state = loadSyncedState(brewRoot);
|
|
325
|
-
const results = removeTrackedSymlinks(state.windsurf, skillsDir);
|
|
326
|
-
state.windsurf = [];
|
|
327
|
-
saveSyncedState(state, brewRoot);
|
|
328
|
-
return results;
|
|
218
|
+
return unsyncSkillsFromAgent(agent_registry_1.AGENT_SKILL_REGISTRY.find(a => a.key === 'windsurf'), brewRoot);
|
|
329
219
|
}
|
|
330
|
-
// ─── Antigravity CLI
|
|
331
|
-
/**
|
|
332
|
-
* Symlinks each skill directory into ~/.gemini/antigravity-cli/skills/<pkgName>-<skillName>
|
|
333
|
-
* so Antigravity CLI can auto-discover them.
|
|
334
|
-
*/
|
|
220
|
+
// ─── Antigravity CLI ──────────────────────────────────────────────────────────
|
|
335
221
|
function syncSkillsToAntigravityCLI(skills, brewRoot) {
|
|
336
|
-
|
|
337
|
-
if (!fs_1.default.existsSync(antigravityDir))
|
|
338
|
-
return [];
|
|
339
|
-
const skillsDir = path_1.default.join(antigravityDir, 'skills');
|
|
340
|
-
fs_1.default.mkdirSync(skillsDir, { recursive: true });
|
|
341
|
-
const state = loadSyncedState(brewRoot);
|
|
342
|
-
return symlinkSkills(skills, skillsDir, state, 'antigravity', brewRoot);
|
|
222
|
+
return syncSkillsToAgent(agent_registry_1.AGENT_SKILL_REGISTRY.find(a => a.key === 'antigravity'), skills, brewRoot);
|
|
343
223
|
}
|
|
344
|
-
/**
|
|
345
|
-
* Removes all Antigravity CLI skill symlinks previously created by syncSkillsToAntigravityCLI.
|
|
346
|
-
*/
|
|
347
224
|
function unsyncSkillsFromAntigravityCLI(brewRoot) {
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
225
|
+
return unsyncSkillsFromAgent(agent_registry_1.AGENT_SKILL_REGISTRY.find(a => a.key === 'antigravity'), brewRoot);
|
|
226
|
+
}
|
|
227
|
+
// ─── Kiro ─────────────────────────────────────────────────────────────────────
|
|
228
|
+
function syncSkillsToKiro(skills, brewRoot) {
|
|
229
|
+
return syncSkillsToAgent(agent_registry_1.AGENT_SKILL_REGISTRY.find(a => a.key === 'kiro'), skills, brewRoot);
|
|
230
|
+
}
|
|
231
|
+
function unsyncSkillsFromKiro(brewRoot) {
|
|
232
|
+
return unsyncSkillsFromAgent(agent_registry_1.AGENT_SKILL_REGISTRY.find(a => a.key === 'kiro'), brewRoot);
|
|
354
233
|
}
|
|
355
234
|
// ─── Orphan cleanup ──────────────────────────────────────────────────────────
|
|
356
235
|
/**
|
|
@@ -358,24 +237,21 @@ function unsyncSkillsFromAntigravityCLI(brewRoot) {
|
|
|
358
237
|
* Call after `agentbrew uninstall` to prevent stale entries.
|
|
359
238
|
*/
|
|
360
239
|
function cleanOrphanSkills(brewRoot) {
|
|
361
|
-
const state = loadSyncedState(brewRoot);
|
|
240
|
+
const state = (0, sync_state_1.loadSyncedState)(brewRoot);
|
|
362
241
|
const results = [];
|
|
363
|
-
const
|
|
364
|
-
|
|
365
|
-
{ key: 'gemini', dir: path_1.default.join(os_1.default.homedir(), '.gemini', 'extensions', AGENTBREW_EXTENSION_NAME, 'skills') },
|
|
366
|
-
{ key: 'windsurf', dir: path_1.default.join(os_1.default.homedir(), '.codeium', 'windsurf', 'skills') },
|
|
367
|
-
{ key: 'antigravity', dir: path_1.default.join(os_1.default.homedir(), '.gemini', 'antigravity-cli', 'skills') },
|
|
368
|
-
{ key: 'kiro', dir: path_1.default.join(os_1.default.homedir(), '.kiro', 'skills') },
|
|
369
|
-
];
|
|
370
|
-
for (const { key, dir } of agentDirs) {
|
|
242
|
+
for (const agent of agent_registry_1.AGENT_SKILL_REGISTRY) {
|
|
243
|
+
const dir = agent.skillsDir();
|
|
371
244
|
const remaining = [];
|
|
372
|
-
for (const entryName of state[key]) {
|
|
245
|
+
for (const entryName of state[agent.key]) {
|
|
373
246
|
const entryPath = path_1.default.join(dir, entryName);
|
|
374
247
|
let symlinkTarget = null;
|
|
375
248
|
try {
|
|
376
249
|
symlinkTarget = fs_1.default.readlinkSync(entryPath);
|
|
377
250
|
}
|
|
378
|
-
catch {
|
|
251
|
+
catch (e) {
|
|
252
|
+
if (e.code !== 'ENOENT')
|
|
253
|
+
logger_1.Logger.warn(`Unexpected error checking ${entryName}: ${e.message}`);
|
|
254
|
+
}
|
|
379
255
|
if (symlinkTarget !== null && !fs_1.default.existsSync(symlinkTarget)) {
|
|
380
256
|
try {
|
|
381
257
|
fs_1.default.rmSync(entryPath, { force: true });
|
|
@@ -390,7 +266,7 @@ function cleanOrphanSkills(brewRoot) {
|
|
|
390
266
|
remaining.push(entryName);
|
|
391
267
|
}
|
|
392
268
|
}
|
|
393
|
-
state[key] = remaining;
|
|
269
|
+
state[agent.key] = remaining;
|
|
394
270
|
}
|
|
395
271
|
// Handle Cursor index file: parse referenced SKILL.md paths and remove the file if any are stale
|
|
396
272
|
if (state.cursor) {
|
|
@@ -418,190 +294,29 @@ function cleanOrphanSkills(brewRoot) {
|
|
|
418
294
|
}
|
|
419
295
|
}
|
|
420
296
|
}
|
|
421
|
-
if (state.codexMcp
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
saveSyncedState(state, brewRoot);
|
|
428
|
-
return results;
|
|
429
|
-
}
|
|
430
|
-
// ─── Cursor MCP server registration ─────────────────────────────────────────
|
|
431
|
-
const CURSOR_MCP_ENTRY = 'agentbrew';
|
|
432
|
-
/**
|
|
433
|
-
* Adds agentbrew to ~/.cursor/mcp.json so Cursor can discover MCP tools directly.
|
|
434
|
-
* Merges into any existing config without disturbing other servers.
|
|
435
|
-
*/
|
|
436
|
-
function syncMcpServerToCursor(brewRoot) {
|
|
437
|
-
const cursorDir = path_1.default.join(os_1.default.homedir(), '.cursor');
|
|
438
|
-
if (!fs_1.default.existsSync(cursorDir))
|
|
439
|
-
return [];
|
|
440
|
-
const mcpJsonPath = path_1.default.join(cursorDir, 'mcp.json');
|
|
441
|
-
const entryName = 'agentbrew (Cursor MCP)';
|
|
442
|
-
let config = {};
|
|
443
|
-
try {
|
|
444
|
-
config = JSON.parse(fs_1.default.readFileSync(mcpJsonPath, 'utf-8'));
|
|
445
|
-
}
|
|
446
|
-
catch { }
|
|
447
|
-
const mcpServers = config.mcpServers ?? {};
|
|
448
|
-
const existing = mcpServers[CURSOR_MCP_ENTRY];
|
|
449
|
-
if (existing?.command === 'agentbrew') {
|
|
450
|
-
const state = loadSyncedState(brewRoot);
|
|
451
|
-
state.cursorMcp = true;
|
|
452
|
-
saveSyncedState(state, brewRoot);
|
|
453
|
-
return [{ entryName, status: 'already_exists', path: mcpJsonPath }];
|
|
454
|
-
}
|
|
455
|
-
config.mcpServers = { ...mcpServers, [CURSOR_MCP_ENTRY]: { command: 'agentbrew' } };
|
|
456
|
-
try {
|
|
457
|
-
fs_1.default.writeFileSync(mcpJsonPath, JSON.stringify(config, null, 2), 'utf-8');
|
|
458
|
-
const state = loadSyncedState(brewRoot);
|
|
459
|
-
state.cursorMcp = true;
|
|
460
|
-
saveSyncedState(state, brewRoot);
|
|
461
|
-
return [{ entryName, status: 'linked', path: mcpJsonPath }];
|
|
462
|
-
}
|
|
463
|
-
catch (e) {
|
|
464
|
-
return [{ entryName, status: 'error', note: e.message }];
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
/**
|
|
468
|
-
* Removes the agentbrew entry from ~/.cursor/mcp.json.
|
|
469
|
-
* Leaves other servers intact; removes the file only if it becomes empty.
|
|
470
|
-
*/
|
|
471
|
-
function unsyncMcpServerFromCursor(brewRoot) {
|
|
472
|
-
const state = loadSyncedState(brewRoot);
|
|
473
|
-
if (!state.cursorMcp)
|
|
474
|
-
return [];
|
|
475
|
-
const mcpJsonPath = path_1.default.join(os_1.default.homedir(), '.cursor', 'mcp.json');
|
|
476
|
-
const entryName = 'agentbrew (Cursor MCP)';
|
|
477
|
-
let config = {};
|
|
478
|
-
try {
|
|
479
|
-
config = JSON.parse(fs_1.default.readFileSync(mcpJsonPath, 'utf-8'));
|
|
480
|
-
}
|
|
481
|
-
catch {
|
|
482
|
-
state.cursorMcp = false;
|
|
483
|
-
saveSyncedState(state, brewRoot);
|
|
484
|
-
return [{ entryName, status: 'skipped', note: 'Not found' }];
|
|
485
|
-
}
|
|
486
|
-
if (!config.mcpServers?.[CURSOR_MCP_ENTRY]) {
|
|
487
|
-
state.cursorMcp = false;
|
|
488
|
-
saveSyncedState(state, brewRoot);
|
|
489
|
-
return [{ entryName, status: 'skipped', note: 'Not found' }];
|
|
490
|
-
}
|
|
491
|
-
delete config.mcpServers[CURSOR_MCP_ENTRY];
|
|
492
|
-
if (Object.keys(config.mcpServers).length === 0)
|
|
493
|
-
delete config.mcpServers;
|
|
494
|
-
try {
|
|
495
|
-
if (Object.keys(config).length === 0) {
|
|
496
|
-
fs_1.default.rmSync(mcpJsonPath, { force: true });
|
|
497
|
-
}
|
|
498
|
-
else {
|
|
499
|
-
fs_1.default.writeFileSync(mcpJsonPath, JSON.stringify(config, null, 2), 'utf-8');
|
|
500
|
-
}
|
|
501
|
-
state.cursorMcp = false;
|
|
502
|
-
saveSyncedState(state, brewRoot);
|
|
503
|
-
return [{ entryName, status: 'removed', path: mcpJsonPath }];
|
|
504
|
-
}
|
|
505
|
-
catch (e) {
|
|
506
|
-
return [{ entryName, status: 'error', note: e.message }];
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
// ─── Codex MCP server registration ──────────────────────────────────────────
|
|
510
|
-
function _removeTomlSection(content, sectionHeader) {
|
|
511
|
-
const lines = content.split('\n');
|
|
512
|
-
const headerLine = `[${sectionHeader}]`;
|
|
513
|
-
const startIdx = lines.findIndex(l => l.trim() === headerLine);
|
|
514
|
-
if (startIdx === -1)
|
|
515
|
-
return content;
|
|
516
|
-
let endIdx = lines.length;
|
|
517
|
-
for (let i = startIdx + 1; i < lines.length; i++) {
|
|
518
|
-
if (lines[i].trim().startsWith('[')) {
|
|
519
|
-
endIdx = i;
|
|
520
|
-
break;
|
|
297
|
+
if (state.codexMcp) {
|
|
298
|
+
const codexConfig = path_1.default.join(os_1.default.homedir(), '.codex', 'config.toml');
|
|
299
|
+
let codexEntryPresent = false;
|
|
300
|
+
try {
|
|
301
|
+
const raw = fs_1.default.readFileSync(codexConfig, 'utf-8');
|
|
302
|
+
codexEntryPresent = toml.parse(raw)?.mcp_servers?.agentbrew?.command === 'agentbrew';
|
|
521
303
|
}
|
|
304
|
+
catch { }
|
|
305
|
+
if (!codexEntryPresent)
|
|
306
|
+
state.codexMcp = false;
|
|
522
307
|
}
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
}
|
|
529
|
-
/**
|
|
530
|
-
* Adds agentbrew to ~/.codex/config.toml so Codex CLI can discover MCP tools directly.
|
|
531
|
-
* Appends the [mcp_servers.agentbrew] section without disturbing existing content.
|
|
532
|
-
*/
|
|
533
|
-
function syncMcpServerToCodex(brewRoot) {
|
|
534
|
-
const codexDir = path_1.default.join(os_1.default.homedir(), '.codex');
|
|
535
|
-
if (!fs_1.default.existsSync(codexDir))
|
|
536
|
-
return [];
|
|
537
|
-
const configPath = path_1.default.join(codexDir, 'config.toml');
|
|
538
|
-
const entryName = 'agentbrew (Codex MCP)';
|
|
539
|
-
let raw = '';
|
|
540
|
-
try {
|
|
541
|
-
raw = fs_1.default.readFileSync(configPath, 'utf-8');
|
|
542
|
-
}
|
|
543
|
-
catch { }
|
|
544
|
-
// Check if already registered with the right command
|
|
545
|
-
try {
|
|
546
|
-
const parsed = toml.parse(raw);
|
|
547
|
-
if (parsed?.mcp_servers?.agentbrew?.command === 'agentbrew') {
|
|
548
|
-
const state = loadSyncedState(brewRoot);
|
|
549
|
-
state.codexMcp = true;
|
|
550
|
-
saveSyncedState(state, brewRoot);
|
|
551
|
-
return [{ entryName, status: 'already_exists', path: configPath }];
|
|
308
|
+
if (state.kiroMcp) {
|
|
309
|
+
const kiroConfig = path_1.default.join(os_1.default.homedir(), '.kiro', 'settings', 'mcp.json');
|
|
310
|
+
let kiroEntryPresent = false;
|
|
311
|
+
try {
|
|
312
|
+
kiroEntryPresent = JSON.parse(fs_1.default.readFileSync(kiroConfig, 'utf-8'))?.mcpServers?.agentbrew?.command === 'agentbrew';
|
|
552
313
|
}
|
|
314
|
+
catch { }
|
|
315
|
+
if (!kiroEntryPresent)
|
|
316
|
+
state.kiroMcp = false;
|
|
553
317
|
}
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
const cleaned = _removeTomlSection(raw, 'mcp_servers.agentbrew');
|
|
557
|
-
const sep = cleaned.length > 0 && !cleaned.endsWith('\n') ? '\n' : '';
|
|
558
|
-
const newContent = cleaned + sep + '\n[mcp_servers.agentbrew]\ncommand = "agentbrew"\n';
|
|
559
|
-
try {
|
|
560
|
-
fs_1.default.writeFileSync(configPath, newContent, 'utf-8');
|
|
561
|
-
const state = loadSyncedState(brewRoot);
|
|
562
|
-
state.codexMcp = true;
|
|
563
|
-
saveSyncedState(state, brewRoot);
|
|
564
|
-
return [{ entryName, status: 'linked', path: configPath }];
|
|
565
|
-
}
|
|
566
|
-
catch (e) {
|
|
567
|
-
return [{ entryName, status: 'error', note: e.message }];
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
/**
|
|
571
|
-
* Removes the agentbrew entry from ~/.codex/config.toml.
|
|
572
|
-
* Leaves the file in place; other sections are untouched.
|
|
573
|
-
*/
|
|
574
|
-
function unsyncMcpServerFromCodex(brewRoot) {
|
|
575
|
-
const state = loadSyncedState(brewRoot);
|
|
576
|
-
if (!state.codexMcp)
|
|
577
|
-
return [];
|
|
578
|
-
const configPath = path_1.default.join(os_1.default.homedir(), '.codex', 'config.toml');
|
|
579
|
-
const entryName = 'agentbrew (Codex MCP)';
|
|
580
|
-
let raw;
|
|
581
|
-
try {
|
|
582
|
-
raw = fs_1.default.readFileSync(configPath, 'utf-8');
|
|
583
|
-
}
|
|
584
|
-
catch {
|
|
585
|
-
state.codexMcp = false;
|
|
586
|
-
saveSyncedState(state, brewRoot);
|
|
587
|
-
return [{ entryName, status: 'skipped', note: 'Not found' }];
|
|
588
|
-
}
|
|
589
|
-
const cleaned = _removeTomlSection(raw, 'mcp_servers.agentbrew');
|
|
590
|
-
if (cleaned === raw) {
|
|
591
|
-
state.codexMcp = false;
|
|
592
|
-
saveSyncedState(state, brewRoot);
|
|
593
|
-
return [{ entryName, status: 'skipped', note: 'Not found' }];
|
|
594
|
-
}
|
|
595
|
-
try {
|
|
596
|
-
const trimmed = cleaned.trimEnd();
|
|
597
|
-
fs_1.default.writeFileSync(configPath, trimmed ? trimmed + '\n' : '', 'utf-8');
|
|
598
|
-
state.codexMcp = false;
|
|
599
|
-
saveSyncedState(state, brewRoot);
|
|
600
|
-
return [{ entryName, status: 'removed', path: configPath }];
|
|
601
|
-
}
|
|
602
|
-
catch (e) {
|
|
603
|
-
return [{ entryName, status: 'error', note: e.message }];
|
|
604
|
-
}
|
|
318
|
+
(0, sync_state_1.saveSyncedState)(state, brewRoot);
|
|
319
|
+
return results;
|
|
605
320
|
}
|
|
606
321
|
// ─── Cursor ──────────────────────────────────────────────────────────────────
|
|
607
322
|
function buildCursorSkillsIndex(skills) {
|
|
@@ -638,11 +353,11 @@ function syncSkillsToCursor(skills, brewRoot) {
|
|
|
638
353
|
fs_1.default.mkdirSync(rulesDir, { recursive: true });
|
|
639
354
|
const indexPath = path_1.default.join(rulesDir, CURSOR_SKILLS_INDEX_FILE);
|
|
640
355
|
const content = buildCursorSkillsIndex(skills);
|
|
641
|
-
const state = loadSyncedState(brewRoot);
|
|
356
|
+
const state = (0, sync_state_1.loadSyncedState)(brewRoot);
|
|
642
357
|
try {
|
|
643
358
|
fs_1.default.writeFileSync(indexPath, content, 'utf-8');
|
|
644
359
|
state.cursor = true;
|
|
645
|
-
saveSyncedState(state, brewRoot);
|
|
360
|
+
(0, sync_state_1.saveSyncedState)(state, brewRoot);
|
|
646
361
|
return [{ entryName: CURSOR_SKILLS_INDEX_FILE, status: 'linked', path: indexPath }];
|
|
647
362
|
}
|
|
648
363
|
catch (e) {
|
|
@@ -653,7 +368,7 @@ function syncSkillsToCursor(skills, brewRoot) {
|
|
|
653
368
|
* Removes the AgentBrew skills index file from ~/.cursor/rules/.
|
|
654
369
|
*/
|
|
655
370
|
function unsyncSkillsFromCursor(brewRoot) {
|
|
656
|
-
const state = loadSyncedState(brewRoot);
|
|
371
|
+
const state = (0, sync_state_1.loadSyncedState)(brewRoot);
|
|
657
372
|
if (!state.cursor)
|
|
658
373
|
return [];
|
|
659
374
|
const indexPath = path_1.default.join(os_1.default.homedir(), '.cursor', 'rules', CURSOR_SKILLS_INDEX_FILE);
|
|
@@ -662,322 +377,43 @@ function unsyncSkillsFromCursor(brewRoot) {
|
|
|
662
377
|
fs_1.default.lstatSync(indexPath);
|
|
663
378
|
exists = true;
|
|
664
379
|
}
|
|
665
|
-
catch {
|
|
380
|
+
catch (e) {
|
|
381
|
+
if (e.code !== 'ENOENT')
|
|
382
|
+
logger_1.Logger.warn(`Unexpected error checking ${CURSOR_SKILLS_INDEX_FILE}: ${e.message}`);
|
|
383
|
+
}
|
|
666
384
|
if (!exists) {
|
|
667
385
|
state.cursor = false;
|
|
668
|
-
saveSyncedState(state, brewRoot);
|
|
386
|
+
(0, sync_state_1.saveSyncedState)(state, brewRoot);
|
|
669
387
|
return [{ entryName: CURSOR_SKILLS_INDEX_FILE, status: 'skipped', note: 'Not found' }];
|
|
670
388
|
}
|
|
671
389
|
try {
|
|
672
390
|
fs_1.default.rmSync(indexPath, { force: true });
|
|
673
391
|
state.cursor = false;
|
|
674
|
-
saveSyncedState(state, brewRoot);
|
|
392
|
+
(0, sync_state_1.saveSyncedState)(state, brewRoot);
|
|
675
393
|
return [{ entryName: CURSOR_SKILLS_INDEX_FILE, status: 'removed', path: indexPath }];
|
|
676
394
|
}
|
|
677
395
|
catch (e) {
|
|
678
396
|
return [{ entryName: CURSOR_SKILLS_INDEX_FILE, status: 'error', note: e.message }];
|
|
679
397
|
}
|
|
680
398
|
}
|
|
681
|
-
// ───
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
function
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
function
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
const results = removeTrackedSymlinks(state.kiro, skillsDir);
|
|
702
|
-
state.kiro = [];
|
|
703
|
-
saveSyncedState(state, brewRoot);
|
|
704
|
-
return results;
|
|
705
|
-
}
|
|
706
|
-
// ─── Kiro MCP server registration ────────────────────────────────────────────
|
|
707
|
-
const KIRO_MCP_ENTRY = 'agentbrew';
|
|
708
|
-
/**
|
|
709
|
-
* Adds agentbrew to ~/.kiro/settings/mcp.json so Kiro can discover MCP tools directly.
|
|
710
|
-
* Merges into any existing config without disturbing other servers.
|
|
711
|
-
*/
|
|
712
|
-
function syncMcpServerToKiro(brewRoot) {
|
|
713
|
-
const kiroDir = path_1.default.join(os_1.default.homedir(), '.kiro');
|
|
714
|
-
if (!fs_1.default.existsSync(kiroDir))
|
|
715
|
-
return [];
|
|
716
|
-
const settingsDir = path_1.default.join(kiroDir, 'settings');
|
|
717
|
-
fs_1.default.mkdirSync(settingsDir, { recursive: true });
|
|
718
|
-
const mcpJsonPath = path_1.default.join(settingsDir, 'mcp.json');
|
|
719
|
-
const entryName = 'agentbrew (Kiro MCP)';
|
|
720
|
-
let config = {};
|
|
721
|
-
try {
|
|
722
|
-
config = JSON.parse(fs_1.default.readFileSync(mcpJsonPath, 'utf-8'));
|
|
723
|
-
}
|
|
724
|
-
catch { }
|
|
725
|
-
const mcpServers = config.mcpServers ?? {};
|
|
726
|
-
const existing = mcpServers[KIRO_MCP_ENTRY];
|
|
727
|
-
if (existing?.command === 'agentbrew') {
|
|
728
|
-
const state = loadSyncedState(brewRoot);
|
|
729
|
-
state.kiroMcp = true;
|
|
730
|
-
saveSyncedState(state, brewRoot);
|
|
731
|
-
return [{ entryName, status: 'already_exists', path: mcpJsonPath }];
|
|
732
|
-
}
|
|
733
|
-
config.mcpServers = { ...mcpServers, [KIRO_MCP_ENTRY]: { command: 'agentbrew' } };
|
|
734
|
-
try {
|
|
735
|
-
fs_1.default.writeFileSync(mcpJsonPath, JSON.stringify(config, null, 2), 'utf-8');
|
|
736
|
-
const state = loadSyncedState(brewRoot);
|
|
737
|
-
state.kiroMcp = true;
|
|
738
|
-
saveSyncedState(state, brewRoot);
|
|
739
|
-
return [{ entryName, status: 'linked', path: mcpJsonPath }];
|
|
740
|
-
}
|
|
741
|
-
catch (e) {
|
|
742
|
-
return [{ entryName, status: 'error', note: e.message }];
|
|
743
|
-
}
|
|
744
|
-
}
|
|
745
|
-
/**
|
|
746
|
-
* Removes the agentbrew entry from ~/.kiro/settings/mcp.json.
|
|
747
|
-
* Leaves other servers intact; removes the file only if it becomes empty.
|
|
748
|
-
*/
|
|
749
|
-
function unsyncMcpServerFromKiro(brewRoot) {
|
|
750
|
-
const state = loadSyncedState(brewRoot);
|
|
751
|
-
if (!state.kiroMcp)
|
|
752
|
-
return [];
|
|
753
|
-
const mcpJsonPath = path_1.default.join(os_1.default.homedir(), '.kiro', 'settings', 'mcp.json');
|
|
754
|
-
const entryName = 'agentbrew (Kiro MCP)';
|
|
755
|
-
let config = {};
|
|
756
|
-
try {
|
|
757
|
-
config = JSON.parse(fs_1.default.readFileSync(mcpJsonPath, 'utf-8'));
|
|
758
|
-
}
|
|
759
|
-
catch {
|
|
760
|
-
state.kiroMcp = false;
|
|
761
|
-
saveSyncedState(state, brewRoot);
|
|
762
|
-
return [{ entryName, status: 'skipped', note: 'Not found' }];
|
|
763
|
-
}
|
|
764
|
-
if (!config.mcpServers?.[KIRO_MCP_ENTRY]) {
|
|
765
|
-
state.kiroMcp = false;
|
|
766
|
-
saveSyncedState(state, brewRoot);
|
|
767
|
-
return [{ entryName, status: 'skipped', note: 'Not found' }];
|
|
768
|
-
}
|
|
769
|
-
delete config.mcpServers[KIRO_MCP_ENTRY];
|
|
770
|
-
if (Object.keys(config.mcpServers).length === 0)
|
|
771
|
-
delete config.mcpServers;
|
|
772
|
-
try {
|
|
773
|
-
if (Object.keys(config).length === 0) {
|
|
774
|
-
fs_1.default.rmSync(mcpJsonPath, { force: true });
|
|
775
|
-
}
|
|
776
|
-
else {
|
|
777
|
-
fs_1.default.writeFileSync(mcpJsonPath, JSON.stringify(config, null, 2), 'utf-8');
|
|
778
|
-
}
|
|
779
|
-
state.kiroMcp = false;
|
|
780
|
-
saveSyncedState(state, brewRoot);
|
|
781
|
-
return [{ entryName, status: 'removed', path: mcpJsonPath }];
|
|
782
|
-
}
|
|
783
|
-
catch (e) {
|
|
784
|
-
return [{ entryName, status: 'error', note: e.message }];
|
|
785
|
-
}
|
|
786
|
-
}
|
|
787
|
-
// ─── Instruction sync (unchanged) ───────────────────────────────────────────
|
|
788
|
-
exports.MARKER_START = '<!-- agentbrew:shared:start -->';
|
|
789
|
-
exports.MARKER_END = '<!-- agentbrew:shared:end -->';
|
|
790
|
-
exports.INSTRUCTIONS_FILE = 'INSTRUCTIONS.md';
|
|
791
|
-
const EXAMPLE_INSTRUCTIONS = `# AgentBrew Shared Instructions
|
|
792
|
-
|
|
793
|
-
These instructions are shared across all your AI agents via AgentBrew.
|
|
794
|
-
Edit this file and run \`agentbrew sync\` to push updates to all agent configs.
|
|
795
|
-
|
|
796
|
-
## Example: API Usage Policy
|
|
797
|
-
- Always use Context7 (the \`context7\` MCP tool) to fetch live API documentation
|
|
798
|
-
before writing code that calls an external library. This prevents using stale or
|
|
799
|
-
hallucinated function signatures.
|
|
800
|
-
|
|
801
|
-
## Notes
|
|
802
|
-
- This file is global. For project-specific context, add it directly to the
|
|
803
|
-
project's CLAUDE.md or GEMINI.md — or reference project docs from within this file.
|
|
804
|
-
`;
|
|
805
|
-
function getDefaultTargets() {
|
|
806
|
-
const home = os_1.default.homedir();
|
|
807
|
-
return [
|
|
808
|
-
{
|
|
809
|
-
name: 'Claude Code',
|
|
810
|
-
configPath: path_1.default.join(home, '.claude', 'CLAUDE.md'),
|
|
811
|
-
isFileOwned: false,
|
|
812
|
-
},
|
|
813
|
-
{
|
|
814
|
-
name: 'Gemini CLI',
|
|
815
|
-
configPath: path_1.default.join(home, '.gemini', 'GEMINI.md'),
|
|
816
|
-
isFileOwned: false,
|
|
817
|
-
},
|
|
818
|
-
{
|
|
819
|
-
name: 'OpenAI Codex CLI',
|
|
820
|
-
// Codex CLI's canonical instruction file is AGENTS.md (instructions.md is a legacy fallback)
|
|
821
|
-
configPath: path_1.default.join(home, '.codex', 'AGENTS.md'),
|
|
822
|
-
isFileOwned: false,
|
|
823
|
-
},
|
|
824
|
-
{
|
|
825
|
-
name: 'Cursor',
|
|
826
|
-
// Cursor "User Rules" directory (Cursor 0.47+): each .md file in this dir is a global rule.
|
|
827
|
-
// We own this specific file entirely — no markers needed.
|
|
828
|
-
configPath: path_1.default.join(home, '.cursor', 'rules', 'agentbrew-shared.md'),
|
|
829
|
-
isFileOwned: true,
|
|
830
|
-
agentRootDir: path_1.default.join(home, '.cursor'),
|
|
831
|
-
},
|
|
832
|
-
{
|
|
833
|
-
name: 'Windsurf',
|
|
834
|
-
configPath: path_1.default.join(home, '.codeium', 'windsurf', 'memories', 'global_rules.md'),
|
|
835
|
-
isFileOwned: false,
|
|
836
|
-
},
|
|
837
|
-
{
|
|
838
|
-
name: 'Kiro',
|
|
839
|
-
// Steering files in ~/.kiro/steering/ are auto-loaded in every Kiro interaction.
|
|
840
|
-
// We own this file entirely and must place the frontmatter at the very top.
|
|
841
|
-
configPath: path_1.default.join(home, '.kiro', 'steering', 'agentbrew-shared.md'),
|
|
842
|
-
isFileOwned: true,
|
|
843
|
-
frontmatter: '---\ninclusion: always\n---',
|
|
844
|
-
agentRootDir: path_1.default.join(home, '.kiro'),
|
|
845
|
-
},
|
|
846
|
-
];
|
|
847
|
-
}
|
|
848
|
-
function getInstructionsPath(brewRoot) {
|
|
849
|
-
return path_1.default.join(brewRoot ?? (0, config_1.getBrewRoot)(), exports.INSTRUCTIONS_FILE);
|
|
850
|
-
}
|
|
851
|
-
function buildInjectedSection(content) {
|
|
852
|
-
const warning = `> ⚠️ Managed by AgentBrew. Edit \`~/.agentbrew/INSTRUCTIONS.md\` and run \`agentbrew sync\` to update.`;
|
|
853
|
-
return `${exports.MARKER_START}\n${warning}\n\n${content.trim()}\n${exports.MARKER_END}`;
|
|
854
|
-
}
|
|
855
|
-
/**
|
|
856
|
-
* Injects (or updates) the agentbrew section in a file.
|
|
857
|
-
* Creates the file and any parent directories if they don't exist.
|
|
858
|
-
*/
|
|
859
|
-
function injectIntoFile(filePath, content) {
|
|
860
|
-
const section = buildInjectedSection(content);
|
|
861
|
-
if (!fs_1.default.existsSync(filePath)) {
|
|
862
|
-
fs_1.default.mkdirSync(path_1.default.dirname(filePath), { recursive: true });
|
|
863
|
-
fs_1.default.writeFileSync(filePath, section + '\n', 'utf-8');
|
|
864
|
-
return 'created';
|
|
865
|
-
}
|
|
866
|
-
const existing = fs_1.default.readFileSync(filePath, 'utf-8');
|
|
867
|
-
const startIdx = existing.indexOf(exports.MARKER_START);
|
|
868
|
-
const endIdx = existing.indexOf(exports.MARKER_END);
|
|
869
|
-
if (startIdx !== -1 && endIdx !== -1) {
|
|
870
|
-
const replaced = existing.substring(0, startIdx) +
|
|
871
|
-
section +
|
|
872
|
-
existing.substring(endIdx + exports.MARKER_END.length);
|
|
873
|
-
if (replaced === existing)
|
|
874
|
-
return 'unchanged';
|
|
875
|
-
fs_1.default.writeFileSync(filePath, replaced, 'utf-8');
|
|
876
|
-
return 'updated';
|
|
877
|
-
}
|
|
878
|
-
// No markers yet — append to end
|
|
879
|
-
const appended = existing.trimEnd() + '\n\n' + section + '\n';
|
|
880
|
-
fs_1.default.writeFileSync(filePath, appended, 'utf-8');
|
|
881
|
-
return 'updated';
|
|
882
|
-
}
|
|
883
|
-
/**
|
|
884
|
-
* Removes the agentbrew section from a file, leaving surrounding content intact.
|
|
885
|
-
*/
|
|
886
|
-
function removeFromFile(filePath) {
|
|
887
|
-
if (!fs_1.default.existsSync(filePath))
|
|
888
|
-
return 'not_found';
|
|
889
|
-
const existing = fs_1.default.readFileSync(filePath, 'utf-8');
|
|
890
|
-
const startIdx = existing.indexOf(exports.MARKER_START);
|
|
891
|
-
const endIdx = existing.indexOf(exports.MARKER_END);
|
|
892
|
-
if (startIdx === -1 || endIdx === -1)
|
|
893
|
-
return 'no_section';
|
|
894
|
-
const before = existing.substring(0, startIdx).trimEnd();
|
|
895
|
-
const after = existing.substring(endIdx + exports.MARKER_END.length).trimStart();
|
|
896
|
-
let result = before;
|
|
897
|
-
if (after)
|
|
898
|
-
result += '\n\n' + after;
|
|
899
|
-
result = result.trimEnd() + '\n';
|
|
900
|
-
fs_1.default.writeFileSync(filePath, result, 'utf-8');
|
|
901
|
-
return 'removed';
|
|
902
|
-
}
|
|
903
|
-
/**
|
|
904
|
-
* Syncs ~/.agentbrew/INSTRUCTIONS.md into each target agent's global config file.
|
|
905
|
-
* Accepts an optional `targets` override (used in tests).
|
|
906
|
-
*/
|
|
907
|
-
function syncInstructions(targets, brewRoot) {
|
|
908
|
-
const instructionsPath = getInstructionsPath(brewRoot);
|
|
909
|
-
if (!fs_1.default.existsSync(instructionsPath)) {
|
|
910
|
-
fs_1.default.mkdirSync(path_1.default.dirname(instructionsPath), { recursive: true });
|
|
911
|
-
fs_1.default.writeFileSync(instructionsPath, EXAMPLE_INSTRUCTIONS, 'utf-8');
|
|
912
|
-
return [];
|
|
913
|
-
}
|
|
914
|
-
const content = fs_1.default.readFileSync(instructionsPath, 'utf-8');
|
|
915
|
-
const resolvedTargets = targets ?? getDefaultTargets();
|
|
916
|
-
const results = [];
|
|
917
|
-
for (const target of resolvedTargets) {
|
|
918
|
-
if (target.configPath === null) {
|
|
919
|
-
results.push({ agent: target.name, status: 'manual', note: target.manualInstructions });
|
|
920
|
-
continue;
|
|
921
|
-
}
|
|
922
|
-
if (target.isFileOwned) {
|
|
923
|
-
// Skip if the agent's root directory doesn't exist (agent not installed).
|
|
924
|
-
// Without this guard, mkdirSync below would create the directory unconditionally.
|
|
925
|
-
if (target.agentRootDir && !fs_1.default.existsSync(target.agentRootDir)) {
|
|
926
|
-
results.push({ agent: target.name, status: 'skipped', note: 'Agent not installed (config directory not found)' });
|
|
927
|
-
continue;
|
|
928
|
-
}
|
|
929
|
-
// We own this file entirely — write raw content, no markers needed.
|
|
930
|
-
// unsync deletes the file; there is no surrounding user content to delimit around.
|
|
931
|
-
const header = `> ⚠️ Managed by AgentBrew. Edit \`~/.agentbrew/INSTRUCTIONS.md\` and run \`agentbrew sync\` to update.\n`;
|
|
932
|
-
const prefix = target.frontmatter ? target.frontmatter + '\n' : '';
|
|
933
|
-
const fileContent = prefix + header + '\n' + content.trim() + '\n';
|
|
934
|
-
fs_1.default.mkdirSync(path_1.default.dirname(target.configPath), { recursive: true });
|
|
935
|
-
const existing = fs_1.default.existsSync(target.configPath)
|
|
936
|
-
? fs_1.default.readFileSync(target.configPath, 'utf-8')
|
|
937
|
-
: null;
|
|
938
|
-
if (existing === fileContent) {
|
|
939
|
-
results.push({ agent: target.name, status: 'unchanged', path: target.configPath });
|
|
940
|
-
}
|
|
941
|
-
else {
|
|
942
|
-
fs_1.default.writeFileSync(target.configPath, fileContent, 'utf-8');
|
|
943
|
-
results.push({ agent: target.name, status: existing !== null ? 'updated' : 'created', path: target.configPath });
|
|
944
|
-
}
|
|
945
|
-
continue;
|
|
946
|
-
}
|
|
947
|
-
// Skip agents that aren't installed (config parent dir absent)
|
|
948
|
-
if (!fs_1.default.existsSync(path_1.default.dirname(target.configPath))) {
|
|
949
|
-
results.push({ agent: target.name, status: 'skipped', note: 'Agent not installed (config directory not found)' });
|
|
950
|
-
continue;
|
|
951
|
-
}
|
|
952
|
-
const status = injectIntoFile(target.configPath, content);
|
|
953
|
-
results.push({ agent: target.name, status, path: target.configPath });
|
|
954
|
-
}
|
|
955
|
-
return results;
|
|
956
|
-
}
|
|
957
|
-
/**
|
|
958
|
-
* Removes the agentbrew section from all target agent config files.
|
|
959
|
-
*/
|
|
960
|
-
function unsyncInstructions(targets) {
|
|
961
|
-
const resolvedTargets = targets ?? getDefaultTargets();
|
|
962
|
-
const results = [];
|
|
963
|
-
for (const target of resolvedTargets) {
|
|
964
|
-
if (target.configPath === null) {
|
|
965
|
-
results.push({ agent: target.name, status: 'manual', note: 'Remove manually from your agent UI settings.' });
|
|
966
|
-
continue;
|
|
967
|
-
}
|
|
968
|
-
if (target.isFileOwned) {
|
|
969
|
-
if (fs_1.default.existsSync(target.configPath)) {
|
|
970
|
-
fs_1.default.rmSync(target.configPath);
|
|
971
|
-
results.push({ agent: target.name, status: 'removed', path: target.configPath });
|
|
972
|
-
}
|
|
973
|
-
else {
|
|
974
|
-
results.push({ agent: target.name, status: 'not_found', path: target.configPath });
|
|
975
|
-
}
|
|
976
|
-
continue;
|
|
977
|
-
}
|
|
978
|
-
const status = removeFromFile(target.configPath);
|
|
979
|
-
results.push({ agent: target.name, status, path: target.configPath });
|
|
980
|
-
}
|
|
981
|
-
return results;
|
|
982
|
-
}
|
|
399
|
+
// ─── MCP registration ────────────────────────────────────────────────────────
|
|
400
|
+
var sync_mcp_1 = require("./sync-mcp");
|
|
401
|
+
Object.defineProperty(exports, "syncMcpServerToCursor", { enumerable: true, get: function () { return sync_mcp_1.syncMcpServerToCursor; } });
|
|
402
|
+
Object.defineProperty(exports, "unsyncMcpServerFromCursor", { enumerable: true, get: function () { return sync_mcp_1.unsyncMcpServerFromCursor; } });
|
|
403
|
+
Object.defineProperty(exports, "syncMcpServerToCodex", { enumerable: true, get: function () { return sync_mcp_1.syncMcpServerToCodex; } });
|
|
404
|
+
Object.defineProperty(exports, "unsyncMcpServerFromCodex", { enumerable: true, get: function () { return sync_mcp_1.unsyncMcpServerFromCodex; } });
|
|
405
|
+
Object.defineProperty(exports, "syncMcpServerToKiro", { enumerable: true, get: function () { return sync_mcp_1.syncMcpServerToKiro; } });
|
|
406
|
+
Object.defineProperty(exports, "unsyncMcpServerFromKiro", { enumerable: true, get: function () { return sync_mcp_1.unsyncMcpServerFromKiro; } });
|
|
407
|
+
// ─── Instruction sync ────────────────────────────────────────────────────────
|
|
408
|
+
var sync_instructions_1 = require("./sync-instructions");
|
|
409
|
+
Object.defineProperty(exports, "MARKER_START", { enumerable: true, get: function () { return sync_instructions_1.MARKER_START; } });
|
|
410
|
+
Object.defineProperty(exports, "MARKER_END", { enumerable: true, get: function () { return sync_instructions_1.MARKER_END; } });
|
|
411
|
+
Object.defineProperty(exports, "INSTRUCTIONS_FILE", { enumerable: true, get: function () { return sync_instructions_1.INSTRUCTIONS_FILE; } });
|
|
412
|
+
Object.defineProperty(exports, "getDefaultTargets", { enumerable: true, get: function () { return sync_instructions_1.getDefaultTargets; } });
|
|
413
|
+
Object.defineProperty(exports, "getInstructionsPath", { enumerable: true, get: function () { return sync_instructions_1.getInstructionsPath; } });
|
|
414
|
+
Object.defineProperty(exports, "buildInjectedSection", { enumerable: true, get: function () { return sync_instructions_1.buildInjectedSection; } });
|
|
415
|
+
Object.defineProperty(exports, "injectIntoFile", { enumerable: true, get: function () { return sync_instructions_1.injectIntoFile; } });
|
|
416
|
+
Object.defineProperty(exports, "removeFromFile", { enumerable: true, get: function () { return sync_instructions_1.removeFromFile; } });
|
|
417
|
+
Object.defineProperty(exports, "syncInstructions", { enumerable: true, get: function () { return sync_instructions_1.syncInstructions; } });
|
|
418
|
+
Object.defineProperty(exports, "unsyncInstructions", { enumerable: true, get: function () { return sync_instructions_1.unsyncInstructions; } });
|
|
983
419
|
//# sourceMappingURL=sync.js.map
|