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 +46 -165
- package/bin/cli.js +168 -120
- package/package.json +4 -1
- package/scripts/postinstall.js +48 -0
- package/scripts/secret-manager.js +352 -0
package/README.md
CHANGED
|
@@ -1,196 +1,77 @@
|
|
|
1
1
|
# ai-agent-config
|
|
2
2
|
|
|
3
|
-
> Universal
|
|
3
|
+
> Universal skill & workflow manager for AI coding assistants with bi-directional GitHub sync
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/ai-agent-config)
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
7
7
|
|
|
8
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
95
|
-
ai-agent
|
|
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
|
-
#
|
|
102
|
-
ai-agent
|
|
103
|
-
```
|
|
20
|
+
# Pull skills and auto-install to platforms
|
|
21
|
+
ai-agent pull
|
|
104
22
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
-
#
|
|
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
|
-
##
|
|
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.
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
//
|
|
740
|
+
// 3. Push changes to repository
|
|
720
741
|
if (result.copied > 0) {
|
|
721
|
-
console.log("\nš¤
|
|
722
|
-
|
|
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
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
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.
|
|
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/",
|
package/scripts/postinstall.js
CHANGED
|
@@ -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
|
+
};
|