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