@joehom/awm-cli 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +666 -0
- package/bin/awm.js +2 -0
- package/package.json +25 -0
- package/skills/awm-cli/SKILL.md +189 -0
- package/src/adapters/jsonAdapter.js +54 -0
- package/src/adapters/skillApplier.js +35 -0
- package/src/adapters/tomlAdapter.js +49 -0
- package/src/commands/doctor.js +100 -0
- package/src/commands/init.js +34 -0
- package/src/commands/mcp.js +253 -0
- package/src/commands/pull.js +168 -0
- package/src/commands/setup.js +31 -0
- package/src/commands/skill.js +187 -0
- package/src/commands/status.js +17 -0
- package/src/commands/tool.js +45 -0
- package/src/defaults/mcps/fetch.json +6 -0
- package/src/defaults/mcps/filesystem.json +6 -0
- package/src/defaults/mcps/github.json +9 -0
- package/src/defaults/mcps/memory.json +6 -0
- package/src/defaults/skills/awm-cli/SKILL.md +189 -0
- package/src/defaults/tools/claude-code.json +27 -0
- package/src/defaults/tools/codex.json +27 -0
- package/src/defaults/tools/copilot-cli.json +18 -0
- package/src/defaults/tools/cursor.json +27 -0
- package/src/defaults/tools/gemini-cli.json +27 -0
- package/src/defaults/tools/github-copilot.json +23 -0
- package/src/defaults/tools/windsurf.json +23 -0
- package/src/index.js +35 -0
- package/src/registry/mcpRegistry.js +68 -0
- package/src/registry/paths.js +21 -0
- package/src/registry/skillRegistry.js +61 -0
- package/src/registry/toolRegistry.js +43 -0
- package/src/seed.js +131 -0
- package/src/tools/claude-code.json +27 -0
- package/src/tools/codex.json +27 -0
- package/src/tools/copilot-cli.json +15 -0
- package/src/tools/cursor.json +27 -0
- package/src/tools/gemini-cli.json +27 -0
- package/src/tools/github-copilot.json +23 -0
- package/src/tools/windsurf.json +23 -0
- package/src/utils/fileUtils.js +76 -0
- package/src/utils/logger.js +17 -0
- package/src/utils/pathResolver.js +40 -0
- package/src/utils/validator.js +68 -0
- package/src/workspace/applyWorkspace.js +81 -0
- package/src/workspace/workspaceConfig.js +34 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import inquirer from 'inquirer';
|
|
3
|
+
import { log } from '../utils/logger.js';
|
|
4
|
+
import { listTools } from '../registry/toolRegistry.js';
|
|
5
|
+
import { readWorkspace, writeWorkspace } from '../workspace/workspaceConfig.js';
|
|
6
|
+
import { applyAll } from '../workspace/applyWorkspace.js';
|
|
7
|
+
|
|
8
|
+
export function makeToolCommand() {
|
|
9
|
+
return new Command('tool')
|
|
10
|
+
.description('Select which tools this workspace targets, then resync MCPs and skills')
|
|
11
|
+
.action(async () => {
|
|
12
|
+
const allTools = listTools();
|
|
13
|
+
if (allTools.length === 0) throw new Error('No tool definitions found');
|
|
14
|
+
|
|
15
|
+
const ws = readWorkspace();
|
|
16
|
+
const currentTools = ws?.tools ?? [];
|
|
17
|
+
|
|
18
|
+
let answer;
|
|
19
|
+
try {
|
|
20
|
+
answer = await inquirer.prompt([{
|
|
21
|
+
type: 'checkbox',
|
|
22
|
+
name: 'tools',
|
|
23
|
+
message: 'Select tools for this workspace (space to toggle, enter to confirm):',
|
|
24
|
+
choices: allTools.map(id => ({ name: id, value: id, checked: currentTools.includes(id) })),
|
|
25
|
+
validate: v => v.length > 0 || 'Select at least one tool',
|
|
26
|
+
}]);
|
|
27
|
+
} catch (err) {
|
|
28
|
+
if (err.name === 'ExitPromptError') {
|
|
29
|
+
log.info('Cancelled.');
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
throw err;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const updated = { ...(ws ?? { mcps: [], skills: [] }), tools: answer.tools };
|
|
36
|
+
writeWorkspace(updated);
|
|
37
|
+
|
|
38
|
+
if (updated.mcps.length > 0 || updated.skills.length > 0) {
|
|
39
|
+
log.info('Resyncing MCPs and skills to updated tool list...');
|
|
40
|
+
await applyAll(updated);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
log.success(`Tools updated: ${answer.tools.join(', ')}`);
|
|
44
|
+
});
|
|
45
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: awm-cli
|
|
3
|
+
description: Use when the user asks to manage MCP servers, skills, or workspace configs for AI coding tools (Claude Code, Cursor, Codex, Copilot CLI, Gemini CLI, Windsurf, GitHub Copilot) using the awm CLI. Triggers on requests to register MCPs, add MCPs/skills to a project, import configs, or check registry health.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# AWM — Agent Workspace Manager CLI
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
AWM is a local-first CLI that keeps a central registry of MCP servers and skills at `~/.agent-workspace/registry/`, then writes the correct config files into projects for supported AI tools.
|
|
11
|
+
|
|
12
|
+
**Core pattern:** Register once → add to workspace → auto-applied to all tools.
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
awm init # create .awm.json in project root
|
|
16
|
+
awm mcp add # pick MCPs from registry → apply to all tools
|
|
17
|
+
awm skill add # pick skills from registry → apply to all tools
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## RULES — Follow These Before Running Any Command
|
|
23
|
+
|
|
24
|
+
**ALWAYS ask before acting. Never assume.**
|
|
25
|
+
|
|
26
|
+
1. **Check existing state first** — Run `awm status` if `.awm.json` exists, and `awm mcp list -g` / `awm skill list -g` to see what's in the registry. Do not recreate things that already exist.
|
|
27
|
+
|
|
28
|
+
2. **Only add what the user explicitly requested** — If the user says "add fetch and memory", add ONLY fetch and memory. Do not add other MCPs or skills, even if they exist in the registry.
|
|
29
|
+
|
|
30
|
+
3. **Confirm the plan before running** — Before executing any sequence of commands, show the user what you are about to do:
|
|
31
|
+
```
|
|
32
|
+
I will run:
|
|
33
|
+
awm init (creates .awm.json, you pick tools)
|
|
34
|
+
awm mcp add (you pick: fetch, memory)
|
|
35
|
+
Proceed?
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
4. **Run `awm init` only once per project** — It creates `.awm.json`. If it already exists, use `awm status` instead.
|
|
39
|
+
|
|
40
|
+
5. **`mcp add` and `skill add` auto-apply** — After selection, MCPs and skills are immediately written to all tool config files in the project.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Quick Reference
|
|
45
|
+
|
|
46
|
+
### Workspace commands
|
|
47
|
+
|
|
48
|
+
| Command | What it does |
|
|
49
|
+
|---------|-------------|
|
|
50
|
+
| `awm init` | Interactive: pick tools → create `.awm.json` in cwd |
|
|
51
|
+
| `awm status` | Show current project tools, MCPs, skills, lastSync |
|
|
52
|
+
| `awm doctor` | Validate registry + `.awm.json` health |
|
|
53
|
+
|
|
54
|
+
### MCP commands
|
|
55
|
+
|
|
56
|
+
| Command | What it does |
|
|
57
|
+
|---------|-------------|
|
|
58
|
+
| `awm mcp register [id]` | Interactive prompt → save MCP to global registry |
|
|
59
|
+
| `awm mcp unregister <id>` | Remove MCP from global registry |
|
|
60
|
+
| `awm mcp import` | Scan cwd tool config files → register found MCPs |
|
|
61
|
+
| `awm mcp add` | Checkbox from registry → add to `.awm.json` + apply to tools |
|
|
62
|
+
| `awm mcp delete <id>` | Remove MCP from `.awm.json` + re-apply tools |
|
|
63
|
+
| `awm mcp list` | List MCPs in current workspace |
|
|
64
|
+
| `awm mcp list -g` | List all MCPs in global registry |
|
|
65
|
+
| `awm mcp show <id>` | Print full MCP definition |
|
|
66
|
+
|
|
67
|
+
### Skill commands
|
|
68
|
+
|
|
69
|
+
| Command | What it does |
|
|
70
|
+
|---------|-------------|
|
|
71
|
+
| `awm skill register <name> --from <path>` | Copy dir/file into global registry |
|
|
72
|
+
| `awm skill unregister <name>` | Remove skill from global registry |
|
|
73
|
+
| `awm skill import` | Scan `.claude/skills/`, `.agents/skills/` → register found skills |
|
|
74
|
+
| `awm skill add` | Checkbox from registry → add to `.awm.json` + apply to tools |
|
|
75
|
+
| `awm skill delete <name>` | Remove skill from `.awm.json` |
|
|
76
|
+
| `awm skill list` | List skills in current workspace |
|
|
77
|
+
| `awm skill list -g` | List all skills in global registry |
|
|
78
|
+
| `awm skill show <name>` | Print SKILL.md |
|
|
79
|
+
|
|
80
|
+
### Other commands
|
|
81
|
+
|
|
82
|
+
| Command | What it does |
|
|
83
|
+
|---------|-------------|
|
|
84
|
+
| `awm tool list` | List known tool IDs |
|
|
85
|
+
| `awm tool show <id>` | Print tool definition JSON |
|
|
86
|
+
| `awm setup [--force]` | Re-seed default MCPs and skills into registry |
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Common Workflows
|
|
91
|
+
|
|
92
|
+
### Set up a new project
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
# 1. Check registry state
|
|
96
|
+
awm mcp list -g
|
|
97
|
+
awm skill list -g
|
|
98
|
+
|
|
99
|
+
# 2. Initialize workspace (interactive: pick tools)
|
|
100
|
+
awm init
|
|
101
|
+
|
|
102
|
+
# 3. Add ONLY the MCPs the user requested
|
|
103
|
+
awm mcp add # checkbox → select fetch, memory (if user asked)
|
|
104
|
+
|
|
105
|
+
# 4. Add ONLY the skills the user requested
|
|
106
|
+
awm skill add # checkbox → select awm-cli (if user asked)
|
|
107
|
+
|
|
108
|
+
# 5. Verify
|
|
109
|
+
awm status
|
|
110
|
+
cat .awm.json
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Register a new MCP from a file
|
|
114
|
+
|
|
115
|
+
To register an MCP defined in a JSON file, use `mcp register` interactively, or import existing tool configs:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
awm mcp import # scans .mcp.json, .cursor/mcp.json, .gemini/settings.json, etc.
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Register a skill
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
# From a directory (must contain SKILL.md)
|
|
125
|
+
awm skill register clean-arch --from ./skills/clean-arch/
|
|
126
|
+
|
|
127
|
+
# From a single markdown file
|
|
128
|
+
awm skill register tdd --from ./TDD.md
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Workspace Config — `.awm.json`
|
|
134
|
+
|
|
135
|
+
Created by `awm init` at the project root:
|
|
136
|
+
|
|
137
|
+
```json
|
|
138
|
+
{
|
|
139
|
+
"tools": ["claude-code", "cursor", "windsurf"],
|
|
140
|
+
"mcps": ["fetch", "memory"],
|
|
141
|
+
"skills": ["awm-cli"],
|
|
142
|
+
"lastSync": "2026-03-08T12:00:00Z"
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
- **`tools`** — which AI tools to write configs for in this project
|
|
147
|
+
- **`mcps`** — MCP IDs from the global registry active in this project
|
|
148
|
+
- **`skills`** — skill names from the global registry active in this project
|
|
149
|
+
- **`lastSync`** — timestamp of last `mcp add` or `skill add` apply
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## Apply Output by Tool
|
|
154
|
+
|
|
155
|
+
| Tool | MCP file | Format | Skills |
|
|
156
|
+
|------|----------|--------|--------|
|
|
157
|
+
| `claude-code` | `.mcp.json` | JSON | `.claude/skills/<name>/` |
|
|
158
|
+
| `codex` | `.codex/config.toml` | TOML | — |
|
|
159
|
+
| `cursor` | `.cursor/mcp.json` | JSON | — |
|
|
160
|
+
| `gemini-cli` | `.gemini/settings.json` | JSON | — |
|
|
161
|
+
| `github-copilot` | `.vscode/mcp.json` | JSON (`servers`) | — |
|
|
162
|
+
| `windsurf` | — | — | — |
|
|
163
|
+
| `copilot-cli` | — | — | — |
|
|
164
|
+
|
|
165
|
+
Notes:
|
|
166
|
+
- **claude-code** is the only tool that supports skills.
|
|
167
|
+
- **Merge-safe writes** — `mcp add` only touches the MCP root key. All other keys in tool config files are preserved.
|
|
168
|
+
- **Env passthrough** — `${env:VAR}` values are written verbatim; the target tool resolves them at runtime.
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## Default MCPs (pre-installed)
|
|
173
|
+
|
|
174
|
+
| ID | Command | Notes |
|
|
175
|
+
|----|---------|-------|
|
|
176
|
+
| `github` | `npx -y @modelcontextprotocol/server-github` | Needs `GITHUB_TOKEN` env var |
|
|
177
|
+
| `filesystem` | `npx -y @modelcontextprotocol/server-filesystem` | Local file access |
|
|
178
|
+
| `memory` | `npx -y @modelcontextprotocol/server-memory` | Persistent knowledge graph |
|
|
179
|
+
| `fetch` | `uvx mcp-server-fetch` | Web fetching (requires `uv`) |
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## Environment Variable Override
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
AWM_REGISTRY=/path/to/registry awm mcp list -g
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Default registry: `~/.agent-workspace/registry/`
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "claude-code",
|
|
3
|
+
"name": "Claude Code",
|
|
4
|
+
"supports": {
|
|
5
|
+
"mcp": true,
|
|
6
|
+
"skills": true
|
|
7
|
+
},
|
|
8
|
+
"mcp": {
|
|
9
|
+
"format": "json",
|
|
10
|
+
"project": {
|
|
11
|
+
"targetFile": ".mcp.json",
|
|
12
|
+
"rootObject": "mcpServers"
|
|
13
|
+
},
|
|
14
|
+
"global": {
|
|
15
|
+
"targetFile": "~/.claude.json",
|
|
16
|
+
"rootObject": "mcpServers"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"skills": {
|
|
20
|
+
"project": {
|
|
21
|
+
"targetFolder": ".claude/skills"
|
|
22
|
+
},
|
|
23
|
+
"global": {
|
|
24
|
+
"targetFolder": "~/.claude/skills"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "codex",
|
|
3
|
+
"name": "Codex CLI",
|
|
4
|
+
"supports": {
|
|
5
|
+
"mcp": true,
|
|
6
|
+
"skills": true
|
|
7
|
+
},
|
|
8
|
+
"mcp": {
|
|
9
|
+
"format": "toml",
|
|
10
|
+
"project": {
|
|
11
|
+
"targetFile": ".codex/config.toml",
|
|
12
|
+
"rootObject": "mcp_servers"
|
|
13
|
+
},
|
|
14
|
+
"global": {
|
|
15
|
+
"targetFile": "~/.codex/config.toml",
|
|
16
|
+
"rootObject": "mcp_servers"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"skills": {
|
|
20
|
+
"project": {
|
|
21
|
+
"targetFolder": ".agents/skills"
|
|
22
|
+
},
|
|
23
|
+
"global": {
|
|
24
|
+
"targetFolder": "~/.agents/skills"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "copilot-cli",
|
|
3
|
+
"name": "Copilot CLI",
|
|
4
|
+
"supports": {
|
|
5
|
+
"mcp": true,
|
|
6
|
+
"skills": true
|
|
7
|
+
},
|
|
8
|
+
"mcp": {
|
|
9
|
+
"format": "json",
|
|
10
|
+
"global": {
|
|
11
|
+
"targetFile": "~/.copilot/mcp-config.json",
|
|
12
|
+
"rootObject": "servers"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"skills": {
|
|
16
|
+
"global": "~/.copilot/skills"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "cursor",
|
|
3
|
+
"name": "Cursor",
|
|
4
|
+
"supports": {
|
|
5
|
+
"mcp": true,
|
|
6
|
+
"skills": true
|
|
7
|
+
},
|
|
8
|
+
"mcp": {
|
|
9
|
+
"format": "json",
|
|
10
|
+
"project": {
|
|
11
|
+
"targetFile": ".cursor/mcp.json",
|
|
12
|
+
"rootObject": "mcpServers"
|
|
13
|
+
},
|
|
14
|
+
"global": {
|
|
15
|
+
"targetFile": "~/.cursor/mcp.json",
|
|
16
|
+
"rootObject": "mcpServers"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"skills": {
|
|
20
|
+
"project": {
|
|
21
|
+
"targetFolder": ".cursor/rules"
|
|
22
|
+
},
|
|
23
|
+
"global": {
|
|
24
|
+
"targetFolder": "~/.cursor/rules"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "gemini-cli",
|
|
3
|
+
"name": "Gemini CLI",
|
|
4
|
+
"supports": {
|
|
5
|
+
"mcp": true,
|
|
6
|
+
"skills": true
|
|
7
|
+
},
|
|
8
|
+
"mcp": {
|
|
9
|
+
"format": "json",
|
|
10
|
+
"project": {
|
|
11
|
+
"targetFile": ".gemini/settings.json",
|
|
12
|
+
"rootObject": "mcpServers"
|
|
13
|
+
},
|
|
14
|
+
"global": {
|
|
15
|
+
"targetFile": "~/.gemini/settings.json",
|
|
16
|
+
"rootObject": "mcpServers"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"skills": {
|
|
20
|
+
"project": {
|
|
21
|
+
"targetFolder": ".gemini/skills"
|
|
22
|
+
},
|
|
23
|
+
"global": {
|
|
24
|
+
"targetFolder": "~/.gemini/skills"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "github-copilot",
|
|
3
|
+
"name": "GitHub Copilot (VS Code)",
|
|
4
|
+
"supports": {
|
|
5
|
+
"mcp": true,
|
|
6
|
+
"skills": true
|
|
7
|
+
},
|
|
8
|
+
"mcp": {
|
|
9
|
+
"format": "json",
|
|
10
|
+
"project": {
|
|
11
|
+
"targetFile": ".vscode/mcp.json",
|
|
12
|
+
"rootObject": "servers"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"skills": {
|
|
16
|
+
"project": {
|
|
17
|
+
"targetFolder": ".github/skills"
|
|
18
|
+
},
|
|
19
|
+
"global": {
|
|
20
|
+
"targetFolder": "~/.copilot/skills"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "windsurf",
|
|
3
|
+
"name": "Windsurf",
|
|
4
|
+
"supports": {
|
|
5
|
+
"mcp": true,
|
|
6
|
+
"skills": true
|
|
7
|
+
},
|
|
8
|
+
"mcp": {
|
|
9
|
+
"format": "json",
|
|
10
|
+
"global": {
|
|
11
|
+
"targetFile": "~/.codeium/windsurf/mcp_config.json",
|
|
12
|
+
"rootObject": "mcpServers"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"skills": {
|
|
16
|
+
"project": {
|
|
17
|
+
"targetFolder": ".windsurf/skills"
|
|
18
|
+
},
|
|
19
|
+
"global": {
|
|
20
|
+
"targetFolder": "~/.codeium/windsurf/skills"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { ensureRegistryDirs } from './registry/paths.js';
|
|
3
|
+
import { log } from './utils/logger.js';
|
|
4
|
+
import { makeMcpCommand } from './commands/mcp.js';
|
|
5
|
+
import { makeSkillCommand } from './commands/skill.js';
|
|
6
|
+
import { makeInitCommand } from './commands/init.js';
|
|
7
|
+
import { makePullCommand } from './commands/pull.js';
|
|
8
|
+
import { makeStatusCommand } from './commands/status.js';
|
|
9
|
+
import { makeDoctorCommand } from './commands/doctor.js';
|
|
10
|
+
import { makeToolCommand } from './commands/tool.js';
|
|
11
|
+
import { makeSetupCommand } from './commands/setup.js';
|
|
12
|
+
|
|
13
|
+
const program = new Command();
|
|
14
|
+
|
|
15
|
+
program
|
|
16
|
+
.name('awm')
|
|
17
|
+
.description('Agent Workspace Manager — manage MCP servers, skills, and workspace configs for AI coding tools')
|
|
18
|
+
.version('1.0.0');
|
|
19
|
+
|
|
20
|
+
program.addCommand(makeInitCommand());
|
|
21
|
+
program.addCommand(makePullCommand());
|
|
22
|
+
program.addCommand(makeStatusCommand());
|
|
23
|
+
program.addCommand(makeMcpCommand());
|
|
24
|
+
program.addCommand(makeSkillCommand());
|
|
25
|
+
program.addCommand(makeDoctorCommand());
|
|
26
|
+
program.addCommand(makeToolCommand());
|
|
27
|
+
program.addCommand(makeSetupCommand());
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
ensureRegistryDirs();
|
|
31
|
+
await program.parseAsync(process.argv);
|
|
32
|
+
} catch (err) {
|
|
33
|
+
log.error(err.message);
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { getMcpsDir } from './paths.js';
|
|
4
|
+
import { readJson, writeJson, listFiles } from '../utils/fileUtils.js';
|
|
5
|
+
import { validateMcp } from '../utils/validator.js';
|
|
6
|
+
|
|
7
|
+
function mcpFile(id) {
|
|
8
|
+
return path.join(getMcpsDir(), `${id}.json`);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* List all registered MCP IDs.
|
|
13
|
+
* @returns {string[]}
|
|
14
|
+
*/
|
|
15
|
+
export function listMcps() {
|
|
16
|
+
return listFiles(getMcpsDir())
|
|
17
|
+
.filter(f => f.endsWith('.json'))
|
|
18
|
+
.map(f => f.replace(/\.json$/, ''));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Get an MCP definition by ID. Returns null if not found.
|
|
23
|
+
* @param {string} id
|
|
24
|
+
* @returns {object|null}
|
|
25
|
+
*/
|
|
26
|
+
export function getMcp(id) {
|
|
27
|
+
return readJson(mcpFile(id));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Save an MCP definition (validates first).
|
|
32
|
+
* Throws if validation fails.
|
|
33
|
+
* @param {object} mcp
|
|
34
|
+
*/
|
|
35
|
+
export function saveMcp(mcp) {
|
|
36
|
+
const { valid, errors } = validateMcp(mcp);
|
|
37
|
+
if (!valid) {
|
|
38
|
+
throw new Error(`Invalid MCP definition: ${errors.join('; ')}`);
|
|
39
|
+
}
|
|
40
|
+
writeJson(mcpFile(mcp.id), mcp);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Remove an MCP by ID. Throws if not found.
|
|
45
|
+
* @param {string} id
|
|
46
|
+
*/
|
|
47
|
+
export function removeMcp(id) {
|
|
48
|
+
const file = mcpFile(id);
|
|
49
|
+
if (!fs.existsSync(file)) {
|
|
50
|
+
throw new Error(`MCP "${id}" not found in registry`);
|
|
51
|
+
}
|
|
52
|
+
fs.rmSync(file);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Import one or more MCPs from a JSON value (single object or array).
|
|
57
|
+
* @param {object|object[]} data
|
|
58
|
+
* @returns {string[]} imported IDs
|
|
59
|
+
*/
|
|
60
|
+
export function importMcps(data) {
|
|
61
|
+
const items = Array.isArray(data) ? data : [data];
|
|
62
|
+
const imported = [];
|
|
63
|
+
for (const item of items) {
|
|
64
|
+
saveMcp(item);
|
|
65
|
+
imported.push(item.id);
|
|
66
|
+
}
|
|
67
|
+
return imported;
|
|
68
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { getRegistryRoot } from '../utils/pathResolver.js';
|
|
4
|
+
|
|
5
|
+
export function getMcpsDir() {
|
|
6
|
+
return path.join(getRegistryRoot(), 'mcps');
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function getSkillsDir() {
|
|
10
|
+
return path.join(getRegistryRoot(), 'skills');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function getToolsDir() {
|
|
14
|
+
return path.join(getRegistryRoot(), 'tools');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function ensureRegistryDirs() {
|
|
18
|
+
for (const dir of [getMcpsDir(), getSkillsDir(), getToolsDir()]) {
|
|
19
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { getSkillsDir } from './paths.js';
|
|
4
|
+
import { listDirs, fileExists, copyDir } from '../utils/fileUtils.js';
|
|
5
|
+
|
|
6
|
+
function skillDir(name) {
|
|
7
|
+
return path.join(getSkillsDir(), name);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* List all registered skill names.
|
|
12
|
+
* @returns {string[]}
|
|
13
|
+
*/
|
|
14
|
+
export function listSkills() {
|
|
15
|
+
return listDirs(getSkillsDir());
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Add a skill from a source path (directory or single .md file).
|
|
20
|
+
* If src is a directory, it is copied as-is.
|
|
21
|
+
* If src is a .md file, it is placed into a new folder named after the skill,
|
|
22
|
+
* saved as SKILL.md.
|
|
23
|
+
* @param {string} name
|
|
24
|
+
* @param {string} src absolute path to source
|
|
25
|
+
*/
|
|
26
|
+
export async function addSkill(name, src) {
|
|
27
|
+
const dest = skillDir(name);
|
|
28
|
+
const stat = fs.statSync(src);
|
|
29
|
+
if (stat.isDirectory()) {
|
|
30
|
+
await copyDir(src, dest);
|
|
31
|
+
} else if (src.endsWith('.md')) {
|
|
32
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
33
|
+
fs.copyFileSync(src, path.join(dest, 'SKILL.md'));
|
|
34
|
+
} else {
|
|
35
|
+
throw new Error(`Skill source must be a directory or .md file: ${src}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Show the SKILL.md contents for a skill.
|
|
41
|
+
* Returns null if SKILL.md doesn't exist.
|
|
42
|
+
* @param {string} name
|
|
43
|
+
* @returns {string|null}
|
|
44
|
+
*/
|
|
45
|
+
export function showSkill(name) {
|
|
46
|
+
const skillMd = path.join(skillDir(name), 'SKILL.md');
|
|
47
|
+
if (!fileExists(skillMd)) return null;
|
|
48
|
+
return fs.readFileSync(skillMd, 'utf8');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Remove a skill by name. Throws if not found.
|
|
53
|
+
* @param {string} name
|
|
54
|
+
*/
|
|
55
|
+
export function removeSkill(name) {
|
|
56
|
+
const dir = skillDir(name);
|
|
57
|
+
if (!fs.existsSync(dir)) {
|
|
58
|
+
throw new Error(`Skill "${name}" not found in registry`);
|
|
59
|
+
}
|
|
60
|
+
fs.rmSync(dir, { recursive: true });
|
|
61
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { getToolsDir } from './paths.js';
|
|
5
|
+
import { readJson, listFiles } from '../utils/fileUtils.js';
|
|
6
|
+
|
|
7
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const BUILTIN_TOOLS_DIR = path.join(__dirname, '..', 'tools');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Load a tool definition by ID.
|
|
12
|
+
* Checks user registry tools/ first, then falls back to builtin src/tools/.
|
|
13
|
+
* Returns null if not found.
|
|
14
|
+
* @param {string} id
|
|
15
|
+
* @returns {object|null}
|
|
16
|
+
*/
|
|
17
|
+
export function loadTool(id) {
|
|
18
|
+
// User override
|
|
19
|
+
const userFile = path.join(getToolsDir(), `${id}.json`);
|
|
20
|
+
if (fs.existsSync(userFile)) return readJson(userFile);
|
|
21
|
+
|
|
22
|
+
// Builtin
|
|
23
|
+
const builtinFile = path.join(BUILTIN_TOOLS_DIR, `${id}.json`);
|
|
24
|
+
if (fs.existsSync(builtinFile)) return readJson(builtinFile);
|
|
25
|
+
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* List all known tool IDs (union of user overrides and builtins).
|
|
31
|
+
* @returns {string[]}
|
|
32
|
+
*/
|
|
33
|
+
export function listTools() {
|
|
34
|
+
const userTools = listFiles(getToolsDir())
|
|
35
|
+
.filter(f => f.endsWith('.json'))
|
|
36
|
+
.map(f => f.replace(/\.json$/, ''));
|
|
37
|
+
|
|
38
|
+
const builtinTools = listFiles(BUILTIN_TOOLS_DIR)
|
|
39
|
+
.filter(f => f.endsWith('.json'))
|
|
40
|
+
.map(f => f.replace(/\.json$/, ''));
|
|
41
|
+
|
|
42
|
+
return [...new Set([...userTools, ...builtinTools])];
|
|
43
|
+
}
|