@1tool/js-boost 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +200 -0
- package/bin/js-boost.js +2 -0
- package/package.json +35 -0
- package/src/agents.js +62 -0
- package/src/cli.js +162 -0
- package/src/configure/mcp.js +223 -0
- package/src/generators/agents.js +64 -0
- package/src/generators/claude.js +60 -0
- package/src/generators/cursor.js +69 -0
- package/src/generators/junie.js +42 -0
- package/src/generators/kiro.js +35 -0
- package/src/index.js +125 -0
- package/src/init.js +134 -0
- package/src/utils/detect.js +58 -0
- package/src/utils/mcp.js +97 -0
- package/src/utils/reader.js +92 -0
- package/src/watch.js +58 -0
package/README.md
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# ⚡ js-boost
|
|
2
|
+
|
|
3
|
+
> Laravel Boost for JavaScript — generate agent files for Claude Code, Cursor, Junie, Codex, Copilot, Kiro, and more from a single `.ai/` source of truth.
|
|
4
|
+
|
|
5
|
+
Instead of manually maintaining separate instruction files for each AI agent, you write your guidelines and skills once in `.ai/` and `js-boost` generates all the agent-specific files automatically.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## How it works
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
.ai/
|
|
13
|
+
├── guidelines/
|
|
14
|
+
│ ├── general.md ← coding conventions
|
|
15
|
+
│ └── testing.md ← testing standards
|
|
16
|
+
└── skills/
|
|
17
|
+
└── my-skill/
|
|
18
|
+
└── SKILL.md ← on-demand skill (loaded when relevant)
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Run `npx js-boost generate` and get the right file for each configured agent:
|
|
22
|
+
|
|
23
|
+
| File | Agent |
|
|
24
|
+
|---|---|
|
|
25
|
+
| `AGENTS.md` | Amp, Codex, GitHub Copilot, Gemini, OpenCode |
|
|
26
|
+
| `CLAUDE.md` | Claude Code |
|
|
27
|
+
| `.mcp.json` | Claude Code + Codex |
|
|
28
|
+
| `.junie/guidelines.md` + `.junie/mcp.json` | JetBrains Junie |
|
|
29
|
+
| `.cursor/rules/js-boost.mdc` + `.cursorrules` | Cursor |
|
|
30
|
+
| `.kiro/steering/guidelines.md` | Kiro |
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Quick Start
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
# 1. Scaffold .ai/ and select your agents
|
|
38
|
+
npx js-boost init
|
|
39
|
+
|
|
40
|
+
# 2. Edit guidelines and skills in .ai/
|
|
41
|
+
|
|
42
|
+
# 3. Generate agent files
|
|
43
|
+
npx js-boost generate
|
|
44
|
+
|
|
45
|
+
# 4. (Optional) Watch mode — auto-regenerate on save
|
|
46
|
+
npx js-boost watch
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Commands
|
|
52
|
+
|
|
53
|
+
### `js-boost init`
|
|
54
|
+
|
|
55
|
+
Scaffolds the `.ai/` folder with placeholder guidelines and a starter skill. Prompts you to select which AI agents to configure and saves the selection to `js-boost.config.json`.
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
npx js-boost init
|
|
59
|
+
npx js-boost init --force # overwrite existing files
|
|
60
|
+
npx js-boost init --dir ./my-project
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### `js-boost agents`
|
|
64
|
+
|
|
65
|
+
Re-run the agent selection prompt without re-scaffolding `.ai/`. Use this to add or remove agents after the initial setup.
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
npx js-boost agents
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### `js-boost mcp`
|
|
72
|
+
|
|
73
|
+
Interactive wizard to add, remove, or toggle MCP servers. Changes are saved to `js-boost.config.json` — run `generate` afterwards to apply them to agent files.
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
npx js-boost mcp
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**Add a remote server:**
|
|
80
|
+
```
|
|
81
|
+
✔ Server key: my-api
|
|
82
|
+
✔ Server type: Remote (HTTP / SSE url)
|
|
83
|
+
✔ URL: https://my-mcp.com/mcp
|
|
84
|
+
✔ Description (optional): Internal API tools
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**Add a local (stdio) server:**
|
|
88
|
+
```
|
|
89
|
+
✔ Server key: local-tools
|
|
90
|
+
✔ Server type: Local (stdio process)
|
|
91
|
+
✔ Command: node
|
|
92
|
+
✔ Arguments: ./mcp-server.js --port 3000
|
|
93
|
+
✔ Environment variables: API_KEY=secret,NODE_ENV=production
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### `js-boost generate`
|
|
97
|
+
|
|
98
|
+
Reads `.ai/guidelines/*.md` and `.ai/skills/*/SKILL.md`, then generates files for all selected agents. Falls back to generating all supported formats if no agents are configured.
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
npx js-boost generate
|
|
102
|
+
npx js-boost gen # alias
|
|
103
|
+
npx js-boost generate --verbose
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### `js-boost watch`
|
|
107
|
+
|
|
108
|
+
Watches `.ai/` for changes and regenerates automatically (debounced 300ms).
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
npx js-boost watch
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### `js-boost status`
|
|
115
|
+
|
|
116
|
+
Shows configured agents, guidelines, skills, MCP servers, and which files will be generated.
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
npx js-boost status
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## Supported Agents
|
|
125
|
+
|
|
126
|
+
| Key | Agent | Auto-detected |
|
|
127
|
+
|---|---|---|
|
|
128
|
+
| `amp` | Amp | `amp` in PATH or `~/.amp` |
|
|
129
|
+
| `claude_code` | Claude Code | `~/.claude` |
|
|
130
|
+
| `codex` | Codex | `codex` in PATH |
|
|
131
|
+
| `copilot` | GitHub Copilot | — |
|
|
132
|
+
| `cursor` | Cursor | `~/.cursor` |
|
|
133
|
+
| `gemini` | Gemini | — |
|
|
134
|
+
| `junie` | JetBrains Junie | `.junie/` in project |
|
|
135
|
+
| `kiro` | Kiro | `~/.kiro` |
|
|
136
|
+
| `opencode` | OpenCode | `opencode` in PATH |
|
|
137
|
+
|
|
138
|
+
During `init` (and `agents`), installed agents are pre-selected automatically based on what's detected on your system.
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Configuration
|
|
143
|
+
|
|
144
|
+
`js-boost.config.json` is managed by the CLI commands (`init`, `agents`, `mcp`) and committed to your repo.
|
|
145
|
+
|
|
146
|
+
```json
|
|
147
|
+
{
|
|
148
|
+
"projectName": "my-app",
|
|
149
|
+
"projectDescription": "",
|
|
150
|
+
"agents": ["claude_code", "cursor", "codex"],
|
|
151
|
+
"mcpServers": {
|
|
152
|
+
"my-api": {
|
|
153
|
+
"type": "remote",
|
|
154
|
+
"url": "https://my-mcp.com/mcp",
|
|
155
|
+
"description": "Internal API tools"
|
|
156
|
+
},
|
|
157
|
+
"local-tools": {
|
|
158
|
+
"type": "stdio",
|
|
159
|
+
"command": "node",
|
|
160
|
+
"args": ["./mcp-server.js"],
|
|
161
|
+
"env": { "API_KEY": "secret" }
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
"disableMcpServers": []
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
MCP servers are written to the right format per agent:
|
|
169
|
+
|
|
170
|
+
| Agent | Format | Remote servers |
|
|
171
|
+
|---|---|---|
|
|
172
|
+
| Claude Code, Codex | `.mcp.json` | wrapped in `mcp-remote` |
|
|
173
|
+
| Junie | `.junie/mcp.json` | referenced by URL directly |
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Skills
|
|
178
|
+
|
|
179
|
+
Skills in `.ai/skills/` use a `SKILL.md` with YAML frontmatter:
|
|
180
|
+
|
|
181
|
+
```markdown
|
|
182
|
+
---
|
|
183
|
+
name: my-skill
|
|
184
|
+
description: Use this skill when doing X.
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
# My Skill
|
|
188
|
+
|
|
189
|
+
Steps, patterns, and examples...
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
- **Claude Code** loads skills on-demand based on the `description`
|
|
193
|
+
- **Codex** supports `$skill-name` invocation and implicit matching
|
|
194
|
+
- **Junie / Cursor / Kiro** receive a reference list pointing to each `SKILL.md`
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## License
|
|
199
|
+
|
|
200
|
+
MIT
|
package/bin/js-boost.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@1tool/js-boost",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Laravel Boost-inspired CLI for JavaScript projects — generates agent files from your .ai/ folder",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"js-boost": "bin/js-boost.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "node bin/js-boost.js"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"bin/",
|
|
15
|
+
"src/"
|
|
16
|
+
],
|
|
17
|
+
"keywords": [
|
|
18
|
+
"ai",
|
|
19
|
+
"agents",
|
|
20
|
+
"mcp",
|
|
21
|
+
"claude",
|
|
22
|
+
"codex",
|
|
23
|
+
"junie",
|
|
24
|
+
"cursor",
|
|
25
|
+
"kiro"
|
|
26
|
+
],
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@clack/prompts": "^1.3.0",
|
|
30
|
+
"chalk": "^5.3.0",
|
|
31
|
+
"chokidar": "^3.6.0",
|
|
32
|
+
"commander": "^12.1.0",
|
|
33
|
+
"glob": "^11.0.0"
|
|
34
|
+
}
|
|
35
|
+
}
|
package/src/agents.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export const AGENTS = {
|
|
2
|
+
amp: {
|
|
3
|
+
key: 'amp',
|
|
4
|
+
name: 'Amp',
|
|
5
|
+
hint: 'ampcode.com',
|
|
6
|
+
generates: ['AGENTS.md'],
|
|
7
|
+
},
|
|
8
|
+
claude_code: {
|
|
9
|
+
key: 'claude_code',
|
|
10
|
+
name: 'Claude Code',
|
|
11
|
+
hint: 'by Anthropic',
|
|
12
|
+
generates: ['CLAUDE.md', '.mcp.json'],
|
|
13
|
+
},
|
|
14
|
+
codex: {
|
|
15
|
+
key: 'codex',
|
|
16
|
+
name: 'Codex',
|
|
17
|
+
hint: 'by OpenAI',
|
|
18
|
+
generates: ['AGENTS.md', '.mcp.json'],
|
|
19
|
+
},
|
|
20
|
+
copilot: {
|
|
21
|
+
key: 'copilot',
|
|
22
|
+
name: 'GitHub Copilot',
|
|
23
|
+
hint: 'by GitHub',
|
|
24
|
+
generates: ['AGENTS.md'],
|
|
25
|
+
},
|
|
26
|
+
cursor: {
|
|
27
|
+
key: 'cursor',
|
|
28
|
+
name: 'Cursor',
|
|
29
|
+
hint: 'cursor.sh',
|
|
30
|
+
generates: ['.cursor/rules/js-boost.mdc', '.cursorrules'],
|
|
31
|
+
},
|
|
32
|
+
gemini: {
|
|
33
|
+
key: 'gemini',
|
|
34
|
+
name: 'Gemini',
|
|
35
|
+
hint: 'by Google',
|
|
36
|
+
generates: ['AGENTS.md'],
|
|
37
|
+
},
|
|
38
|
+
junie: {
|
|
39
|
+
key: 'junie',
|
|
40
|
+
name: 'Junie',
|
|
41
|
+
hint: 'by JetBrains',
|
|
42
|
+
generates: ['.junie/guidelines.md', '.junie/mcp.json'],
|
|
43
|
+
},
|
|
44
|
+
kiro: {
|
|
45
|
+
key: 'kiro',
|
|
46
|
+
name: 'Kiro',
|
|
47
|
+
hint: 'by Amazon',
|
|
48
|
+
generates: ['.kiro/steering/guidelines.md'],
|
|
49
|
+
},
|
|
50
|
+
opencode: {
|
|
51
|
+
key: 'opencode',
|
|
52
|
+
name: 'OpenCode',
|
|
53
|
+
hint: 'opencode.ai',
|
|
54
|
+
generates: ['AGENTS.md'],
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/** Agents that consume the shared AGENTS.md format */
|
|
59
|
+
export const AGENTS_MD_CONSUMERS = ['amp', 'codex', 'copilot', 'gemini', 'opencode'];
|
|
60
|
+
|
|
61
|
+
/** Agents that consume .mcp.json */
|
|
62
|
+
export const MCP_JSON_CONSUMERS = ['claude_code', 'codex'];
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { program } from 'commander';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { generate } from './index.js';
|
|
5
|
+
import { init, selectAgents } from './init.js';
|
|
6
|
+
import { watch } from './watch.js';
|
|
7
|
+
|
|
8
|
+
program
|
|
9
|
+
.name('js-boost')
|
|
10
|
+
.description('Generate agent files (AGENTS.md, CLAUDE.md, .mcp.json, Junie, Cursor) from your .ai/ folder')
|
|
11
|
+
.version('1.0.0');
|
|
12
|
+
|
|
13
|
+
// ─── js-boost init ───────────────────────────────────────────────────────────
|
|
14
|
+
program
|
|
15
|
+
.command('init')
|
|
16
|
+
.description('Scaffold the .ai/ folder with example guidelines and skills')
|
|
17
|
+
.option('--force', 'Overwrite existing files')
|
|
18
|
+
.option('--dir <path>', 'Project directory', process.cwd())
|
|
19
|
+
.action(async (options) => {
|
|
20
|
+
const projectDir = path.resolve(options.dir);
|
|
21
|
+
await init(projectDir, { force: options.force });
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// ─── js-boost generate ───────────────────────────────────────────────────────
|
|
25
|
+
program
|
|
26
|
+
.command('generate')
|
|
27
|
+
.alias('gen')
|
|
28
|
+
.description('Generate all agent files from .ai/guidelines/ and .ai/skills/')
|
|
29
|
+
.option('--dir <path>', 'Project directory', process.cwd())
|
|
30
|
+
.option('--verbose', 'Show skipped files')
|
|
31
|
+
.action(async (options) => {
|
|
32
|
+
const projectDir = path.resolve(options.dir);
|
|
33
|
+
try {
|
|
34
|
+
await generate(projectDir, { verbose: options.verbose });
|
|
35
|
+
} catch (err) {
|
|
36
|
+
console.error(chalk.red('Error:'), err.message);
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// ─── js-boost watch ──────────────────────────────────────────────────────────
|
|
42
|
+
program
|
|
43
|
+
.command('watch')
|
|
44
|
+
.description('Watch .ai/ for changes and regenerate automatically')
|
|
45
|
+
.option('--dir <path>', 'Project directory', process.cwd())
|
|
46
|
+
.action((options) => {
|
|
47
|
+
const projectDir = path.resolve(options.dir);
|
|
48
|
+
watch(projectDir);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// ─── js-boost agents ─────────────────────────────────────────────────────────
|
|
52
|
+
program
|
|
53
|
+
.command('agents')
|
|
54
|
+
.description('Select which AI agents to configure (updates js-boost.config.json)')
|
|
55
|
+
.option('--dir <path>', 'Project directory', process.cwd())
|
|
56
|
+
.action(async (options) => {
|
|
57
|
+
const projectDir = path.resolve(options.dir);
|
|
58
|
+
const { readConfig, writeFile } = await import('./utils/reader.js');
|
|
59
|
+
const configPath = `${projectDir}/js-boost.config.json`;
|
|
60
|
+
const config = readConfig(projectDir);
|
|
61
|
+
|
|
62
|
+
const selected = await selectAgents(projectDir, config.agents ?? null);
|
|
63
|
+
config.agents = selected;
|
|
64
|
+
|
|
65
|
+
writeFile(configPath, JSON.stringify(config, null, 2));
|
|
66
|
+
console.log('');
|
|
67
|
+
console.log(chalk.green(' ✓ Agent configuration saved to js-boost.config.json'));
|
|
68
|
+
console.log('');
|
|
69
|
+
console.log(chalk.dim(` Selected: ${selected.join(', ')}`));
|
|
70
|
+
console.log('');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// ─── js-boost mcp ────────────────────────────────────────────────────────────
|
|
74
|
+
program
|
|
75
|
+
.command('mcp')
|
|
76
|
+
.description('Configure MCP servers — add, remove, or toggle built-in defaults')
|
|
77
|
+
.option('--dir <path>', 'Project directory', process.cwd())
|
|
78
|
+
.action(async (options) => {
|
|
79
|
+
const projectDir = path.resolve(options.dir);
|
|
80
|
+
const { configureMcp } = await import('./configure/mcp.js');
|
|
81
|
+
await configureMcp(projectDir);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// ─── js-boost status ─────────────────────────────────────────────────────────
|
|
85
|
+
program
|
|
86
|
+
.command('status')
|
|
87
|
+
.description('Show what .ai/ contains and what would be generated')
|
|
88
|
+
.option('--dir <path>', 'Project directory', process.cwd())
|
|
89
|
+
.action(async (options) => {
|
|
90
|
+
const projectDir = path.resolve(options.dir);
|
|
91
|
+
const { readGuidelines, readSkills, readConfig } = await import('./utils/reader.js');
|
|
92
|
+
const { buildMcpServers } = await import('./utils/mcp.js');
|
|
93
|
+
const { AGENTS, AGENTS_MD_CONSUMERS, MCP_JSON_CONSUMERS } = await import('./agents.js');
|
|
94
|
+
const aiDir = path.join(projectDir, '.ai');
|
|
95
|
+
const config = readConfig(projectDir);
|
|
96
|
+
|
|
97
|
+
const guidelines = await readGuidelines(aiDir);
|
|
98
|
+
const skills = await readSkills(aiDir);
|
|
99
|
+
const mcpServers = buildMcpServers(config);
|
|
100
|
+
|
|
101
|
+
const allAgentKeys = Object.keys(AGENTS);
|
|
102
|
+
const activeAgents = new Set(config.agents ?? allAgentKeys);
|
|
103
|
+
const has = (key) => activeAgents.has(key);
|
|
104
|
+
const hasAny = (keys) => keys.some(k => activeAgents.has(k));
|
|
105
|
+
|
|
106
|
+
console.log('');
|
|
107
|
+
console.log(chalk.bold.blue('⚡ js-boost') + chalk.dim(' — status'));
|
|
108
|
+
console.log('');
|
|
109
|
+
|
|
110
|
+
// Agents
|
|
111
|
+
console.log(chalk.bold(' Agents') + chalk.dim(` (${activeAgents.size} of ${allAgentKeys.length} selected)`));
|
|
112
|
+
for (const [key, agent] of Object.entries(AGENTS)) {
|
|
113
|
+
const active = activeAgents.has(key);
|
|
114
|
+
const icon = active ? chalk.green('✓') : chalk.dim('–');
|
|
115
|
+
const label = active ? chalk.cyan(agent.name) : chalk.dim(agent.name);
|
|
116
|
+
const hint = chalk.dim(`(${agent.hint})`);
|
|
117
|
+
console.log(` ${icon} ${label} ${hint}`);
|
|
118
|
+
}
|
|
119
|
+
if (config.agents == null) console.log(chalk.dim(' (all agents — run `js-boost agents` to configure)'));
|
|
120
|
+
console.log('');
|
|
121
|
+
|
|
122
|
+
console.log(chalk.bold(' Guidelines') + chalk.dim(` (${guidelines.length})`));
|
|
123
|
+
for (const g of guidelines) {
|
|
124
|
+
console.log(` ${chalk.green('•')} ${chalk.cyan(g.filename)} — ${chalk.dim(g.title)}`);
|
|
125
|
+
}
|
|
126
|
+
if (guidelines.length === 0) console.log(chalk.dim(' none — add .ai/guidelines/*.md'));
|
|
127
|
+
console.log('');
|
|
128
|
+
|
|
129
|
+
console.log(chalk.bold(' Skills') + chalk.dim(` (${skills.length})`));
|
|
130
|
+
for (const s of skills) {
|
|
131
|
+
console.log(` ${chalk.green('•')} ${chalk.cyan(s.name)} — ${chalk.dim(s.description || s.dir)}`);
|
|
132
|
+
}
|
|
133
|
+
if (skills.length === 0) console.log(chalk.dim(' none — add .ai/skills/<name>/SKILL.md'));
|
|
134
|
+
console.log('');
|
|
135
|
+
|
|
136
|
+
console.log(chalk.bold(' MCP Servers') + chalk.dim(` (${Object.keys(mcpServers).length})`));
|
|
137
|
+
for (const [key, srv] of Object.entries(mcpServers)) {
|
|
138
|
+
const url = srv.url || `${srv.command} ${(srv.args || []).join(' ')}`;
|
|
139
|
+
console.log(` ${chalk.green('•')} ${chalk.cyan(key)} — ${chalk.dim(url)}`);
|
|
140
|
+
}
|
|
141
|
+
console.log('');
|
|
142
|
+
|
|
143
|
+
console.log(chalk.bold(' Will generate:'));
|
|
144
|
+
const willGenerate = [
|
|
145
|
+
hasAny(AGENTS_MD_CONSUMERS) && ['AGENTS.md', 'Amp, Codex, Copilot, Gemini, OpenCode'],
|
|
146
|
+
has('claude_code') && ['CLAUDE.md', 'Claude Code'],
|
|
147
|
+
hasAny(MCP_JSON_CONSUMERS) && ['.mcp.json', 'Claude Code + Codex MCP'],
|
|
148
|
+
has('junie') && ['.junie/guidelines.md + .junie/mcp.json', 'JetBrains Junie'],
|
|
149
|
+
has('cursor') && ['.cursor/rules/js-boost.mdc + .cursorrules', 'Cursor'],
|
|
150
|
+
has('kiro') && ['.kiro/steering/guidelines.md', 'Kiro'],
|
|
151
|
+
].filter(Boolean);
|
|
152
|
+
|
|
153
|
+
for (const [file, label] of willGenerate) {
|
|
154
|
+
console.log(` ${chalk.green('•')} ${chalk.cyan(file.padEnd(42))} ${chalk.dim(label)}`);
|
|
155
|
+
}
|
|
156
|
+
if (willGenerate.length === 0) {
|
|
157
|
+
console.log(chalk.dim(' none — run `js-boost agents` to select agents'));
|
|
158
|
+
}
|
|
159
|
+
console.log('');
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
program.parse();
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { text, select, multiselect, isCancel } from '@clack/prompts';
|
|
4
|
+
import { DEFAULT_MCP_SERVERS } from '../utils/mcp.js';
|
|
5
|
+
import { readConfig, writeFile } from '../utils/reader.js';
|
|
6
|
+
|
|
7
|
+
function displayServers(config) {
|
|
8
|
+
const userServers = config.mcpServers || {};
|
|
9
|
+
const disabled = new Set(config.disableMcpServers || []);
|
|
10
|
+
const builtinKeys = Object.keys(DEFAULT_MCP_SERVERS);
|
|
11
|
+
|
|
12
|
+
if (builtinKeys.length > 0) {
|
|
13
|
+
console.log(chalk.bold(' Built-in'));
|
|
14
|
+
for (const key of builtinKeys) {
|
|
15
|
+
const srv = DEFAULT_MCP_SERVERS[key];
|
|
16
|
+
const status = disabled.has(key) ? chalk.dim('disabled') : chalk.green('enabled');
|
|
17
|
+
console.log(` ${chalk.cyan(key.padEnd(18))} ${chalk.dim(srv.url)} [${status}]`);
|
|
18
|
+
}
|
|
19
|
+
console.log('');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
console.log(chalk.bold(' Custom'));
|
|
23
|
+
const userEntries = Object.entries(userServers);
|
|
24
|
+
if (userEntries.length === 0) {
|
|
25
|
+
console.log(chalk.dim(' (none)'));
|
|
26
|
+
} else {
|
|
27
|
+
for (const [key, srv] of userEntries) {
|
|
28
|
+
const addr = srv.type === 'remote'
|
|
29
|
+
? srv.url
|
|
30
|
+
: `${srv.command}${srv.args?.length ? ' ' + srv.args.join(' ') : ''}`;
|
|
31
|
+
console.log(` ${chalk.cyan(key.padEnd(18))} ${chalk.dim(addr)} ${chalk.dim(`(${srv.type})`)}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
console.log('');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function addServer(config) {
|
|
38
|
+
const name = await text({
|
|
39
|
+
message: 'Server key',
|
|
40
|
+
placeholder: 'my-api',
|
|
41
|
+
validate: (v) => {
|
|
42
|
+
if (!v.trim()) return 'Key is required';
|
|
43
|
+
if ((config.mcpServers || {})[v.trim()]) return `"${v.trim()}" already exists`;
|
|
44
|
+
if (DEFAULT_MCP_SERVERS[v.trim()]) return `"${v.trim()}" is a built-in — use "Toggle built-ins" to enable/disable it`;
|
|
45
|
+
if (!/^[a-z0-9_-]+$/.test(v.trim())) return 'Use only lowercase letters, numbers, hyphens, underscores';
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
if (isCancel(name)) return;
|
|
49
|
+
|
|
50
|
+
const type = await select({
|
|
51
|
+
message: 'Server type',
|
|
52
|
+
options: [
|
|
53
|
+
{ value: 'remote', label: 'Remote', hint: 'HTTP / SSE url' },
|
|
54
|
+
{ value: 'stdio', label: 'Local', hint: 'stdio process (node, python, etc.)' },
|
|
55
|
+
],
|
|
56
|
+
});
|
|
57
|
+
if (isCancel(type)) return;
|
|
58
|
+
|
|
59
|
+
const key = name.trim();
|
|
60
|
+
|
|
61
|
+
if (type === 'remote') {
|
|
62
|
+
const url = await text({
|
|
63
|
+
message: 'URL',
|
|
64
|
+
placeholder: 'https://my-mcp.com/mcp',
|
|
65
|
+
validate: (v) => v.trim() ? undefined : 'URL is required',
|
|
66
|
+
});
|
|
67
|
+
if (isCancel(url)) return;
|
|
68
|
+
|
|
69
|
+
const description = await text({
|
|
70
|
+
message: 'Description (optional)',
|
|
71
|
+
placeholder: 'What does this server provide?',
|
|
72
|
+
});
|
|
73
|
+
if (isCancel(description)) return;
|
|
74
|
+
|
|
75
|
+
config.mcpServers[key] = {
|
|
76
|
+
type: 'remote',
|
|
77
|
+
url: url.trim(),
|
|
78
|
+
...(description.trim() ? { description: description.trim() } : {}),
|
|
79
|
+
};
|
|
80
|
+
} else {
|
|
81
|
+
const command = await text({
|
|
82
|
+
message: 'Command',
|
|
83
|
+
placeholder: 'node',
|
|
84
|
+
validate: (v) => v.trim() ? undefined : 'Command is required',
|
|
85
|
+
});
|
|
86
|
+
if (isCancel(command)) return;
|
|
87
|
+
|
|
88
|
+
const argsRaw = await text({
|
|
89
|
+
message: 'Arguments (space-separated, optional)',
|
|
90
|
+
placeholder: './mcp-server.js --port 3000',
|
|
91
|
+
});
|
|
92
|
+
if (isCancel(argsRaw)) return;
|
|
93
|
+
|
|
94
|
+
const envRaw = await text({
|
|
95
|
+
message: 'Environment variables (KEY=VALUE, comma-separated, optional)',
|
|
96
|
+
placeholder: 'API_KEY=secret,NODE_ENV=production',
|
|
97
|
+
});
|
|
98
|
+
if (isCancel(envRaw)) return;
|
|
99
|
+
|
|
100
|
+
const args = argsRaw.trim() ? argsRaw.trim().split(/\s+/) : [];
|
|
101
|
+
|
|
102
|
+
const env = {};
|
|
103
|
+
if (envRaw.trim()) {
|
|
104
|
+
for (const pair of envRaw.trim().split(',')) {
|
|
105
|
+
const eq = pair.indexOf('=');
|
|
106
|
+
if (eq > 0) {
|
|
107
|
+
const k = pair.slice(0, eq).trim();
|
|
108
|
+
const v = pair.slice(eq + 1).trim();
|
|
109
|
+
if (k) env[k] = v;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
config.mcpServers[key] = {
|
|
115
|
+
type: 'stdio',
|
|
116
|
+
command: command.trim(),
|
|
117
|
+
...(args.length ? { args } : {}),
|
|
118
|
+
...(Object.keys(env).length ? { env } : {}),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
console.log(` ${chalk.green('✓')} Added ${chalk.cyan(key)}`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function removeServer(config) {
|
|
126
|
+
const keys = Object.keys(config.mcpServers);
|
|
127
|
+
if (keys.length === 0) {
|
|
128
|
+
console.log(chalk.dim(' No custom servers to remove.'));
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const toRemove = await multiselect({
|
|
133
|
+
message: 'Select servers to remove',
|
|
134
|
+
options: keys.map((k) => {
|
|
135
|
+
const srv = config.mcpServers[k];
|
|
136
|
+
const addr = srv.type === 'remote'
|
|
137
|
+
? srv.url
|
|
138
|
+
: `${srv.command}${srv.args?.length ? ' ' + srv.args.join(' ') : ''}`;
|
|
139
|
+
return { value: k, label: k, hint: addr };
|
|
140
|
+
}),
|
|
141
|
+
required: false,
|
|
142
|
+
});
|
|
143
|
+
if (isCancel(toRemove)) return;
|
|
144
|
+
|
|
145
|
+
for (const k of toRemove) {
|
|
146
|
+
delete config.mcpServers[k];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (toRemove.length) {
|
|
150
|
+
console.log(` ${chalk.green('✓')} Removed: ${toRemove.map(k => chalk.cyan(k)).join(', ')}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async function toggleDefaults(config) {
|
|
155
|
+
const keys = Object.keys(DEFAULT_MCP_SERVERS);
|
|
156
|
+
if (keys.length === 0) {
|
|
157
|
+
console.log(chalk.dim(' No built-in servers configured.'));
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const disabled = new Set(config.disableMcpServers || []);
|
|
162
|
+
|
|
163
|
+
const enabled = await multiselect({
|
|
164
|
+
message: 'Which built-in servers should be enabled?',
|
|
165
|
+
options: keys.map((k) => ({
|
|
166
|
+
value: k,
|
|
167
|
+
label: k,
|
|
168
|
+
hint: DEFAULT_MCP_SERVERS[k].url,
|
|
169
|
+
})),
|
|
170
|
+
initialValues: keys.filter((k) => !disabled.has(k)),
|
|
171
|
+
required: false,
|
|
172
|
+
});
|
|
173
|
+
if (isCancel(enabled)) return;
|
|
174
|
+
|
|
175
|
+
const enabledSet = new Set(enabled);
|
|
176
|
+
config.disableMcpServers = keys.filter((k) => !enabledSet.has(k));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export async function configureMcp(projectDir) {
|
|
180
|
+
const configPath = path.join(projectDir, 'js-boost.config.json');
|
|
181
|
+
const config = readConfig(projectDir);
|
|
182
|
+
config.mcpServers = config.mcpServers || {};
|
|
183
|
+
config.disableMcpServers = config.disableMcpServers || [];
|
|
184
|
+
|
|
185
|
+
console.log('');
|
|
186
|
+
console.log(chalk.bold.blue('⚡ js-boost') + chalk.dim(' — MCP server configuration'));
|
|
187
|
+
console.log('');
|
|
188
|
+
|
|
189
|
+
let running = true;
|
|
190
|
+
|
|
191
|
+
while (running) {
|
|
192
|
+
displayServers(config);
|
|
193
|
+
|
|
194
|
+
const hasCustom = Object.keys(config.mcpServers).length > 0;
|
|
195
|
+
const hasBuiltins = Object.keys(DEFAULT_MCP_SERVERS).length > 0;
|
|
196
|
+
|
|
197
|
+
const options = [
|
|
198
|
+
{ value: 'add', label: 'Add a server' },
|
|
199
|
+
...(hasCustom ? [{ value: 'remove', label: 'Remove a server' }] : []),
|
|
200
|
+
...(hasBuiltins ? [{ value: 'toggle', label: 'Disable / enable built-in servers' }] : []),
|
|
201
|
+
{ value: 'done', label: 'Save and exit' },
|
|
202
|
+
];
|
|
203
|
+
|
|
204
|
+
const action = await select({ message: 'What would you like to do?', options });
|
|
205
|
+
|
|
206
|
+
if (isCancel(action) || action === 'done') {
|
|
207
|
+
running = false;
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
console.log('');
|
|
212
|
+
if (action === 'add') await addServer(config);
|
|
213
|
+
if (action === 'remove') await removeServer(config);
|
|
214
|
+
if (action === 'toggle') await toggleDefaults(config);
|
|
215
|
+
console.log('');
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
writeFile(configPath, JSON.stringify(config, null, 2));
|
|
219
|
+
console.log('');
|
|
220
|
+
console.log(chalk.green(' ✓ Saved to js-boost.config.json'));
|
|
221
|
+
console.log(chalk.dim(' Run `js-boost generate` to apply changes to agent files.'));
|
|
222
|
+
console.log('');
|
|
223
|
+
}
|