@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 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
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import '../src/cli.js';
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
+ }