ai-agent-config 2.6.6 → 2.7.1

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/README.md CHANGED
@@ -61,14 +61,14 @@ ai-agent update
61
61
 
62
62
  ## Supported Platforms
63
63
 
64
- | Platform | Skills Path |
65
- |----------|-------------|
66
- | Claude Code | `~/.claude/skills/` |
67
- | Antigravity IDE | `~/.gemini/antigravity/skills/` |
68
- | Cursor | `~/.cursor/skills/` |
69
- | Windsurf | `~/.windsurf/skills/` |
70
- | Codex CLI | `~/.codex/skills/` |
71
- | GitHub Copilot | `~/.github/copilot-instructions.md` |
64
+ | Platform | Skills Path | MCP Support |
65
+ |----------|-------------|-------------|
66
+ | Claude Code | `~/.claude/skills/` | `claude_desktop_config.json` |
67
+ | Antigravity IDE | `~/.gemini/antigravity/skills/` | `mcp_config.json` |
68
+ | Cursor | `~/.cursor/skills/` | - |
69
+ | Windsurf | `~/.windsurf/skills/` | - |
70
+ | Codex CLI | `~/.codex/skills/` | - |
71
+ | GitHub Copilot | `~/.github/copilot-instructions.md` | - |
72
72
 
73
73
  ## Secret Management
74
74
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-agent-config",
3
- "version": "2.6.6",
3
+ "version": "2.7.1",
4
4
  "description": "Universal skill & workflow manager for AI coding assistants with bi-directional GitHub sync",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -233,10 +233,10 @@ function installToPlatform(platform, options = {}) {
233
233
  }
234
234
  }
235
235
 
