ai-agent-config 2.4.9 → 2.5.2

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
@@ -1,196 +1,77 @@
1
1
  # ai-agent-config
2
2
 
3
- > Universal Global Skills & Workflows for AI Coding Assistants - User-configurable skill sources
3
+ > Universal skill & workflow manager for AI coding assistants with bi-directional GitHub sync
4
4
 
5
5
  [![npm version](https://badge.fury.io/js/ai-agent-config.svg)](https://www.npmjs.com/package/ai-agent-config)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
7
 
8
- **One command to manage AI coding skills across Claude Code, Antigravity, Cursor, Windsurf, and more.**
9
-
10
- ## šŸš€ What's New in v2.2
11
-
12
- - āœ… **Minimal core** - Only 2 essential skills bundled (config-manager, skill-updater)
13
- - āœ… **User-configurable sources** - Add any skill repositories from GitHub
14
- - āœ… **Source management** - Enable, disable, add, remove sources via CLI
15
- - āœ… **Config management** - Export/import configs for team sharing
16
- - āœ… **Zero defaults** - No external sources by default, full user control
17
-
18
- ## šŸ“¦ Quick Start
8
+ ## Install
19
9
 
20
10
  ```bash
21
- # Install globally
22
11
  npm install -g ai-agent-config
23
-
24
- # Initialize (optional)
25
- ai-agent init --repo https://github.com/youruser/my-ai-skills.git
26
-
27
- # Pull and auto-install
28
- ai-agent pull
29
- ```
30
-
31
- ## šŸŽÆ Bundled Skills (2)
32
-
33
- The package includes 2 core skills for managing the system:
34
-
35
- 1. **config-manager** - Manage configuration and custom sources
36
- 2. **skill-updater** - Update skills from GitHub repositories
37
-
38
- ## šŸ“š Add More Skills
39
-
40
- To get more skills, add custom sources from GitHub:
41
-
42
- ```bash
43
- # Add Vercel Labs skills
44
- ai-agent source add https://github.com/vercel-labs/agent-skills.git \
45
- --name vercel-labs \
46
- --path skills
47
-
48
- # Add Everything Claude Code
49
- ai-agent source add https://github.com/affaan-m/everything-claude-code.git \
50
- --name everything-claude-code \
51
- --path skills
52
-
53
- # Update and install
54
- ai-agent update
55
- ai-agent install
56
- ```
57
-
58
- ## šŸ› ļø CLI Commands
59
-
60
- ### Source Management
61
- ```bash
62
- ai-agent source add <repo-url> [options] # Add custom source
63
- ai-agent source remove <name> # Remove source
64
- ai-agent source list # List all sources
65
- ai-agent source enable <name> # Enable source
66
- ai-agent source disable <name> # Disable source
67
- ai-agent source info <name> # View source details
68
- ```
69
-
70
- ### Config Management
71
- ```bash
72
- ai-agent config get <key> # Get config value
73
- ai-agent config set <key> <value> # Set config value
74
- ai-agent config edit # Edit in $EDITOR
75
- ai-agent config validate # Validate config
76
- ai-agent config export [file] # Export config
77
- ai-agent config import <file> [--merge] # Import config
78
- ai-agent config reset --yes # Reset to defaults
79
12
  ```
80
13
 
81
- ### Installation & Updates
82
- ```bash
83
- ai-agent init # Initialize/migrate config
84
- ai-agent pull # Pull + auto-install from GitHub
85
- ai-agent list # List installed skills
86
- ai-agent platforms # Show detected platforms
87
- ai-agent uninstall # Remove skills
88
- ```
14
+ ## Quick Start
89
15
 
90
- ## šŸŽØ Use Cases
91
-
92
- ### For Companies
93
16
  ```bash
94
- # Add your company's private skills repo
95
- ai-agent source add https://github.com/acme-corp/coding-standards \
96
- --name acme-standards
97
-
98
- # Share config file with team
99
- ai-agent config export acme-config.json
17
+ # Initialize with your GitHub repo
18
+ ai-agent init --repo https://github.com/youruser/my-ai-skills.git
100
19
 
101
- # Team members import
102
- ai-agent config import acme-config.json --merge
103
- ```
20
+ # Pull skills and auto-install to platforms
21
+ ai-agent pull
104
22
 
105
- ### For Individual Developers
106
- ```bash
107
- # Add skills from multiple sources
108
- ai-agent source add https://github.com/vercel-labs/agent-skills.git --name vercel
109
- ai-agent source add https://github.com/yourname/my-skills --name personal
23
+ # Add external skill sources
24
+ ai-agent source add https://github.com/vercel-labs/agent-skills.git \
25
+ --name vercel-labs --path skills
110
26
 
111
- # Update and install
27
+ # Sync external skills (pull -> sync -> push)
112
28
  ai-agent update
113
- ai-agent install
114
- ```
115
-
116
- ## šŸ“ File Locations
117
-
118
- ```
119
- ~/.ai-agent/
120
- ā”œā”€ā”€ config.json # User configuration
121
- └── .ai-agent-external-cache/ # Downloaded skill repositories
122
-
123
- AI Platform Skills:
124
- ~/.claude/skills/ # Claude Code
125
- ~/.gemini/antigravity/skills/ # Antigravity IDE
126
- ~/.cursor/skills/ # Cursor
127
- ~/.windsurf/skills/ # Windsurf
128
29
  ```
129
30
 
130
- ## šŸ”§ Configuration File
31
+ ## Commands
32
+
33
+ | Command | Description |
34
+ |---------|-------------|
35
+ | `init --repo <url>` | Initialize config and clone repo |
36
+ | `push` / `pull` | Git push/pull with your skills repo |
37
+ | `update` | Pull -> sync external skills -> push |
38
+ | `install` | Copy skills to platform directories |
39
+ | `list` | List installed skills |
40
+ | `platforms` | Show detected platforms |
41
+ | `source add/remove/list` | Manage skill sources |
42
+ | `config get/set/edit` | Manage configuration |
43
+ | `uninstall` | Remove installed skills |
44
+
45
+ ## Supported Platforms
46
+
47
+ | Platform | Skills Path |
48
+ |----------|-------------|
49
+ | Claude Code | `~/.claude/skills/` |
50
+ | Antigravity IDE | `~/.gemini/antigravity/skills/` |
51
+ | Cursor | `~/.cursor/skills/` |
52
+ | Windsurf | `~/.windsurf/skills/` |
53
+ | Codex CLI | `~/.codex/skills/` |
54
+ | GitHub Copilot | `~/.github/copilot-instructions.md` |
55
+
56
+ ## Configuration
131
57
 
132
58
  User config at `~/.ai-agent/config.json`:
133
59
 
134
60
  ```json
135
61
  {
136
- "version": "2.0",
62
+ "version": "2.3",
63
+ "repository": {
64
+ "url": "https://github.com/youruser/my-ai-skills.git",
65
+ "branch": "main",
66
+ "local": "~/.ai-agent/sync-repo"
67
+ },
137
68
  "sources": {
138
69
  "official": [],
139
- "custom": [
140
- {
141
- "name": "my-skills",
142
- "repo": "https://github.com/me/my-skills.git",
143
- "branch": "main",
144
- "path": "skills",
145
- "enabled": true
146
- }
147
- ]
148
- },
149
- "preferences": {
150
- "autoUpdate": true,
151
- "updateInterval": "weekly"
70
+ "custom": []
152
71
  }
153
72
  }
154
73
  ```
155
74
 
156
- ## šŸ¤ Contributing
157
-
158
- We welcome contributions! Here's how:
159
-
160
- 1. **Share your skills**: Create skills repo and share with community
161
- 2. **Report issues**: [GitHub Issues](https://github.com/dongitran/ai-agent-config/issues)
162
- 3. **Submit PRs**: Improve the core tool
163
-
164
- ## 🌟 Why ai-agent-config?
165
-
166
- - āœ… **Minimal & focused** - Only 2 core skills bundled, add what you need
167
- - āœ… **Full control** - No default external sources, you decide what to install
168
- - āœ… **User-configurable** - Add unlimited custom skill sources
169
- - āœ… **Team-friendly** - Export/import configs for collaboration
170
- - āœ… **Zero dependencies** - Lightweight, fast, secure
171
- - āœ… **Open & extensible** - Use any GitHub repo as skill source
172
-
173
- ## šŸ“Š Supported Platforms
174
-
175
- | Platform | Status | Skills Directory |
176
- |----------|--------|------------------|
177
- | Claude Code | āœ… Supported | `~/.claude/skills/` |
178
- | Antigravity IDE | āœ… Supported | `~/.gemini/antigravity/skills/` |
179
- | Cursor | āœ… Supported | `~/.cursor/skills/` |
180
- | Windsurf | āœ… Supported | `~/.windsurf/skills/` |
181
- | Codex CLI | āœ… Supported | `~/.codex/skills/` |
182
-
183
- ## šŸ“„ License
184
-
185
- MIT Ā© [Dong Tran](https://github.com/dongitran)
186
-
187
- ## šŸ”— Links
188
-
189
- - **NPM**: https://www.npmjs.com/package/ai-agent-config
190
- - **GitHub**: https://github.com/dongitran/ai-agent-config
191
- - **Issues**: https://github.com/dongitran/ai-agent-config/issues
192
- - **Changelog**: [CHANGELOG.md](CHANGELOG.md)
193
-
194
- ---
75
+ ## License
195
76
 
196
- **Keywords**: AI coding assistant, Claude Code, Antigravity, Cursor, Windsurf, AI skills, code automation, developer tools, coding standards, best practices, AI agent config, skill management, team collaboration, custom skills, GitHub integration
77
+ MIT
package/bin/cli.js CHANGED
@@ -8,6 +8,7 @@ const installer = require("../scripts/installer");
8
8
  const platforms = require("../scripts/platforms");
9
9
  const migration = require("../scripts/migration");
10
10
  const externalSync = require("../scripts/external-sync");
11
+ const secretManager = require("../scripts/secret-manager");
11
12
 
12
13
  const VERSION = require("../package.json").version;
13
14
 
@@ -39,6 +40,9 @@ const COMMANDS = {
39
40
  "config import": "Import config",
40
41
  "config reset": "Reset to defaults",
41
42
 
43
+ // Secret Management
44
+ "secrets sync": "Sync MCP secrets from Bitwarden",
45
+
42
46
  // Original Commands (updated)
43
47
  install: "Install skills to detected platforms",
44
48
  update: "Update skills from all sources",
@@ -93,6 +97,9 @@ Usage: ai-agent <command> [options]
93
97
  push [--message <msg>] Push skills to GitHub
94
98
  pull Pull skills from GitHub
95
99
 
100
+ šŸ” Secret Management:
101
+ secrets sync Sync MCP secrets from Bitwarden
102
+
96
103
  šŸ” External Skills:
97
104
  sync-external [opts] Sync skills from external sources
98
105
  list-external List available external skills
@@ -707,6 +714,20 @@ function update(args) {
707
714
  }
708
715
 
709
716
  try {
717
+ const SyncManager = require("../scripts/sync-manager");
718
+ const config = configManager.loadConfig();
719
+ const syncManager = new SyncManager(config);
720
+
721
+ // 1. Pull latest from repository first
722
+ console.log("šŸ“„ Pulling latest from repository...\n");
723
+ const pullResult = syncManager.pull();
724
+ if (pullResult.pulled) {
725
+ console.log(" āœ“ Repository up to date\n");
726
+ } else {
727
+ console.log(` āŠ— Pull: ${pullResult.reason || "failed"}\n`);
728
+ }
729
+
730
+ // 2. Sync external skills
710
731
  const result = externalSync.syncAll(options);
711
732
 
712
733
  console.log(`\nāœ“ Updated from ${result.synced} source(s)`);
@@ -716,17 +737,19 @@ function update(args) {
716
737
  console.log(` Failed: ${result.failed} source(s)`);
717
738
  }
718
739
 
719
- // Auto-push after successful sync
740
+ // 3. Push changes to repository
720
741
  if (result.copied > 0) {
721
- console.log("\nšŸ“¤ Auto-pushing synced skills to repository...\n");
722
- const SyncManager = require("../scripts/sync-manager");
723
- const config = configManager.loadConfig();
724
- const syncManager = new SyncManager(config);
725
-
742
+ console.log("\nšŸ“¤ Pushing synced skills to repository...\n");
743
+
726
744
  const skillNames = options.skill || "external skills";
727
745
  const message = `chore: sync ${skillNames} from external sources`;
728
-
729
- syncManager.push({ message });
746
+
747
+ const pushResult = syncManager.push({ message });
748
+ if (pushResult.pushed) {
749
+ console.log(" āœ“ Pushed to repository");
750
+ } else {
751
+ console.log(` āŠ— ${pushResult.reason}`);
752
+ }
730
753
  }
731
754
 
732
755
  console.log("");
@@ -899,117 +922,142 @@ function listExternal() {
899
922
  }
900
923
  }
901
924
 
902
- // Main
903
- const args = process.argv.slice(2);
904
- const command = args[0];
905
- const subcommand = args[1];
906
-
907
- // Handle multi-word commands
908
- if (command === "source") {
909
- switch (subcommand) {
910
- case "add":
911
- sourceAdd(args.slice(2));
912
- break;
913
- case "remove":
914
- sourceRemove(args.slice(2));
915
- break;
916
- case "list":
917
- sourceList(args.slice(2));
918
- break;
919
- case "enable":
920
- sourceToggle(args.slice(2), true);
921
- break;
922
- case "disable":
923
- sourceToggle(args.slice(2), false);
924
- break;
925
- case "info":
926
- sourceInfo(args.slice(2));
927
- break;
928
- default:
929
- console.error(`Unknown source command: ${subcommand}`);
930
- console.log('Run "ai-agent help" for usage information.');
931
- process.exit(1);
932
- }
933
- } else if (command === "config") {
934
- switch (subcommand) {
935
- case "get":
936
- configGet(args.slice(2));
937
- break;
938
- case "set":
939
- configSet(args.slice(2));
940
- break;
941
- case "edit":
942
- configEdit(args.slice(2));
943
- break;
944
- case "validate":
945
- configValidate(args.slice(2));
946
- break;
947
- case "export":
948
- configExport(args.slice(2));
949
- break;
950
- case "import":
951
- configImport(args.slice(2));
952
- break;
953
- case "reset":
954
- configReset(args.slice(2));
955
- break;
956
- default:
957
- console.error(`Unknown config command: ${subcommand}`);
958
- console.log('Run "ai-agent help" for usage information.');
959
- process.exit(1);
960
- }
961
- } else {
962
- // Single-word commands
963
- switch (command) {
964
- case "init":
965
- init(args.slice(1));
966
- break;
967
- case "migrate":
968
- migrateCmd(args.slice(1));
969
- break;
970
- case "push":
971
- push(args.slice(1));
972
- break;
973
- case "pull":
974
- pull(args.slice(1));
975
- break;
976
- case "install":
977
- install(args.slice(1));
978
- break;
979
- case "update":
980
- update(args.slice(1));
981
- break;
982
-
983
- case "sync-external":
984
- // Backward compatibility - alias for update
985
- update(args.slice(1));
986
- break;
987
- case "list":
988
- listSkills();
989
- break;
990
- case "list-external":
991
- listExternal(args.slice(1));
992
- break;
993
- case "platforms":
994
- showPlatforms();
995
- break;
996
- case "uninstall":
997
- uninstall(args.slice(1));
998
- break;
999
- case "version":
1000
- case "--version":
1001
- case "-v":
1002
- console.log(`v${VERSION}`);
1003
- break;
1004
- case "help":
1005
- case "--help":
1006
- case "-h":
1007
- case undefined:
1008
- showHelp();
1009
- break;
1010
- default:
1011
- console.error(`Unknown command: ${command}`);
1012
- console.log('Run "ai-agent help" for usage information.');
1013
- process.exit(1);
925
+ /**
926
+ * Sync secrets from Bitwarden
927
+ */
928
+ async function secretsSync() {
929
+ try {
930
+ await secretManager.syncSecrets();
931
+ } catch (error) {
932
+ console.error(`\nāŒ Secrets sync failed: ${error.message}`);
933
+ process.exit(1);
1014
934
  }
1015
935
  }
936
+
937
+ // Main
938
+ (async () => {
939
+ const args = process.argv.slice(2);
940
+ const command = args[0];
941
+ const subcommand = args[1];
942
+
943
+ // Handle multi-word commands
944
+ if (command === "source") {
945
+ switch (subcommand) {
946
+ case "add":
947
+ sourceAdd(args.slice(2));
948
+ break;
949
+ case "remove":
950
+ sourceRemove(args.slice(2));
951
+ break;
952
+ case "list":
953
+ sourceList(args.slice(2));
954
+ break;
955
+ case "enable":
956
+ sourceToggle(args.slice(2), true);
957
+ break;
958
+ case "disable":
959
+ sourceToggle(args.slice(2), false);
960
+ break;
961
+ case "info":
962
+ sourceInfo(args.slice(2));
963
+ break;
964
+ default:
965
+ console.error(`Unknown source command: ${subcommand}`);
966
+ console.log('Run "ai-agent help" for usage information.');
967
+ process.exit(1);
968
+ }
969
+ } else if (command === "config") {
970
+ switch (subcommand) {
971
+ case "get":
972
+ configGet(args.slice(2));
973
+ break;
974
+ case "set":
975
+ configSet(args.slice(2));
976
+ break;
977
+ case "edit":
978
+ configEdit(args.slice(2));
979
+ break;
980
+ case "validate":
981
+ configValidate(args.slice(2));
982
+ break;
983
+ case "export":
984
+ configExport(args.slice(2));
985
+ break;
986
+ case "import":
987
+ configImport(args.slice(2));
988
+ break;
989
+ case "reset":
990
+ configReset(args.slice(2));
991
+ break;
992
+ default:
993
+ console.error(`Unknown config command: ${subcommand}`);
994
+ console.log('Run "ai-agent help" for usage information.');
995
+ process.exit(1);
996
+ }
997
+ } else if (command === "secrets") {
998
+ switch (subcommand) {
999
+ case "sync":
1000
+ await secretsSync();
1001
+ break;
1002
+ default:
1003
+ console.error(`Unknown secrets command: ${subcommand}`);
1004
+ console.log('Run "ai-agent help" for usage information.');
1005
+ process.exit(1);
1006
+ }
1007
+ } else {
1008
+ // Single-word commands
1009
+ switch (command) {
1010
+ case "init":
1011
+ init(args.slice(1));
1012
+ break;
1013
+ case "migrate":
1014
+ migrateCmd(args.slice(1));
1015
+ break;
1016
+ case "push":
1017
+ push(args.slice(1));
1018
+ break;
1019
+ case "pull":
1020
+ pull(args.slice(1));
1021
+ break;
1022
+ case "install":
1023
+ install(args.slice(1));
1024
+ break;
1025
+ case "update":
1026
+ update(args.slice(1));
1027
+ break;
1028
+
1029
+ case "sync-external":
1030
+ // Backward compatibility - alias for update
1031
+ update(args.slice(1));
1032
+ break;
1033
+ case "list":
1034
+ listSkills();
1035
+ break;
1036
+ case "list-external":
1037
+ listExternal(args.slice(1));
1038
+ break;
1039
+ case "platforms":
1040
+ showPlatforms();
1041
+ break;
1042
+ case "uninstall":
1043
+ uninstall(args.slice(1));
1044
+ break;
1045
+ case "version":
1046
+ case "--version":
1047
+ case "-v":
1048
+ console.log(`v${VERSION}`);
1049
+ break;
1050
+ case "help":
1051
+ case "--help":
1052
+ case "-h":
1053
+ case undefined:
1054
+ showHelp();
1055
+ break;
1056
+ default:
1057
+ console.error(`Unknown command: ${command}`);
1058
+ console.log('Run "ai-agent help" for usage information.');
1059
+ process.exit(1);
1060
+ }
1061
+ }
1062
+ })();
1063
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-agent-config",
3
- "version": "2.4.9",
3
+ "version": "2.5.2",
4
4
  "description": "Universal skill & workflow manager for AI coding assistants with bi-directional GitHub sync",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -38,6 +38,9 @@
38
38
  "engines": {
39
39
  "node": ">=18.0.0"
40
40
  },
41
+ "dependencies": {
42
+ "inquirer": "^9.2.12"
43
+ },
41
44
  "files": [
42
45
  "bin/",
43
46
  "scripts/",
@@ -37,6 +37,54 @@ function main() {
37
37
  console.log(" 4. Update & install additional skills:");
38
38
  console.log(" $ ai-agent update && ai-agent install\n");
39
39
  console.log("šŸ“¦ Repository: https://github.com/dongitran/ai-agent-config\n");
40
+
41
+ // Auto-install Bitwarden MCP server to Antigravity
42
+ const fs = require("fs");
43
+ const path = require("path");
44
+ const os = require("os");
45
+
46
+ const antigravityMcpPath = path.join(
47
+ os.homedir(),
48
+ ".gemini",
49
+ "antigravity",
50
+ "mcp_config.json"
51
+ );
52
+
53
+ if (fs.existsSync(path.dirname(antigravityMcpPath))) {
54
+ try {
55
+ let mcpConfig = { mcpServers: {} };
56
+
57
+ // Read existing config if it exists
58
+ if (fs.existsSync(antigravityMcpPath)) {
59
+ const content = fs.readFileSync(antigravityMcpPath, "utf-8");
60
+ if (content.trim()) {
61
+ mcpConfig = JSON.parse(content);
62
+ }
63
+ }
64
+
65
+ // Add Bitwarden MCP server (enabled by default)
66
+ if (!mcpConfig.mcpServers.bitwarden) {
67
+ mcpConfig.mcpServers.bitwarden = {
68
+ command: "npx",
69
+ args: ["-y", "@bitwarden/mcp-server"],
70
+ env: {
71
+ BW_CLIENTID: "${BW_CLIENTID}",
72
+ BW_CLIENTSECRET: "${BW_CLIENTSECRET}",
73
+ },
74
+ };
75
+
76
+ fs.writeFileSync(
77
+ antigravityMcpPath,
78
+ JSON.stringify(mcpConfig, null, 2),
79
+ "utf-8"
80
+ );
81
+ console.log("šŸ” Bitwarden MCP server added to Antigravity (āœ“ enabled)");
82
+ console.log(" Config: ~/.gemini/antigravity/mcp_config.json\n");
83
+ }
84
+ } catch (error) {
85
+ // Silent fail - not critical
86
+ }
87
+ }
40
88
  }
41
89
 
42
90
  main();
@@ -0,0 +1,352 @@
1
+ /**
2
+ * Secret Manager Module
3
+ * Manages MCP secrets using Bitwarden integration
4
+ */
5
+
6
+ const fs = require("fs");
7
+ const path = require("path");
8
+ const { execSync, spawnSync } = require("child_process");
9
+ const inquirer = require("inquirer");
10
+ const os = require("os");
11
+
12
+ const HOME = os.homedir();
13
+ const BITWARDEN_FOLDER = "MCP Secrets";
14
+
15
+ /**
16
+ * Validate that Bitwarden CLI is installed
17
+ */
18
+ function validateBitwardenCLI() {
19
+ try {
20
+ execSync("which bw", { stdio: "pipe" });
21
+ return { valid: true };
22
+ } catch (error) {
23
+ return {
24
+ valid: false,
25
+ message: "Bitwarden CLI not found. Install with: npm install -g @bitwarden/cli",
26
+ };
27
+ }
28
+ }
29
+
30
+ /**
31
+ * Validate that Bitwarden API credentials are set
32
+ */
33
+ function validateBitwardenAuth() {
34
+ const clientId = process.env.BW_CLIENTID;
35
+ const clientSecret = process.env.BW_CLIENTSECRET;
36
+
37
+ if (!clientId || !clientSecret) {
38
+ return {
39
+ valid: false,
40
+ message:
41
+ "Bitwarden API credentials not set. Add to ~/.zshrc:\n" +
42
+ " export BW_CLIENTID=\"user.xxx\"\n" +
43
+ " export BW_CLIENTSECRET=\"yyy\"",
44
+ };
45
+ }
46
+
47
+ return { valid: true };
48
+ }
49
+
50
+ /**
51
+ * Prompt user for Bitwarden master password
52
+ */
53
+ async function promptPassword() {
54
+ const answers = await inquirer.prompt([
55
+ {
56
+ type: "password",
57
+ name: "masterPassword",
58
+ message: "Enter Bitwarden master password:",
59
+ mask: "*",
60
+ },
61
+ ]);
62
+
63
+ return answers.masterPassword;
64
+ }
65
+
66
+ /**
67
+ * Unlock Bitwarden vault with password
68
+ * Returns session key or null if failed
69
+ * Uses spawnSync to avoid shell injection
70
+ */
71
+ function unlockBitwarden(password) {
72
+ try {
73
+ // Use spawnSync with stdin to avoid shell injection
74
+ const result = spawnSync("bw", ["unlock", "--passwordstdin", "--raw"], {
75
+ input: password + "\n",
76
+ encoding: "utf-8",
77
+ });
78
+
79
+ if (result.status === 0 && result.stdout) {
80
+ return { success: true, sessionKey: result.stdout.trim() };
81
+ } else {
82
+ const errorMsg = result.stderr || result.stdout || "Unknown error";
83
+ return {
84
+ success: false,
85
+ message: `Failed to unlock vault: ${errorMsg.trim()}`,
86
+ };
87
+ }
88
+ } catch (error) {
89
+ return {
90
+ success: false,
91
+ message: `Failed to unlock vault: ${error.message}`,
92
+ };
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Discover required secrets from MCP configs
98
+ * Scans ~/.gemini/antigravity/mcp_config.json for ${VAR} patterns
99
+ */
100
+ function discoverRequiredSecrets() {
101
+ const platforms = require("./platforms");
102
+ const antigravity = platforms.getByName("antigravity");
103
+
104
+ if (!antigravity) {
105
+ return { found: false, secrets: [], reason: "Antigravity platform not detected" };
106
+ }
107
+
108
+ const mcpConfigPath = path.join(antigravity.configPath, "mcp_config.json");
109
+
110
+ if (!fs.existsSync(mcpConfigPath)) {
111
+ return { found: false, secrets: [], reason: "MCP config not found" };
112
+ }
113
+
114
+ try {
115
+ const content = fs.readFileSync(mcpConfigPath, "utf-8");
116
+
117
+ // Extract all ${VAR_NAME} patterns (supports mixed/lowercase)
118
+ const regex = /\$\{([a-zA-Z_][a-zA-Z0-9_]*)\}/g;
119
+ const matches = [...content.matchAll(regex)];
120
+ const secretNames = [...new Set(matches.map((m) => m[1]))];
121
+
122
+ return { found: true, secrets: secretNames };
123
+ } catch (error) {
124
+ return { found: false, secrets: [], reason: error.message };
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Fetch secrets from Bitwarden vault
130
+ * Only searches in "MCP Secrets" folder
131
+ */
132
+ function fetchSecretsFromBitwarden(sessionKey, secretNames) {
133
+ const results = {
134
+ found: [],
135
+ missing: [],
136
+ };
137
+
138
+ try {
139
+ // Step 1: Get folder ID for "MCP Secrets"
140
+ const foldersJson = execSync(`bw list folders --session ${sessionKey}`, {
141
+ encoding: "utf-8",
142
+ stdio: ["pipe", "pipe", "pipe"],
143
+ });
144
+ const folders = JSON.parse(foldersJson);
145
+ const mcpFolder = folders.find((f) => f.name === BITWARDEN_FOLDER);
146
+
147
+ if (!mcpFolder) {
148
+ console.warn(`\nāš ļø Folder "${BITWARDEN_FOLDER}" not found in Bitwarden vault`);
149
+ console.warn(` Create folder "${BITWARDEN_FOLDER}" and add your secrets there\n`);
150
+ // All secrets are missing since folder doesn't exist
151
+ secretNames.forEach((name) => results.missing.push(name));
152
+ return results;
153
+ }
154
+
155
+ // Step 2: List all items in "MCP Secrets" folder
156
+ const itemsJson = execSync(`bw list items --folderid ${mcpFolder.id} --session ${sessionKey}`, {
157
+ encoding: "utf-8",
158
+ stdio: ["pipe", "pipe", "pipe"],
159
+ });
160
+ const items = JSON.parse(itemsJson);
161
+
162
+ // Step 3: Match secrets by name
163
+ for (const secretName of secretNames) {
164
+ const item = items.find((i) => i.name === secretName);
165
+
166
+ if (item && item.login && item.login.password) {
167
+ results.found.push({
168
+ name: secretName,
169
+ value: item.login.password,
170
+ });
171
+ } else {
172
+ results.missing.push(secretName);
173
+ }
174
+ }
175
+ } catch (error) {
176
+ console.error(`\nāŒ Error fetching from Bitwarden: ${error.message}\n`);
177
+ // On error, mark all as missing
178
+ secretNames.forEach((name) => results.missing.push(name));
179
+ }
180
+
181
+ return results;
182
+ }
183
+
184
+ /**
185
+ * Detect shell profile file
186
+ */
187
+ function detectShellProfile() {
188
+ const shell = process.env.SHELL || "";
189
+
190
+ if (shell.includes("zsh")) {
191
+ return path.join(HOME, ".zshrc");
192
+ } else if (shell.includes("bash")) {
193
+ return path.join(HOME, ".bashrc");
194
+ }
195
+
196
+ // Default to zsh on macOS
197
+ return path.join(HOME, ".zshrc");
198
+ }
199
+
200
+ /**
201
+ * Write secrets to shell profile
202
+ */
203
+ function writeToShellProfile(secrets) {
204
+ const profilePath = detectShellProfile();
205
+ const timestamp = new Date().toISOString();
206
+
207
+ const startMarker = "# === AI Agent MCP Secrets (auto-generated, do not edit manually) ===";
208
+ const endMarker = `# === End AI Agent MCP Secrets (last updated: ${timestamp}) ===`;
209
+
210
+ let profileContent = "";
211
+ if (fs.existsSync(profilePath)) {
212
+ profileContent = fs.readFileSync(profilePath, "utf-8");
213
+ }
214
+
215
+ // Build secrets block
216
+ const secretsBlock = secrets.map((s) => `export ${s.name}="${s.value}"`).join("\n");
217
+ const fullBlock = `${startMarker}\n${secretsBlock}\n${endMarker}`;
218
+
219
+ // Check if markers already exist
220
+ const startIndex = profileContent.indexOf(startMarker);
221
+ const endIndex = profileContent.indexOf("# === End AI Agent MCP Secrets");
222
+
223
+ if (startIndex !== -1 && endIndex !== -1) {
224
+ // Replace existing block
225
+ const beforeBlock = profileContent.substring(0, startIndex);
226
+ const afterBlock = profileContent.substring(
227
+ profileContent.indexOf("\n", endIndex) + 1
228
+ );
229
+ profileContent = beforeBlock + fullBlock + "\n" + afterBlock;
230
+ } else {
231
+ // Append new block
232
+ profileContent += "\n" + fullBlock + "\n";
233
+ }
234
+
235
+ fs.writeFileSync(profilePath, profileContent, "utf-8");
236
+
237
+ return { path: profilePath, count: secrets.length };
238
+ }
239
+
240
+ /**
241
+ * Main sync function
242
+ */
243
+ async function syncSecrets() {
244
+ let sessionKey = null;
245
+
246
+ try {
247
+ console.log("\nšŸ” Bitwarden Secret Sync\n");
248
+
249
+ // 1. Validate Bitwarden CLI
250
+ const cliCheck = validateBitwardenCLI();
251
+ if (!cliCheck.valid) {
252
+ console.error(`āŒ ${cliCheck.message}\n`);
253
+ process.exit(1);
254
+ }
255
+
256
+ // 2. Validate API credentials
257
+ const authCheck = validateBitwardenAuth();
258
+ if (!authCheck.valid) {
259
+ console.error(`āŒ ${authCheck.message}\n`);
260
+ process.exit(1);
261
+ }
262
+
263
+ // 3. Prompt for password
264
+ const password = await promptPassword();
265
+
266
+ // 4. Unlock vault
267
+ console.log("\nšŸ”“ Unlocking vault...");
268
+ const unlockResult = unlockBitwarden(password);
269
+
270
+ if (!unlockResult.success) {
271
+ console.error(`āŒ ${unlockResult.message}\n`);
272
+ process.exit(1);
273
+ }
274
+
275
+ console.log("āœ“ Vault unlocked\n");
276
+ sessionKey = unlockResult.sessionKey;
277
+
278
+ // 5. Discover required secrets
279
+ console.log("šŸ” Scanning MCP configs for required secrets...");
280
+ const discovery = discoverRequiredSecrets();
281
+
282
+ if (!discovery.found) {
283
+ console.log(`āš ļø ${discovery.reason}`);
284
+ console.log("\nšŸ’” No secrets to sync. MCP configs will be available after implementing Plan 01.\n");
285
+ return;
286
+ }
287
+
288
+ if (discovery.secrets.length === 0) {
289
+ console.log("No environment variables found in MCP configs.\n");
290
+ return;
291
+ }
292
+
293
+ console.log(`Found ${discovery.secrets.length} secret(s):`);
294
+ discovery.secrets.forEach((name) => {
295
+ console.log(` • ${name}`);
296
+ });
297
+
298
+ // 6. Fetch secrets from Bitwarden
299
+ console.log(`\nšŸ” Fetching from Bitwarden (folder: ${BITWARDEN_FOLDER})...`);
300
+ const fetchResults = fetchSecretsFromBitwarden(sessionKey, discovery.secrets);
301
+
302
+ fetchResults.found.forEach((secret) => {
303
+ console.log(`āœ“ ${secret.name} (found)`);
304
+ });
305
+
306
+ fetchResults.missing.forEach((name) => {
307
+ console.log(`⚠ ${name} (not found in vault)`);
308
+ });
309
+
310
+ // 7. Write to shell profile
311
+ if (fetchResults.found.length > 0) {
312
+ console.log("\nšŸ’¾ Writing secrets to shell profile...");
313
+ const writeResult = writeToShellProfile(fetchResults.found);
314
+ console.log(`āœ“ Added ${writeResult.count} environment variable(s) to ${writeResult.path}`);
315
+ }
316
+
317
+ // 8. Summary
318
+ console.log("\nāœ… Secrets synced successfully!\n");
319
+
320
+ console.log("ā„¹ļø Next steps:");
321
+ console.log(" 1. Restart terminal or run: source ~/.zshrc");
322
+
323
+ if (fetchResults.missing.length > 0) {
324
+ console.log(` 2. Missing secrets: ${fetchResults.missing.join(", ")}`);
325
+ console.log(" Add them to Bitwarden or set manually");
326
+ }
327
+
328
+ console.log("");
329
+ } finally {
330
+ // Cleanup: Lock vault to invalidate session key
331
+ if (sessionKey) {
332
+ try {
333
+ execSync("bw lock", { stdio: "pipe" });
334
+ } catch (e) {
335
+ // Silent fail - vault may already be locked
336
+ }
337
+ sessionKey = null;
338
+ }
339
+ }
340
+ }
341
+
342
+ module.exports = {
343
+ syncSecrets,
344
+ validateBitwardenCLI,
345
+ validateBitwardenAuth,
346
+ promptPassword,
347
+ unlockBitwarden,
348
+ discoverRequiredSecrets,
349
+ fetchSecretsFromBitwarden,
350
+ detectShellProfile,
351
+ writeToShellProfile,
352
+ };