@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/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 config_1 = require("./config");
72
- const package_json_1 = __importDefault(require("../package.json"));
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
- * Symlinks each skill directory into ~/.claude/skills/<pkgName>-<skillName>
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 syncSkillsToClaudeCode(skills, brewRoot) {
209
- const claudeDir = path_1.default.join(os_1.default.homedir(), '.claude');
210
- if (!fs_1.default.existsSync(claudeDir))
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 = path_1.default.join(claudeDir, 'skills');
178
+ const skillsDir = agent.skillsDir();
213
179
  fs_1.default.mkdirSync(skillsDir, { recursive: true });
214
- const state = loadSyncedState(brewRoot);
215
- return symlinkSkills(skills, skillsDir, state, 'claude', brewRoot);
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
- * Removes all skill symlinks previously created by syncSkillsToClaudeCode.
188
+ * Generic skill unsync for any agent in AGENT_SKILL_REGISTRY.
219
189
  */
220
- function unsyncSkillsFromClaudeCode(brewRoot) {
221
- const skillsDir = path_1.default.join(os_1.default.homedir(), '.claude', 'skills');
222
- const state = loadSyncedState(brewRoot);
223
- const results = removeTrackedSymlinks(state.claude, skillsDir);
224
- state.claude = [];
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
- // ─── Gemini CLI ──────────────────────────────────────────────────────────────
229
- /**
230
- * Registers skills with Gemini CLI by creating an agentbrew extension at
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
- * Removes Gemini CLI skill symlinks, the extension manifest, and the
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
- function _enableGeminiExtension(geminiDir) {
278
- const enablementPath = path_1.default.join(geminiDir, 'extensions', 'extension-enablement.json');
279
- let data = {};
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 _disableGeminiExtension(geminiDir) {
290
- const enablementPath = path_1.default.join(geminiDir, 'extensions', 'extension-enablement.json');
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
- const windsurfDir = path_1.default.join(os_1.default.homedir(), '.codeium', 'windsurf');
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
- const skillsDir = path_1.default.join(os_1.default.homedir(), '.codeium', 'windsurf', 'skills');
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
- const antigravityDir = path_1.default.join(os_1.default.homedir(), '.gemini', 'antigravity-cli');
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
- const skillsDir = path_1.default.join(os_1.default.homedir(), '.gemini', 'antigravity-cli', 'skills');
349
- const state = loadSyncedState(brewRoot);
350
- const results = removeTrackedSymlinks(state.antigravity, skillsDir);
351
- state.antigravity = [];
352
- saveSyncedState(state, brewRoot);
353
- return results;
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 agentDirs = [
364
- { key: 'claude', dir: path_1.default.join(os_1.default.homedir(), '.claude', 'skills') },
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 && !fs_1.default.existsSync(path_1.default.join(os_1.default.homedir(), '.codex'))) {
422
- state.codexMcp = false;
423
- }
424
- if (state.kiroMcp && !fs_1.default.existsSync(path_1.default.join(os_1.default.homedir(), '.kiro'))) {
425
- state.kiroMcp = false;
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
- // Also absorb a preceding blank line
524
- let removeFrom = startIdx;
525
- if (removeFrom > 0 && lines[removeFrom - 1].trim() === '')
526
- removeFrom--;
527
- return [...lines.slice(0, removeFrom), ...lines.slice(endIdx)].join('\n');
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
- catch { }
555
- // Remove any stale entry (e.g. wrong command) before re-adding to avoid duplicate TOML table headers
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
- // ─── Kiro ────────────────────────────────────────────────────────────────────
682
- /**
683
- * Symlinks each skill directory into ~/.kiro/skills/<pkgName>-<skillName>
684
- * so Kiro can discover them as invocable skills.
685
- */
686
- function syncSkillsToKiro(skills, brewRoot) {
687
- const kiroDir = path_1.default.join(os_1.default.homedir(), '.kiro');
688
- if (!fs_1.default.existsSync(kiroDir))
689
- return [];
690
- const skillsDir = path_1.default.join(kiroDir, 'skills');
691
- fs_1.default.mkdirSync(skillsDir, { recursive: true });
692
- const state = loadSyncedState(brewRoot);
693
- return symlinkSkills(skills, skillsDir, state, 'kiro', brewRoot);
694
- }
695
- /**
696
- * Removes all Kiro skill symlinks previously created by syncSkillsToKiro.
697
- */
698
- function unsyncSkillsFromKiro(brewRoot) {
699
- const skillsDir = path_1.default.join(os_1.default.homedir(), '.kiro', 'skills');
700
- const state = loadSyncedState(brewRoot);
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