@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,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,54 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { readJson, writeJson } from '../utils/fileUtils.js';
|
|
4
|
+
import { resolvePath } from '../utils/pathResolver.js';
|
|
5
|
+
import { log } from '../utils/logger.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Build the MCP block for a tool config file from an MCP definition.
|
|
9
|
+
* Strips internal fields (id, transport). Does NOT expand ${env:VAR} in env values.
|
|
10
|
+
* @param {object} mcp
|
|
11
|
+
* @returns {object}
|
|
12
|
+
*/
|
|
13
|
+
export function buildMcpBlock(mcp) {
|
|
14
|
+
const block = {
|
|
15
|
+
command: mcp.command,
|
|
16
|
+
};
|
|
17
|
+
if (mcp.args && mcp.args.length > 0) {
|
|
18
|
+
block.args = mcp.args;
|
|
19
|
+
}
|
|
20
|
+
if (mcp.env && Object.keys(mcp.env).length > 0) {
|
|
21
|
+
block.env = mcp.env; // passthrough — NOT expanded
|
|
22
|
+
}
|
|
23
|
+
return block;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Apply MCPs to a JSON config file (merge-safe).
|
|
28
|
+
* Reads existing file, merges into [rootObject] key, writes back (or dry-runs).
|
|
29
|
+
* @param {string} targetFile may contain ~ or env vars
|
|
30
|
+
* @param {string} rootObject e.g. "mcpServers"
|
|
31
|
+
* @param {object[]} mcps canonical MCP definitions
|
|
32
|
+
* @param {boolean} dryRun
|
|
33
|
+
*/
|
|
34
|
+
export async function applyMcpJson(targetFile, rootObject, mcps, dryRun = false) {
|
|
35
|
+
const resolved = resolvePath(targetFile);
|
|
36
|
+
const existing = readJson(resolved) ?? {};
|
|
37
|
+
|
|
38
|
+
if (!existing[rootObject]) {
|
|
39
|
+
existing[rootObject] = {};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
for (const mcp of mcps) {
|
|
43
|
+
existing[rootObject][mcp.id] = buildMcpBlock(mcp);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (dryRun) {
|
|
47
|
+
log.dryRun(`Would write ${mcps.length} MCP(s) to ${resolved} under "${rootObject}"`);
|
|
48
|
+
log.dryRun(JSON.stringify(existing, null, 2));
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
writeJson(resolved, existing);
|
|
53
|
+
log.success(`Wrote ${mcps.length} MCP(s) to ${resolved}`);
|
|
54
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { getSkillsDir } from '../registry/paths.js';
|
|
3
|
+
import { fileExists, copyDir } from '../utils/fileUtils.js';
|
|
4
|
+
import { resolvePath } from '../utils/pathResolver.js';
|
|
5
|
+
import { log } from '../utils/logger.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Apply skills to a target folder.
|
|
9
|
+
* For each skill: validates SKILL.md exists, then copies the skill directory.
|
|
10
|
+
* @param {string[]} skillNames
|
|
11
|
+
* @param {string} targetFolder may contain ~ or env vars
|
|
12
|
+
* @param {boolean} dryRun
|
|
13
|
+
*/
|
|
14
|
+
export async function applySkills(skillNames, targetFolder, dryRun = false) {
|
|
15
|
+
const resolvedTarget = resolvePath(targetFolder);
|
|
16
|
+
|
|
17
|
+
for (const name of skillNames) {
|
|
18
|
+
const skillDir = path.join(getSkillsDir(), name);
|
|
19
|
+
const skillMd = path.join(skillDir, 'SKILL.md');
|
|
20
|
+
|
|
21
|
+
if (!fileExists(skillMd)) {
|
|
22
|
+
throw new Error(`Skill "${name}" is missing SKILL.md at ${skillMd}`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const dest = path.join(resolvedTarget, name);
|
|
26
|
+
|
|
27
|
+
if (dryRun) {
|
|
28
|
+
log.dryRun(`Would copy skill "${name}" → ${dest}`);
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
await copyDir(skillDir, dest);
|
|
33
|
+
log.success(`Copied skill "${name}" → ${dest}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import TOML from '@iarna/toml';
|
|
4
|
+
import { fileExists } from '../utils/fileUtils.js';
|
|
5
|
+
import { resolvePath } from '../utils/pathResolver.js';
|
|
6
|
+
import { log } from '../utils/logger.js';
|
|
7
|
+
import { buildMcpBlock } from './jsonAdapter.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Apply MCPs to a TOML config file (merge-safe).
|
|
11
|
+
* Reads existing file, merges into [rootObject] key, writes back (or dry-runs).
|
|
12
|
+
* @param {string} targetFile may contain ~ or env vars
|
|
13
|
+
* @param {string} rootObject e.g. "mcpServers"
|
|
14
|
+
* @param {object[]} mcps canonical MCP definitions
|
|
15
|
+
* @param {boolean} dryRun
|
|
16
|
+
*/
|
|
17
|
+
export async function applyMcpToml(targetFile, rootObject, mcps, dryRun = false) {
|
|
18
|
+
const resolved = resolvePath(targetFile);
|
|
19
|
+
let existing = {};
|
|
20
|
+
|
|
21
|
+
if (fileExists(resolved)) {
|
|
22
|
+
try {
|
|
23
|
+
const raw = fs.readFileSync(resolved, 'utf8');
|
|
24
|
+
existing = TOML.parse(raw);
|
|
25
|
+
} catch (e) {
|
|
26
|
+
throw new Error(`Failed to parse TOML at ${resolved}: ${e.message}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (!existing[rootObject]) {
|
|
31
|
+
existing[rootObject] = {};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
for (const mcp of mcps) {
|
|
35
|
+
existing[rootObject][mcp.id] = buildMcpBlock(mcp);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const tomlStr = TOML.stringify(existing);
|
|
39
|
+
|
|
40
|
+
if (dryRun) {
|
|
41
|
+
log.dryRun(`Would write ${mcps.length} MCP(s) to ${resolved} under "${rootObject}"`);
|
|
42
|
+
log.dryRun(tomlStr);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
fs.mkdirSync(path.dirname(resolved), { recursive: true });
|
|
47
|
+
fs.writeFileSync(resolved, tomlStr, 'utf8');
|
|
48
|
+
log.success(`Wrote ${mcps.length} MCP(s) to ${resolved}`);
|
|
49
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import { log } from '../utils/logger.js';
|
|
5
|
+
import { getMcpsDir, getSkillsDir } from '../registry/paths.js';
|
|
6
|
+
import { listFiles, listDirs, fileExists } from '../utils/fileUtils.js';
|
|
7
|
+
import { validateMcp, validateToolDef } from '../utils/validator.js';
|
|
8
|
+
import { loadTool, listTools } from '../registry/toolRegistry.js';
|
|
9
|
+
import { readJson } from '../utils/fileUtils.js';
|
|
10
|
+
import { getWorkspacePath } from '../workspace/workspaceConfig.js';
|
|
11
|
+
|
|
12
|
+
export function makeDoctorCommand() {
|
|
13
|
+
return new Command('doctor')
|
|
14
|
+
.description('Check registry integrity and validate workspace config')
|
|
15
|
+
.action(() => {
|
|
16
|
+
const issues = [];
|
|
17
|
+
|
|
18
|
+
// Check registry dirs exist
|
|
19
|
+
for (const [label, dir] of [
|
|
20
|
+
['mcps', getMcpsDir()],
|
|
21
|
+
['skills', getSkillsDir()],
|
|
22
|
+
]) {
|
|
23
|
+
if (!fs.existsSync(dir)) {
|
|
24
|
+
issues.push(`Registry dir missing: ${dir}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Validate all MCP JSON files in registry
|
|
29
|
+
const mcpsDir = getMcpsDir();
|
|
30
|
+
const mcpFiles = listFiles(mcpsDir).filter(f => f.endsWith('.json'));
|
|
31
|
+
for (const file of mcpFiles) {
|
|
32
|
+
const data = readJson(path.join(mcpsDir, file));
|
|
33
|
+
const { valid, errors } = validateMcp(data);
|
|
34
|
+
if (!valid) {
|
|
35
|
+
issues.push(`MCP file "${file}": ${errors.join('; ')}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Validate all tool definitions
|
|
40
|
+
const knownToolIds = listTools();
|
|
41
|
+
for (const toolId of knownToolIds) {
|
|
42
|
+
const toolDef = loadTool(toolId);
|
|
43
|
+
const { valid, errors } = validateToolDef(toolDef);
|
|
44
|
+
if (!valid) {
|
|
45
|
+
issues.push(`Tool "${toolId}": ${errors.join('; ')}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Validate .awm.json if present in cwd
|
|
50
|
+
const wsPath = getWorkspacePath();
|
|
51
|
+
if (fs.existsSync(wsPath)) {
|
|
52
|
+
const ws = readJson(wsPath);
|
|
53
|
+
if (!ws) {
|
|
54
|
+
issues.push('.awm.json: invalid JSON');
|
|
55
|
+
} else {
|
|
56
|
+
if (!Array.isArray(ws.tools)) issues.push('.awm.json: "tools" must be an array');
|
|
57
|
+
if (!Array.isArray(ws.mcps)) issues.push('.awm.json: "mcps" must be an array');
|
|
58
|
+
if (!Array.isArray(ws.skills)) issues.push('.awm.json: "skills" must be an array');
|
|
59
|
+
|
|
60
|
+
if (Array.isArray(ws.tools)) {
|
|
61
|
+
for (const toolId of ws.tools) {
|
|
62
|
+
if (!knownToolIds.includes(toolId)) {
|
|
63
|
+
issues.push(`.awm.json: unknown tool "${toolId}"`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const knownMcpIds = new Set(mcpFiles.map(f => f.replace(/\.json$/, '')));
|
|
69
|
+
if (Array.isArray(ws.mcps)) {
|
|
70
|
+
for (const mcpId of ws.mcps) {
|
|
71
|
+
if (!knownMcpIds.has(mcpId)) {
|
|
72
|
+
issues.push(`.awm.json: MCP "${mcpId}" not found in registry`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const knownSkills = new Set(listDirs(getSkillsDir()));
|
|
78
|
+
if (Array.isArray(ws.skills)) {
|
|
79
|
+
for (const skillName of ws.skills) {
|
|
80
|
+
if (!knownSkills.has(skillName)) {
|
|
81
|
+
issues.push(`.awm.json: skill "${skillName}" not found in registry`);
|
|
82
|
+
} else {
|
|
83
|
+
const skillMd = path.join(getSkillsDir(), skillName, 'SKILL.md');
|
|
84
|
+
if (!fileExists(skillMd)) {
|
|
85
|
+
issues.push(`.awm.json: skill "${skillName}" missing SKILL.md`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (issues.length > 0) {
|
|
94
|
+
for (const issue of issues) log.error(issue);
|
|
95
|
+
process.exit(1);
|
|
96
|
+
} else {
|
|
97
|
+
log.success('No issues found');
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import inquirer from 'inquirer';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import { log } from '../utils/logger.js';
|
|
5
|
+
import { listTools } from '../registry/toolRegistry.js';
|
|
6
|
+
import { getWorkspacePath, writeWorkspace } from '../workspace/workspaceConfig.js';
|
|
7
|
+
|
|
8
|
+
export function makeInitCommand() {
|
|
9
|
+
return new Command('init')
|
|
10
|
+
.description('Initialize a workspace config (.awm.json) in the current directory')
|
|
11
|
+
.option('--force', 'Overwrite existing .awm.json')
|
|
12
|
+
.action(async (opts) => {
|
|
13
|
+
const wsPath = getWorkspacePath();
|
|
14
|
+
|
|
15
|
+
if (fs.existsSync(wsPath) && !opts.force) {
|
|
16
|
+
throw new Error(`.awm.json already exists. Use --force to overwrite.`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const tools = listTools();
|
|
20
|
+
if (tools.length === 0) throw new Error('No tool definitions found');
|
|
21
|
+
|
|
22
|
+
const { selectedTools } = await inquirer.prompt([{
|
|
23
|
+
type: 'checkbox',
|
|
24
|
+
name: 'selectedTools',
|
|
25
|
+
message: 'Select tools to support in this workspace:',
|
|
26
|
+
choices: tools,
|
|
27
|
+
validate: v => v.length > 0 || 'Select at least one tool',
|
|
28
|
+
}]);
|
|
29
|
+
|
|
30
|
+
writeWorkspace({ tools: selectedTools, mcps: [], skills: [] });
|
|
31
|
+
log.success(`Workspace initialized with tools: ${selectedTools.join(', ')}`);
|
|
32
|
+
log.info(` Run "awm mcp add" to add MCPs, "awm skill add" to add skills.`);
|
|
33
|
+
});
|
|
34
|
+
}
|