236
- // Install MCP servers (Antigravity only for now)
237
- if (platform.name === "antigravity") {
236
+ // Install MCP servers to platforms with MCP support
237
+ if (platform.mcpConfigPath) {
238
238
  try {
239
- results.mcpServers = mcpInstaller.installMcpServers({ force });
239
+ results.mcpServers = mcpInstaller.installMcpServers({ force, platform });
240
240
  } catch (error) {
241
241
  console.warn(` ⚠️ MCP install failed: ${error.message}`);
242
242
  }
@@ -110,116 +110,150 @@ function collectBitwardenEnvs() {
110
110
  }
111
111
 
112
112
  /**
113
- * Install MCP servers to Antigravity's mcp_config.json
114
- * This is the "pull" flow - installs structure without resolved secrets
115
- * @param {Object} options - { force: boolean }
116
- * @returns {{ added: number, skipped: number, servers: string[] }}
113
+ * Write MCP servers to a platform's config file
114
+ * @param {string} configPath - Path to platform's MCP config file
115
+ * @param {Array} servers - MCP server configs to install
116
+ * @param {Object} options - { force, platformName }
117
+ * @returns {{ added: number, skipped: number }}
117
118
  */
118
- function installMcpServers(options = {}) {
119
- const { force = false } = options;
120
- const antigravity = platforms.getByName("antigravity");
121
-
122
- if (!antigravity || !antigravity.mcpConfigPath) {
123
- return { added: 0, skipped: 0, servers: [] };
124
- }
125
-
126
- const servers = getAvailableMcpServers().filter((s) => s.enabled !== false);
127
- if (servers.length === 0) {
128
- return { added: 0, skipped: 0, servers: [] };
129
- }
119
+ function writeMcpToPlatformConfig(configPath, servers, options = {}) {
120
+ const { force = false, platformName = "" } = options;
130
121
 
131
- // Read existing mcp_config.json
132
- let mcpConfig = { mcpServers: {} };
133
- if (fs.existsSync(antigravity.mcpConfigPath)) {
122
+ // Read existing config — preserve ALL existing keys (e.g. Claude's "preferences")
123
+ let config = { mcpServers: {} };
124
+ if (fs.existsSync(configPath)) {
134
125
  try {
135
- mcpConfig = JSON.parse(fs.readFileSync(antigravity.mcpConfigPath, "utf-8"));
136
- if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};
126
+ config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
127
+ if (!config.mcpServers) config.mcpServers = {};
137
128
  } catch {
138
- mcpConfig = { mcpServers: {} };
129
+ config = { mcpServers: {} };
139
130
  }
140
131
  }
141
132
 
142
133
  let added = 0;
143
134
  let skipped = 0;
144
- const serverNames = [];
145
135
 
146
136
  for (const server of servers) {
147
- const existing = mcpConfig.mcpServers[server.name];
137
+ const existing = config.mcpServers[server.name];
148
138
 
149
139
  if (existing && !force) {
150
140
  skipped++;
151
- serverNames.push(server.name);
152
141
  continue;
153
142
  }
154
143
 
155
- // Build server entry for Antigravity format
156
144
  const entry = {
157
145
  command: server.command,
158
146
  args: server.args,
159
147
  };
160
148
 
161
- // Preserve existing env if not forcing
149
+ // Preserve existing env
162
150
  if (existing && existing.env) {
163
151
  entry.env = existing.env;
164
152
  }
165
153
 
166
- if (server.disabledTools && server.disabledTools.length > 0) {
154
+ // disabledTools: only add for platforms that support it (not Claude)
155
+ if (platformName !== "claude" && server.disabledTools && server.disabledTools.length > 0) {
167
156
  entry.disabledTools = server.disabledTools;
168
157
  }
169
158
 
170
- mcpConfig.mcpServers[server.name] = entry;
159
+ config.mcpServers[server.name] = entry;
171
160
  added++;
172
- serverNames.push(server.name);
173
161
  }
174
162
 
175
- // Write back
163
+ // Write back (create directory if needed)
176
164
  if (added > 0) {
177
- const configDir = path.dirname(antigravity.mcpConfigPath);
165
+ const configDir = path.dirname(configPath);
178
166
  if (!fs.existsSync(configDir)) {
179
167
  fs.mkdirSync(configDir, { recursive: true });
180
168
  }
181
- fs.writeFileSync(antigravity.mcpConfigPath, JSON.stringify(mcpConfig, null, 2) + "\n", "utf-8");
169
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
170
+ // Set restrictive permissions (owner read/write only) to protect secrets
171
+ // Only on Unix-like systems (macOS, Linux) - Windows uses ACL instead
172
+ if (process.platform !== "win32") {
173
+ try {
174
+ fs.chmodSync(configPath, 0o600);
175
+ } catch (e) {
176
+ console.warn(`⚠️ Warning: Could not set file permissions on ${configPath}: ${e.message}`);
177
+ }
178
+ }
182
179
  }
183
180
 
184
- return { added, skipped, servers: serverNames };
181
+ return { added, skipped };
185
182
  }
186
183
 
187
184
  /**
188
- * Install MCP servers with resolved secrets to Antigravity
189
- * This is the "secrets sync" flow - updates env with real values
190
- * @param {Object} resolvedSecrets - Map of envVar resolvedValue
191
- * @returns {{ installed: number, servers: Array<{ name: string, secretsCount: number }> }}
185
+ * Install MCP servers to platforms' config files
186
+ * This is the "pull" flow - installs structure without resolved secrets
187
+ * @param {Object} options - { force: boolean, platform: Object|null }
188
+ * @returns {{ added: number, skipped: number, servers: string[] }}
192
189
  */
193
- function installMcpServersWithSecrets(resolvedSecrets) {
194
- const antigravity = platforms.getByName("antigravity");
190
+ function installMcpServers(options = {}) {
191
+ const { force = false, platform = null } = options;
195
192
 
196
- if (!antigravity || !antigravity.mcpConfigPath) {
197
- return { installed: 0, servers: [] };
193
+ const servers = getAvailableMcpServers().filter((s) => s.enabled !== false);
194
+ if (servers.length === 0) {
195
+ return { added: 0, skipped: 0, servers: [] };
198
196
  }
199
197
 
200
- const allServers = getAvailableMcpServers().filter((s) => s.enabled !== false);
201
- if (allServers.length === 0) {
202
- return { installed: 0, servers: [] };
198
+ // Determine target platforms
199
+ const targetPlatforms = [];
200
+ if (platform) {
201
+ // Single platform specified
202
+ if (platform.mcpConfigPath) targetPlatforms.push(platform);
203
+ } else {
204
+ // All detected platforms with MCP support
205
+ for (const p of platforms.detectAll()) {
206
+ const full = platforms.getByName(p.name);
207
+ if (full && full.mcpConfigPath) targetPlatforms.push(full);
208
+ }
203
209
  }
204
210
 
205
- // Read existing mcp_config.json
206
- let mcpConfig = { mcpServers: {} };
207
- if (fs.existsSync(antigravity.mcpConfigPath)) {
211
+ if (targetPlatforms.length === 0) {
212
+ return { added: 0, skipped: 0, servers: [] };
213
+ }
214
+
215
+ let totalAdded = 0;
216
+ let totalSkipped = 0;
217
+ const serverNames = [...new Set(servers.map((s) => s.name))];
218
+
219
+ for (const p of targetPlatforms) {
220
+ const result = writeMcpToPlatformConfig(p.mcpConfigPath, servers, {
221
+ force,
222
+ platformName: p.name,
223
+ });
224
+ totalAdded += result.added;
225
+ totalSkipped += result.skipped;
226
+ }
227
+
228
+ return { added: totalAdded, skipped: totalSkipped, servers: serverNames };
229
+ }
230
+
231
+ /**
232
+ * Write MCP servers with resolved secrets to a platform's config file
233
+ * @param {string} configPath - Path to platform's MCP config file
234
+ * @param {Array} servers - MCP server configs
235
+ * @param {Object} resolvedSecrets - Map of bitwardenItem → resolvedValue
236
+ * @param {string} platformName - Platform name for disabledTools handling
237
+ * @returns {{ installed: number, servers: Array<{ name: string, secretsCount: number }> }}
238
+ */
239
+ function writeMcpWithSecretsToPlatformConfig(configPath, servers, resolvedSecrets, platformName) {
240
+ // Read existing config — preserve ALL existing keys
241
+ let config = { mcpServers: {} };
242
+ if (fs.existsSync(configPath)) {
208
243
  try {
209
- mcpConfig = JSON.parse(fs.readFileSync(antigravity.mcpConfigPath, "utf-8"));
210
- if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};
244
+ config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
245
+ if (!config.mcpServers) config.mcpServers = {};
211
246
  } catch {
212
- mcpConfig = { mcpServers: {} };
247
+ config = { mcpServers: {} };
213
248
  }
214
249
  }
215
250
 
216
251
  let installed = 0;
217
252
  const serverResults = [];
218
253
 
219
- for (const server of allServers) {
220
- const existing = mcpConfig.mcpServers[server.name] || {};
254
+ for (const server of servers) {
255
+ const existing = config.mcpServers[server.name] || {};
221
256
 
222
- // Build entry: keep existing custom fields (disabledTools user may have customized)
223
257
  const entry = {
224
258
  command: server.command,
225
259
  args: server.args,
@@ -246,29 +280,80 @@ function installMcpServersWithSecrets(resolvedSecrets) {
246
280
  serverResults.push({ name: server.name, secretsCount: 0 });
247
281
  }
248
282
 
249
- // Preserve user-customized disabledTools from existing config
250
- if (existing.disabledTools) {
251
- entry.disabledTools = existing.disabledTools;
252
- } else if (server.disabledTools && server.disabledTools.length > 0) {
253
- entry.disabledTools = server.disabledTools;
283
+ // disabledTools: only for platforms that support it (not Claude)
284
+ if (platformName !== "claude") {
285
+ if (existing.disabledTools) {
286
+ entry.disabledTools = existing.disabledTools;
287
+ } else if (server.disabledTools && server.disabledTools.length > 0) {
288
+ entry.disabledTools = server.disabledTools;
289
+ }
254
290
  }
255
291
 
256
- mcpConfig.mcpServers[server.name] = entry;
292
+ config.mcpServers[server.name] = entry;
257
293
  installed++;
258
294
  }
259
295
 
260
296
  // Write back
261
297
  if (installed > 0) {
262
- const configDir = path.dirname(antigravity.mcpConfigPath);
298
+ const configDir = path.dirname(configPath);
263
299
  if (!fs.existsSync(configDir)) {
264
300
  fs.mkdirSync(configDir, { recursive: true });
265
301
  }
266
- fs.writeFileSync(antigravity.mcpConfigPath, JSON.stringify(mcpConfig, null, 2) + "\n", "utf-8");
302
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
303
+ // Set restrictive permissions (owner read/write only) to protect secrets
304
+ // Only on Unix-like systems (macOS, Linux) - Windows uses ACL instead
305
+ if (process.platform !== "win32") {
306
+ try {
307
+ fs.chmodSync(configPath, 0o600);
308
+ } catch (e) {
309
+ console.warn(`⚠️ Warning: Could not set file permissions on ${configPath}: ${e.message}`);
310
+ }
311
+ }
267
312
  }
268
313
 
269
314
  return { installed, servers: serverResults };
270
315
  }
271
316
 
317
+ /**
318
+ * Install MCP servers with resolved secrets to all MCP-capable platforms
319
+ * This is the "secrets sync" flow - updates env with real values
320
+ * @param {Object} resolvedSecrets - Map of bitwardenItem → resolvedValue
321
+ * @returns {{ installed: number, servers: Array<{ name: string, secretsCount: number }> }}
322
+ */
323
+ function installMcpServersWithSecrets(resolvedSecrets) {
324
+ const allServers = getAvailableMcpServers().filter((s) => s.enabled !== false);
325
+ if (allServers.length === 0) {
326
+ return { installed: 0, servers: [] };
327
+ }
328
+
329
+ // All detected platforms with MCP support
330
+ const targetPlatforms = [];
331
+ for (const p of platforms.detectAll()) {
332
+ const full = platforms.getByName(p.name);
333
+ if (full && full.mcpConfigPath) targetPlatforms.push(full);
334
+ }
335
+
336
+ if (targetPlatforms.length === 0) {
337
+ return { installed: 0, servers: [] };
338
+ }
339
+
340
+ let totalInstalled = 0;
341
+ let aggregatedServers = [];
342
+
343
+ for (const p of targetPlatforms) {
344
+ const result = writeMcpWithSecretsToPlatformConfig(
345
+ p.mcpConfigPath, allServers, resolvedSecrets, p.name
346
+ );
347
+ totalInstalled += result.installed;
348
+ // Use server results from last platform (they're the same servers)
349
+ if (result.servers.length > 0) {
350
+ aggregatedServers = result.servers;
351
+ }
352
+ }
353
+
354
+ return { installed: totalInstalled, servers: aggregatedServers };
355
+ }
356
+
272
357
  module.exports = {
273
358
  getMcpServersDir,
274
359
  validateMcpConfig,
@@ -276,5 +361,6 @@ module.exports = {
276
361
  collectBitwardenEnvs,
277
362
  installMcpServers,
278
363
  installMcpServersWithSecrets,
364
+ writeMcpToPlatformConfig,
279
365
  SKIP_FOLDERS,
280
366
  };
@@ -32,6 +32,15 @@ const SUPPORTED = [
32
32
  get commandsPath() {
33
33
  return path.join(HOME, this.configDir, this.commandsDir);
34
34
  },
35
+ get mcpConfigPath() {
36
+ if (process.platform === "darwin") {
37
+ return path.join(HOME, "Library", "Application Support", "Claude", "claude_desktop_config.json");
38
+ } else if (process.platform === "win32") {
39
+ return path.join(process.env.APPDATA || "", "Claude", "claude_desktop_config.json");
40
+ } else {
41
+ return path.join(HOME, ".config", "Claude", "claude_desktop_config.json");
42
+ }
43
+ },
35
44
  detect() {
36
45
  return fs.existsSync(this.configPath);
37
46
  },
@@ -7,6 +7,26 @@
7
7
 
8
8
  const platforms = require("./platforms");
9
9
 
10
+ // Bitwarden MCP: Tools to disable (org management, device approval, sends, etc.)
11
+ const BITWARDEN_DISABLED_TOOLS = [
12
+ "lock", "sync", "status", "confirm",
13
+ "create_org_collection", "edit_org_collection", "edit_item_collections", "move",
14
+ "device_approval_list", "device_approval_approve", "device_approval_approve_all",
15
+ "device_approval_deny", "device_approval_deny_all",
16
+ "create_text_send", "create_file_send", "list_send", "get_send",
17
+ "edit_send", "delete_send", "remove_send_password",
18
+ "create_attachment",
19
+ "list_org_collections", "get_org_collection", "update_org_collection", "delete_org_collection",
20
+ "list_org_members", "get_org_member", "get_org_member_groups",
21
+ "invite_org_member", "update_org_member", "update_org_member_groups",
22
+ "remove_org_member", "reinvite_org_member",
23
+ "list_org_groups", "get_org_group", "get_org_group_members",
24
+ "create_org_group", "update_org_group", "delete_org_group", "update_org_group_members",
25
+ "list_org_policies", "get_org_policy", "update_org_policy",
26
+ "get_org_events", "get_org_subscription", "update_org_subscription",
27
+ "import_org_users_and_groups"
28
+ ];
29
+
10
30
  function main() {
11
31
  console.log("\n╔═══════════════════════════════════════════════════════════════╗");
12
32
  console.log("║ AI Agent Config Installed! ║");
@@ -73,24 +93,7 @@ function main() {
73
93
  BW_CLIENT_ID: "${BW_CLIENT_ID}",
74
94
  BW_CLIENT_SECRET: "${BW_CLIENT_SECRET}",
75
95
  },
76
- disabledTools: [
77
- "lock", "sync", "status", "confirm",
78
- "create_org_collection", "edit_org_collection", "edit_item_collections", "move",
79
- "device_approval_list", "device_approval_approve", "device_approval_approve_all",
80
- "device_approval_deny", "device_approval_deny_all",
81
- "create_text_send", "create_file_send", "list_send", "get_send",
82
- "edit_send", "delete_send", "remove_send_password",
83
- "create_attachment",
84
- "list_org_collections", "get_org_collection", "update_org_collection", "delete_org_collection",
85
- "list_org_members", "get_org_member", "get_org_member_groups",
86
- "invite_org_member", "update_org_member", "update_org_member_groups",
87
- "remove_org_member", "reinvite_org_member",
88
- "list_org_groups", "get_org_group", "get_org_group_members",
89
- "create_org_group", "update_org_group", "delete_org_group", "update_org_group_members",
90
- "list_org_policies", "get_org_policy", "update_org_policy",
91
- "get_org_events", "get_org_subscription", "update_org_subscription",
92
- "import_org_users_and_groups"
93
- ],
96
+ disabledTools: BITWARDEN_DISABLED_TOOLS,
94
97
  };
95
98
  changed = true;
96
99
  console.log("🔐 Bitwarden MCP server added to Antigravity (✓ enabled)");
@@ -110,24 +113,7 @@ function main() {
110
113
 
111
114
  // Phase 4: Add disabledTools if not present (don't override if user customized)
112
115
  if (!bw.disabledTools) {
113
- bw.disabledTools = [
114
- "lock", "sync", "status", "confirm",
115
- "create_org_collection", "edit_org_collection", "edit_item_collections", "move",
116
- "device_approval_list", "device_approval_approve", "device_approval_approve_all",
117
- "device_approval_deny", "device_approval_deny_all",
118
- "create_text_send", "create_file_send", "list_send", "get_send",
119
- "edit_send", "delete_send", "remove_send_password",
120
- "create_attachment",
121
- "list_org_collections", "get_org_collection", "update_org_collection", "delete_org_collection",
122
- "list_org_members", "get_org_member", "get_org_member_groups",
123
- "invite_org_member", "update_org_member", "update_org_member_groups",
124
- "remove_org_member", "reinvite_org_member",
125
- "list_org_groups", "get_org_group", "get_org_group_members",
126
- "create_org_group", "update_org_group", "delete_org_group", "update_org_group_members",
127
- "list_org_policies", "get_org_policy", "update_org_policy",
128
- "get_org_events", "get_org_subscription", "update_org_subscription",
129
- "import_org_users_and_groups"
130
- ];
116
+ bw.disabledTools = BITWARDEN_DISABLED_TOOLS;
131
117
  changed = true;
132
118
  console.log("🎛️ Bitwarden MCP: Added tool filters (disabled org-management tools)");
133
119
  }
@@ -124,13 +124,14 @@ async function promptPassword() {
124
124
  /**
125
125
  * Unlock Bitwarden vault with password
126
126
  * Returns session key or null if failed
127
- * Uses spawnSync to avoid shell injection
127
+ * Uses --passwordenv to pass password securely (not visible in process list)
128
128
  */
129
129
  function unlockBitwarden(password) {
130
130
  try {
131
- // Use positional password argument for compatibility with older Bitwarden CLI versions
132
- // Since we use spawnSync without shell: true, the password doesn't leak into shell history
133
- const result = spawnSync("bw", ["unlock", password, "--raw"], {
131
+ // Use --passwordenv to avoid leaking password in /proc/<pid>/cmdline
132
+ // Set password in env only for this child process
133
+ const result = spawnSync("bw", ["unlock", "--passwordenv", "BW_UNLOCK_PASSWORD", "--raw"], {
134
+ env: { ...process.env, BW_UNLOCK_PASSWORD: password },
134
135
  encoding: "utf-8",
135
136
  });
136
137