ai-agent-config 2.5.8 → 2.6.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/README.md +49 -10
- package/bin/cli.js +34 -1
- package/package.json +1 -1
- package/scripts/installer.js +12 -0
- package/scripts/mcp-installer.js +280 -0
- package/scripts/platforms.js +4 -0
- package/scripts/postinstall.js +42 -0
- package/scripts/secret-manager.js +41 -41
- package/scripts/sync-manager.js +6 -2
package/README.md
CHANGED
|
@@ -33,14 +33,31 @@ ai-agent update
|
|
|
33
33
|
| Command | Description |
|
|
34
34
|
|---------|-------------|
|
|
35
35
|
| `init --repo <url>` | Initialize config and clone repo |
|
|
36
|
-
| `push
|
|
37
|
-
| `
|
|
36
|
+
| `push [--message "msg"]` | Git push to your skills repo |
|
|
37
|
+
| `pull` | Git pull from repo + auto-install |
|
|
38
|
+
| `update` | Pull → sync external skills → push |
|
|
38
39
|
| `install` | Copy skills to platform directories |
|
|
39
40
|
| `list` | List installed skills |
|
|
40
41
|
| `platforms` | Show detected platforms |
|
|
41
|
-
| `source add/remove/list` | Manage skill sources |
|
|
42
|
-
| `config get/set/edit` | Manage configuration |
|
|
43
42
|
| `uninstall` | Remove installed skills |
|
|
43
|
+
| `source add <url>` | Add custom skill source |
|
|
44
|
+
| `source remove <name>` | Remove skill source |
|
|
45
|
+
| `source list` | List all sources |
|
|
46
|
+
| `source enable <name>` | Enable a source |
|
|
47
|
+
| `source disable <name>` | Disable a source |
|
|
48
|
+
| `source info <name>` | View source details |
|
|
49
|
+
| `config get <key>` | Get config value |
|
|
50
|
+
| `config set <key> <value>` | Set config value |
|
|
51
|
+
| `config edit` | Open config in $EDITOR |
|
|
52
|
+
| `config validate` | Validate configuration |
|
|
53
|
+
| `config export [file]` | Export configuration |
|
|
54
|
+
| `config import [file]` | Import configuration |
|
|
55
|
+
| `config reset --yes` | Reset to defaults |
|
|
56
|
+
| `secrets sync` | Sync MCP secrets from Bitwarden vault |
|
|
57
|
+
| `sync-external` | Alias for `update` |
|
|
58
|
+
| `list-external` | List available external skills |
|
|
59
|
+
| `version` | Show version |
|
|
60
|
+
| `help` | Show help |
|
|
44
61
|
|
|
45
62
|
## Supported Platforms
|
|
46
63
|
|
|
@@ -53,22 +70,44 @@ ai-agent update
|
|
|
53
70
|
| Codex CLI | `~/.codex/skills/` |
|
|
54
71
|
| GitHub Copilot | `~/.github/copilot-instructions.md` |
|
|
55
72
|
|
|
73
|
+
## Secret Management
|
|
74
|
+
|
|
75
|
+
Securely sync MCP secrets from Bitwarden vault to your shell profile:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
ai-agent secrets sync
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**How it works:**
|
|
82
|
+
- Discovers required secrets from MCP config files (e.g., `${GITHUB_TOKEN}`)
|
|
83
|
+
- Fetches secrets from Bitwarden vault folder "MCP Secrets"
|
|
84
|
+
- Writes to `~/.zshrc` for persistence across sessions
|
|
85
|
+
- Never stores Bitwarden master password
|
|
86
|
+
|
|
87
|
+
**Setup:** See [Bitwarden MCP Setup Guide](./mcp-servers/bitwarden/README.md)
|
|
88
|
+
|
|
89
|
+
**Auto-configuration:** Package automatically configures Bitwarden MCP server in Antigravity on install
|
|
90
|
+
|
|
56
91
|
## Configuration
|
|
57
92
|
|
|
58
93
|
User config at `~/.ai-agent/config.json`:
|
|
59
94
|
|
|
60
95
|
```json
|
|
61
96
|
{
|
|
62
|
-
"version": "2.
|
|
97
|
+
"version": "2.5",
|
|
63
98
|
"repository": {
|
|
64
99
|
"url": "https://github.com/youruser/my-ai-skills.git",
|
|
65
100
|
"branch": "main",
|
|
66
|
-
"local": "
|
|
101
|
+
"local": "/Users/you/.ai-agent/sync-repo"
|
|
67
102
|
},
|
|
68
|
-
"sources":
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
103
|
+
"sources": [
|
|
104
|
+
{
|
|
105
|
+
"name": "vercel-labs",
|
|
106
|
+
"url": "https://github.com/vercel-labs/agent-skills.git",
|
|
107
|
+
"enabled": true
|
|
108
|
+
}
|
|
109
|
+
],
|
|
110
|
+
"lastSync": "2026-02-13T12:00:00.000Z"
|
|
72
111
|
}
|
|
73
112
|
```
|
|
74
113
|
|
package/bin/cli.js
CHANGED
|
@@ -199,6 +199,21 @@ function listSkills() {
|
|
|
199
199
|
});
|
|
200
200
|
}
|
|
201
201
|
|
|
202
|
+
// MCP Servers
|
|
203
|
+
const mcpInstaller = require("../scripts/mcp-installer");
|
|
204
|
+
const mcpServers = mcpInstaller.getAvailableMcpServers();
|
|
205
|
+
|
|
206
|
+
console.log("\nMCP Servers:");
|
|
207
|
+
if (mcpServers.length === 0) {
|
|
208
|
+
console.log(" (no MCP servers found)");
|
|
209
|
+
} else {
|
|
210
|
+
mcpServers.forEach((server) => {
|
|
211
|
+
const status = server.enabled === false ? " (disabled)" : "";
|
|
212
|
+
const desc = server.description ? ` - ${server.description}` : "";
|
|
213
|
+
console.log(` • ${server.name}${desc}${status}`);
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
202
217
|
console.log(`\nSource: ${installer.CACHE_DIR}`);
|
|
203
218
|
console.log("");
|
|
204
219
|
}
|
|
@@ -668,6 +683,13 @@ function install(args) {
|
|
|
668
683
|
if (result.skillsCount > 0) parts.push(`${result.skillsCount} skill(s)`);
|
|
669
684
|
if (result.workflowsCount > 0) parts.push(`${result.workflowsCount} workflow(s)`);
|
|
670
685
|
|
|
686
|
+
// Count MCP servers across all platforms
|
|
687
|
+
let mcpTotal = 0;
|
|
688
|
+
result.details.forEach((d) => {
|
|
689
|
+
if (d.mcpServers) mcpTotal += d.mcpServers.added + d.mcpServers.skipped;
|
|
690
|
+
});
|
|
691
|
+
if (mcpTotal > 0) parts.push(`${mcpTotal} MCP server(s)`);
|
|
692
|
+
|
|
671
693
|
console.log(`\n✓ Installed ${parts.join(", ")} to ${result.platformsCount} platform(s)\n`);
|
|
672
694
|
result.details.forEach((d) => {
|
|
673
695
|
console.log(` ${d.platform}:`);
|
|
@@ -685,7 +707,18 @@ function install(args) {
|
|
|
685
707
|
console.log(` • ${w.name} ${status}`);
|
|
686
708
|
});
|
|
687
709
|
}
|
|
710
|
+
if (d.mcpServers && d.mcpServers.servers.length > 0) {
|
|
711
|
+
console.log(` MCP Servers: ${d.mcpServers.added} added, ${d.mcpServers.skipped} skipped`);
|
|
712
|
+
d.mcpServers.servers.forEach((name) => {
|
|
713
|
+
console.log(` • ${name}`);
|
|
714
|
+
});
|
|
715
|
+
}
|
|
688
716
|
});
|
|
717
|
+
|
|
718
|
+
// Hint about secrets sync if MCP servers were installed
|
|
719
|
+
if (mcpTotal > 0) {
|
|
720
|
+
console.log("\n💡 Run 'ai-agent secrets sync' to resolve Bitwarden secrets");
|
|
721
|
+
}
|
|
689
722
|
} else {
|
|
690
723
|
console.log("\n⚠️ No skills or workflows installed.");
|
|
691
724
|
}
|
|
@@ -830,7 +863,7 @@ function pull(args) {
|
|
|
830
863
|
const noInstall = args.includes("--no-install");
|
|
831
864
|
|
|
832
865
|
if (!noInstall) {
|
|
833
|
-
console.log("📥 Auto-installing skills...\n");
|
|
866
|
+
console.log("📥 Auto-installing skills + MCP servers...\n");
|
|
834
867
|
install(["--force"]); // Force install to ensure latest
|
|
835
868
|
}
|
|
836
869
|
} else {
|
package/package.json
CHANGED
package/scripts/installer.js
CHANGED
|
@@ -8,6 +8,8 @@ const path = require("path");
|
|
|
8
8
|
const { execSync } = require("child_process");
|
|
9
9
|
const platforms = require("./platforms");
|
|
10
10
|
|
|
11
|
+
const mcpInstaller = require("./mcp-installer");
|
|
12
|
+
|
|
11
13
|
const REPO_URL = "https://github.com/dongitran/ai-agent-config.git";
|
|
12
14
|
const CACHE_DIR = path.join(platforms.HOME, ".ai-agent-config-cache");
|
|
13
15
|
const REPO_SKILLS_DIR = path.join(CACHE_DIR, ".agent", "skills");
|
|
@@ -178,6 +180,7 @@ function installToPlatform(platform, options = {}) {
|
|
|
178
180
|
workflowsPath: workflowsPath,
|
|
179
181
|
skills: [],
|
|
180
182
|
workflows: [],
|
|
183
|
+
mcpServers: null,
|
|
181
184
|
};
|
|
182
185
|
|
|
183
186
|
// Install skills
|
|
@@ -230,6 +233,15 @@ function installToPlatform(platform, options = {}) {
|
|
|
230
233
|
}
|
|
231
234
|
}
|
|
232
235
|
|
|
236
|
+
// Install MCP servers (Antigravity only for now)
|
|
237
|
+
if (platform.name === "antigravity") {
|
|
238
|
+
try {
|
|
239
|
+
results.mcpServers = mcpInstaller.installMcpServers({ force });
|
|
240
|
+
} catch (error) {
|
|
241
|
+
console.warn(` ⚠️ MCP install failed: ${error.message}`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
233
245
|
return results;
|
|
234
246
|
}
|
|
235
247
|
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Installer Module
|
|
3
|
+
* Handles discovering, validating, and installing MCP servers from repo to platforms
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require("fs");
|
|
7
|
+
const path = require("path");
|
|
8
|
+
const platforms = require("./platforms");
|
|
9
|
+
const configManager = require("./config-manager");
|
|
10
|
+
|
|
11
|
+
const SKIP_FOLDERS = ["bitwarden"];
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Get the MCP servers directory from the user's sync repo
|
|
15
|
+
* @returns {string|null} Path to .agent/mcp-servers/ or null
|
|
16
|
+
*/
|
|
17
|
+
function getMcpServersDir() {
|
|
18
|
+
const config = configManager.loadConfig();
|
|
19
|
+
const repoLocal = config.repository && config.repository.local;
|
|
20
|
+
if (!repoLocal) return null;
|
|
21
|
+
|
|
22
|
+
const expanded = repoLocal.replace(/^~/, process.env.HOME || process.env.USERPROFILE);
|
|
23
|
+
const mcpDir = path.join(expanded, ".agent", "mcp-servers");
|
|
24
|
+
return fs.existsSync(mcpDir) ? mcpDir : null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Validate an MCP server config
|
|
29
|
+
* @param {Object} config - Parsed config.json
|
|
30
|
+
* @returns {{ valid: boolean, errors: string[] }}
|
|
31
|
+
*/
|
|
32
|
+
function validateMcpConfig(config) {
|
|
33
|
+
const errors = [];
|
|
34
|
+
|
|
35
|
+
if (!config.name || typeof config.name !== "string") {
|
|
36
|
+
errors.push("Missing or invalid 'name' field");
|
|
37
|
+
}
|
|
38
|
+
if (!config.command || typeof config.command !== "string") {
|
|
39
|
+
errors.push("Missing or invalid 'command' field");
|
|
40
|
+
}
|
|
41
|
+
if (!Array.isArray(config.args)) {
|
|
42
|
+
errors.push("Missing or invalid 'args' field (must be array)");
|
|
43
|
+
}
|
|
44
|
+
if (config.bitwardenEnv && typeof config.bitwardenEnv !== "object") {
|
|
45
|
+
errors.push("'bitwardenEnv' must be an object");
|
|
46
|
+
}
|
|
47
|
+
if (config.disabledTools && !Array.isArray(config.disabledTools)) {
|
|
48
|
+
errors.push("'disabledTools' must be an array");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return { valid: errors.length === 0, errors };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Get all available MCP servers from the repo
|
|
56
|
+
* @returns {Array<Object>} Array of parsed and validated MCP server configs
|
|
57
|
+
*/
|
|
58
|
+
function getAvailableMcpServers() {
|
|
59
|
+
const mcpDir = getMcpServersDir();
|
|
60
|
+
if (!mcpDir) return [];
|
|
61
|
+
|
|
62
|
+
const servers = [];
|
|
63
|
+
|
|
64
|
+
const entries = fs.readdirSync(mcpDir, { withFileTypes: true });
|
|
65
|
+
for (const entry of entries) {
|
|
66
|
+
if (!entry.isDirectory()) continue;
|
|
67
|
+
if (SKIP_FOLDERS.includes(entry.name)) continue;
|
|
68
|
+
|
|
69
|
+
const configPath = path.join(mcpDir, entry.name, "config.json");
|
|
70
|
+
if (!fs.existsSync(configPath)) continue;
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
74
|
+
const validation = validateMcpConfig(config);
|
|
75
|
+
if (!validation.valid) {
|
|
76
|
+
console.warn(` ⚠️ Invalid MCP config in ${entry.name}/: ${validation.errors.join(", ")}`);
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
servers.push(config);
|
|
80
|
+
} catch (error) {
|
|
81
|
+
console.warn(` ⚠️ Failed to parse ${entry.name}/config.json: ${error.message}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return servers;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Collect all bitwardenEnv entries from MCP server configs
|
|
90
|
+
* @returns {Array<{ serverName: string, envVar: string, bitwardenItem: string }>}
|
|
91
|
+
*/
|
|
92
|
+
function collectBitwardenEnvs() {
|
|
93
|
+
const servers = getAvailableMcpServers();
|
|
94
|
+
const envs = [];
|
|
95
|
+
|
|
96
|
+
for (const server of servers) {
|
|
97
|
+
if (server.enabled === false) continue;
|
|
98
|
+
if (!server.bitwardenEnv) continue;
|
|
99
|
+
|
|
100
|
+
for (const [envVar, bitwardenItem] of Object.entries(server.bitwardenEnv)) {
|
|
101
|
+
envs.push({
|
|
102
|
+
serverName: server.name,
|
|
103
|
+
envVar,
|
|
104
|
+
bitwardenItem,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return envs;
|
|
110
|
+
}
|
|
111
|
+
|
|
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[] }}
|
|
117
|
+
*/
|
|
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
|
+
}
|
|
130
|
+
|
|
131
|
+
// Read existing mcp_config.json
|
|
132
|
+
let mcpConfig = { mcpServers: {} };
|
|
133
|
+
if (fs.existsSync(antigravity.mcpConfigPath)) {
|
|
134
|
+
try {
|
|
135
|
+
mcpConfig = JSON.parse(fs.readFileSync(antigravity.mcpConfigPath, "utf-8"));
|
|
136
|
+
if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};
|
|
137
|
+
} catch {
|
|
138
|
+
mcpConfig = { mcpServers: {} };
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
let added = 0;
|
|
143
|
+
let skipped = 0;
|
|
144
|
+
const serverNames = [];
|
|
145
|
+
|
|
146
|
+
for (const server of servers) {
|
|
147
|
+
const existing = mcpConfig.mcpServers[server.name];
|
|
148
|
+
|
|
149
|
+
if (existing && !force) {
|
|
150
|
+
skipped++;
|
|
151
|
+
serverNames.push(server.name);
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Build server entry for Antigravity format
|
|
156
|
+
const entry = {
|
|
157
|
+
command: server.command,
|
|
158
|
+
args: server.args,
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
// Preserve existing env if not forcing
|
|
162
|
+
if (existing && existing.env) {
|
|
163
|
+
entry.env = existing.env;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (server.disabledTools && server.disabledTools.length > 0) {
|
|
167
|
+
entry.disabledTools = server.disabledTools;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
mcpConfig.mcpServers[server.name] = entry;
|
|
171
|
+
added++;
|
|
172
|
+
serverNames.push(server.name);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Write back
|
|
176
|
+
if (added > 0) {
|
|
177
|
+
const configDir = path.dirname(antigravity.mcpConfigPath);
|
|
178
|
+
if (!fs.existsSync(configDir)) {
|
|
179
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
180
|
+
}
|
|
181
|
+
fs.writeFileSync(antigravity.mcpConfigPath, JSON.stringify(mcpConfig, null, 2) + "\n", "utf-8");
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return { added, skipped, servers: serverNames };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
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 }> }}
|
|
192
|
+
*/
|
|
193
|
+
function installMcpServersWithSecrets(resolvedSecrets) {
|
|
194
|
+
const antigravity = platforms.getByName("antigravity");
|
|
195
|
+
|
|
196
|
+
if (!antigravity || !antigravity.mcpConfigPath) {
|
|
197
|
+
return { installed: 0, servers: [] };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const allServers = getAvailableMcpServers().filter((s) => s.enabled !== false);
|
|
201
|
+
if (allServers.length === 0) {
|
|
202
|
+
return { installed: 0, servers: [] };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Read existing mcp_config.json
|
|
206
|
+
let mcpConfig = { mcpServers: {} };
|
|
207
|
+
if (fs.existsSync(antigravity.mcpConfigPath)) {
|
|
208
|
+
try {
|
|
209
|
+
mcpConfig = JSON.parse(fs.readFileSync(antigravity.mcpConfigPath, "utf-8"));
|
|
210
|
+
if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};
|
|
211
|
+
} catch {
|
|
212
|
+
mcpConfig = { mcpServers: {} };
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
let installed = 0;
|
|
217
|
+
const serverResults = [];
|
|
218
|
+
|
|
219
|
+
for (const server of allServers) {
|
|
220
|
+
const existing = mcpConfig.mcpServers[server.name] || {};
|
|
221
|
+
|
|
222
|
+
// Build entry: keep existing custom fields (disabledTools user may have customized)
|
|
223
|
+
const entry = {
|
|
224
|
+
command: server.command,
|
|
225
|
+
args: server.args,
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
// Build env from resolved secrets
|
|
229
|
+
if (server.bitwardenEnv) {
|
|
230
|
+
const env = {};
|
|
231
|
+
let secretsCount = 0;
|
|
232
|
+
|
|
233
|
+
for (const [envVar, bitwardenItem] of Object.entries(server.bitwardenEnv)) {
|
|
234
|
+
if (resolvedSecrets[bitwardenItem]) {
|
|
235
|
+
env[envVar] = resolvedSecrets[bitwardenItem];
|
|
236
|
+
secretsCount++;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (Object.keys(env).length > 0) {
|
|
241
|
+
entry.env = { ...(existing.env || {}), ...env };
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
serverResults.push({ name: server.name, secretsCount });
|
|
245
|
+
} else {
|
|
246
|
+
serverResults.push({ name: server.name, secretsCount: 0 });
|
|
247
|
+
}
|
|
248
|
+
|
|
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;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
mcpConfig.mcpServers[server.name] = entry;
|
|
257
|
+
installed++;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Write back
|
|
261
|
+
if (installed > 0) {
|
|
262
|
+
const configDir = path.dirname(antigravity.mcpConfigPath);
|
|
263
|
+
if (!fs.existsSync(configDir)) {
|
|
264
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
265
|
+
}
|
|
266
|
+
fs.writeFileSync(antigravity.mcpConfigPath, JSON.stringify(mcpConfig, null, 2) + "\n", "utf-8");
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return { installed, servers: serverResults };
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
module.exports = {
|
|
273
|
+
getMcpServersDir,
|
|
274
|
+
validateMcpConfig,
|
|
275
|
+
getAvailableMcpServers,
|
|
276
|
+
collectBitwardenEnvs,
|
|
277
|
+
installMcpServers,
|
|
278
|
+
installMcpServersWithSecrets,
|
|
279
|
+
SKIP_FOLDERS,
|
|
280
|
+
};
|
package/scripts/platforms.js
CHANGED
|
@@ -42,6 +42,7 @@ const SUPPORTED = [
|
|
|
42
42
|
configDir: ".gemini/antigravity",
|
|
43
43
|
skillsDir: "skills",
|
|
44
44
|
workflowsDir: "workflows",
|
|
45
|
+
mcpConfigFile: "mcp_config.json",
|
|
45
46
|
get configPath() {
|
|
46
47
|
return path.join(HOME, this.configDir);
|
|
47
48
|
},
|
|
@@ -51,6 +52,9 @@ const SUPPORTED = [
|
|
|
51
52
|
get workflowsPath() {
|
|
52
53
|
return path.join(HOME, this.configDir, this.workflowsDir);
|
|
53
54
|
},
|
|
55
|
+
get mcpConfigPath() {
|
|
56
|
+
return path.join(HOME, this.configDir, this.mcpConfigFile);
|
|
57
|
+
},
|
|
54
58
|
detect() {
|
|
55
59
|
// Check for .gemini directory or Antigravity app
|
|
56
60
|
return (
|
package/scripts/postinstall.js
CHANGED
|
@@ -73,6 +73,24 @@ function main() {
|
|
|
73
73
|
BW_CLIENT_ID: "${BW_CLIENT_ID}",
|
|
74
74
|
BW_CLIENT_SECRET: "${BW_CLIENT_SECRET}",
|
|
75
75
|
},
|
|
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
|
+
],
|
|
76
94
|
};
|
|
77
95
|
changed = true;
|
|
78
96
|
console.log("🔐 Bitwarden MCP server added to Antigravity (✓ enabled)");
|
|
@@ -89,6 +107,30 @@ function main() {
|
|
|
89
107
|
changed = true;
|
|
90
108
|
console.log("🔓 Bitwarden MCP server configuration repaired and enabled");
|
|
91
109
|
}
|
|
110
|
+
|
|
111
|
+
// Phase 4: Add disabledTools if not present (don't override if user customized)
|
|
112
|
+
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
|
+
];
|
|
131
|
+
changed = true;
|
|
132
|
+
console.log("🎛️ Bitwarden MCP: Added tool filters (disabled org-management tools)");
|
|
133
|
+
}
|
|
92
134
|
}
|
|
93
135
|
|
|
94
136
|
if (changed) {
|
|
@@ -153,35 +153,25 @@ function unlockBitwarden(password) {
|
|
|
153
153
|
}
|
|
154
154
|
|
|
155
155
|
/**
|
|
156
|
-
* Discover required secrets from MCP configs
|
|
157
|
-
* Scans
|
|
156
|
+
* Discover required secrets from MCP server configs in the repo
|
|
157
|
+
* Scans .agent/mcp-servers/{name}/config.json for bitwardenEnv fields
|
|
158
158
|
*/
|
|
159
159
|
function discoverRequiredSecrets() {
|
|
160
|
-
const
|
|
161
|
-
const
|
|
160
|
+
const mcpInstaller = require("./mcp-installer");
|
|
161
|
+
const envs = mcpInstaller.collectBitwardenEnvs();
|
|
162
162
|
|
|
163
|
-
if (
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
if (!fs.existsSync(mcpConfigPath)) {
|
|
170
|
-
return { found: false, secrets: [], reason: "MCP config not found" };
|
|
163
|
+
if (envs.length === 0) {
|
|
164
|
+
const mcpDir = mcpInstaller.getMcpServersDir();
|
|
165
|
+
if (!mcpDir) {
|
|
166
|
+
return { found: false, secrets: [], reason: "No repository configured or no MCP servers found" };
|
|
167
|
+
}
|
|
168
|
+
return { found: true, secrets: [] };
|
|
171
169
|
}
|
|
172
170
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
// Extract all ${VAR_NAME} patterns (supports mixed/lowercase)
|
|
177
|
-
const regex = /\$\{([a-zA-Z_][a-zA-Z0-9_]*)\}/g;
|
|
178
|
-
const matches = [...content.matchAll(regex)];
|
|
179
|
-
const secretNames = [...new Set(matches.map((m) => m[1]))];
|
|
171
|
+
// Collect unique Bitwarden item names to fetch
|
|
172
|
+
const secretNames = [...new Set(envs.map((e) => e.bitwardenItem))];
|
|
180
173
|
|
|
181
|
-
|
|
182
|
-
} catch (error) {
|
|
183
|
-
return { found: false, secrets: [], reason: error.message };
|
|
184
|
-
}
|
|
174
|
+
return { found: true, secrets: secretNames, envs };
|
|
185
175
|
}
|
|
186
176
|
|
|
187
177
|
/**
|
|
@@ -334,22 +324,22 @@ async function syncSecrets() {
|
|
|
334
324
|
console.log("✓ Vault unlocked\n");
|
|
335
325
|
sessionKey = unlockResult.sessionKey;
|
|
336
326
|
|
|
337
|
-
// 5. Discover required secrets
|
|
338
|
-
console.log("🔍 Scanning MCP configs for required secrets...");
|
|
327
|
+
// 5. Discover required secrets from repo's bitwardenEnv
|
|
328
|
+
console.log("🔍 Scanning MCP server configs for required secrets...");
|
|
339
329
|
const discovery = discoverRequiredSecrets();
|
|
340
330
|
|
|
341
331
|
if (!discovery.found) {
|
|
342
332
|
console.log(`⚠️ ${discovery.reason}`);
|
|
343
|
-
console.log("\n💡
|
|
333
|
+
console.log("\n💡 Configure a repository first: ai-agent init --repo <url>\n");
|
|
344
334
|
return;
|
|
345
335
|
}
|
|
346
336
|
|
|
347
337
|
if (discovery.secrets.length === 0) {
|
|
348
|
-
console.log("No
|
|
338
|
+
console.log("No bitwardenEnv entries found in MCP server configs.\n");
|
|
349
339
|
return;
|
|
350
340
|
}
|
|
351
341
|
|
|
352
|
-
console.log(`Found ${discovery.secrets.length} secret(s):`);
|
|
342
|
+
console.log(`Found ${discovery.secrets.length} secret(s) to fetch:`);
|
|
353
343
|
discovery.secrets.forEach((name) => {
|
|
354
344
|
console.log(` • ${name}`);
|
|
355
345
|
});
|
|
@@ -359,32 +349,42 @@ async function syncSecrets() {
|
|
|
359
349
|
const fetchResults = fetchSecretsFromBitwarden(sessionKey, discovery.secrets);
|
|
360
350
|
|
|
361
351
|
fetchResults.found.forEach((secret) => {
|
|
362
|
-
console.log(
|
|
352
|
+
console.log(` ✓ ${secret.name}`);
|
|
363
353
|
});
|
|
364
354
|
|
|
365
355
|
fetchResults.missing.forEach((name) => {
|
|
366
|
-
console.log(
|
|
356
|
+
console.log(` ⚠ ${name} (not found in vault)`);
|
|
367
357
|
});
|
|
368
358
|
|
|
369
|
-
// 7.
|
|
359
|
+
// 7. Install MCP servers with resolved secrets to Antigravity
|
|
370
360
|
if (fetchResults.found.length > 0) {
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
361
|
+
const mcpInstaller = require("./mcp-installer");
|
|
362
|
+
|
|
363
|
+
// Build resolvedSecrets map: bitwardenItemName → value
|
|
364
|
+
const resolvedSecrets = {};
|
|
365
|
+
fetchResults.found.forEach((s) => {
|
|
366
|
+
resolvedSecrets[s.name] = s.value;
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
console.log("\n📦 Installing MCP servers to Antigravity...");
|
|
370
|
+
const installResult = mcpInstaller.installMcpServersWithSecrets(resolvedSecrets);
|
|
371
|
+
|
|
372
|
+
installResult.servers.forEach((s) => {
|
|
373
|
+
if (s.secretsCount > 0) {
|
|
374
|
+
console.log(` ✓ ${s.name}: ${s.secretsCount} secret(s) resolved`);
|
|
375
|
+
} else {
|
|
376
|
+
console.log(` ✓ ${s.name}: no secrets needed`);
|
|
377
|
+
}
|
|
378
|
+
});
|
|
374
379
|
}
|
|
375
380
|
|
|
376
381
|
// 8. Summary
|
|
377
382
|
console.log("\n✅ Secrets synced successfully!\n");
|
|
378
383
|
|
|
379
|
-
console.log("ℹ️ Next steps:");
|
|
380
|
-
console.log(" 1. Restart terminal or run: source ~/.zshrc");
|
|
381
|
-
|
|
382
384
|
if (fetchResults.missing.length > 0) {
|
|
383
|
-
console.log(
|
|
384
|
-
console.log(
|
|
385
|
+
console.log(`⚠️ Missing secrets: ${fetchResults.missing.join(", ")}`);
|
|
386
|
+
console.log(` Add them to Bitwarden vault folder "${BITWARDEN_FOLDER}"\n`);
|
|
385
387
|
}
|
|
386
|
-
|
|
387
|
-
console.log("");
|
|
388
388
|
} finally {
|
|
389
389
|
// Cleanup: Lock vault to invalidate session key
|
|
390
390
|
if (sessionKey) {
|
package/scripts/sync-manager.js
CHANGED
|
@@ -168,9 +168,13 @@ class SyncManager {
|
|
|
168
168
|
// Add all .agent/ files except bundled package skills
|
|
169
169
|
execSync("git add .agent/workflows/", { cwd: this.repoPath, stdio: "pipe" });
|
|
170
170
|
|
|
171
|
+
// Add MCP servers
|
|
172
|
+
const mcpServersDir = path.join(this.repoPath, ".agent/mcp-servers");
|
|
173
|
+
if (fs.existsSync(mcpServersDir)) {
|
|
174
|
+
execSync("git add .agent/mcp-servers/", { cwd: this.repoPath, stdio: "pipe" });
|
|
175
|
+
}
|
|
176
|
+
|
|
171
177
|
// Add skills individually, excluding bundled ones
|
|
172
|
-
const fs = require("fs");
|
|
173
|
-
const path = require("path");
|
|
174
178
|
const skillsDir = path.join(this.repoPath, ".agent/skills");
|
|
175
179
|
const bundledSkills = ["ai-agent-config", "config-manager"];
|
|
176
180
|
|