@1tool/js-boost 1.2.0 → 1.3.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 +70 -36
- package/package.json +1 -1
- package/src/cli.js +32 -30
- package/src/configure/mcp.js +78 -43
- package/src/generators/agents.js +2 -24
- package/src/generators/claude.js +2 -5
- package/src/generators/cursor.js +8 -21
- package/src/generators/junie.js +2 -17
- package/src/generators/kiro.js +7 -11
- package/src/index.js +47 -26
- package/src/init.js +28 -17
- package/src/utils/mcp.js +28 -33
- package/src/utils/reader.js +16 -13
- package/src/watch.js +1 -1
package/README.md
CHANGED
|
@@ -72,37 +72,43 @@ npx @1tool/js-boost agents
|
|
|
72
72
|
|
|
73
73
|
### `@1tool/js-boost mcp`
|
|
74
74
|
|
|
75
|
-
Interactive wizard
|
|
75
|
+
Interactive wizard for managing MCP servers. Team server definitions are stored in `.ai/mcp/mcp.json` (committed). Per-developer enable/disable state is stored in `.js-boost.json` (gitignored). Run `generate` afterwards to apply changes to agent files.
|
|
76
76
|
|
|
77
77
|
```bash
|
|
78
78
|
npx @1tool/js-boost mcp
|
|
79
79
|
```
|
|
80
80
|
|
|
81
|
-
|
|
81
|
+
The wizard offers three actions:
|
|
82
|
+
|
|
83
|
+
**Add a remote server** — HTTP/SSE endpoint with optional auth headers:
|
|
82
84
|
```
|
|
83
|
-
✔ Server key
|
|
84
|
-
✔ Server type
|
|
85
|
-
✔ URL
|
|
86
|
-
✔
|
|
85
|
+
✔ Server key my-api
|
|
86
|
+
✔ Server type Remote (HTTP / SSE url)
|
|
87
|
+
✔ URL https://my-mcp.com/mcp
|
|
88
|
+
✔ Headers Authorization: Bearer YOUR_TOKEN_HERE
|
|
89
|
+
✔ Description Internal API tools
|
|
87
90
|
```
|
|
88
91
|
|
|
89
|
-
**Add a local
|
|
92
|
+
**Add a local server** — stdio process with optional args and env vars:
|
|
90
93
|
```
|
|
91
|
-
✔ Server key
|
|
92
|
-
✔ Server type
|
|
93
|
-
✔ Command
|
|
94
|
-
✔ Arguments
|
|
95
|
-
✔ Environment variables
|
|
94
|
+
✔ Server key local-tools
|
|
95
|
+
✔ Server type Local (stdio process)
|
|
96
|
+
✔ Command node
|
|
97
|
+
✔ Arguments ./mcp-server.js --port 3000
|
|
98
|
+
✔ Environment variables API_KEY=secret,NODE_ENV=production
|
|
96
99
|
```
|
|
97
100
|
|
|
101
|
+
**Enable / disable servers locally** — multiselect over all configured servers. Unchecked servers are added to `disabledMcpServers` in `.js-boost.json` and excluded from your generated files without affecting teammates.
|
|
102
|
+
|
|
98
103
|
### `@1tool/js-boost generate`
|
|
99
104
|
|
|
100
|
-
Reads `.ai/guidelines/*.md` and `.ai/skills/*/SKILL.md`, then generates files for all selected agents.
|
|
105
|
+
Reads `.ai/guidelines/*.md` and `.ai/skills/*/SKILL.md`, then generates files for all selected agents. On first run (no `.js-boost.json`), prompts for agent selection inline.
|
|
101
106
|
|
|
102
107
|
```bash
|
|
103
108
|
npx @1tool/js-boost generate
|
|
104
|
-
npx @1tool/js-boost gen
|
|
105
|
-
npx @1tool/js-boost generate --verbose
|
|
109
|
+
npx @1tool/js-boost gen # alias
|
|
110
|
+
npx @1tool/js-boost generate --verbose # show skipped files
|
|
111
|
+
npx @1tool/js-boost generate --agents claude_code,cursor # CI one-off, not saved
|
|
106
112
|
```
|
|
107
113
|
|
|
108
114
|
### `@1tool/js-boost watch`
|
|
@@ -143,43 +149,71 @@ During `init` (and `agents`), installed agents are pre-selected automatically ba
|
|
|
143
149
|
|
|
144
150
|
## Configuration
|
|
145
151
|
|
|
146
|
-
|
|
152
|
+
### `.ai/mcp/mcp.json` — team MCP servers
|
|
153
|
+
|
|
154
|
+
Committed to the repo. Defines the MCP servers available to the whole team. Managed by `js-boost mcp`.
|
|
147
155
|
|
|
148
156
|
```json
|
|
149
157
|
{
|
|
150
|
-
"
|
|
151
|
-
"my-
|
|
152
|
-
"type": "
|
|
158
|
+
"mcpServers": {
|
|
159
|
+
"my-remote": {
|
|
160
|
+
"type": "http",
|
|
153
161
|
"url": "https://my-mcp.com/mcp",
|
|
154
|
-
"
|
|
162
|
+
"headers": {
|
|
163
|
+
"Authorization": "Bearer YOUR_TOKEN_HERE"
|
|
164
|
+
}
|
|
155
165
|
},
|
|
156
|
-
"local
|
|
157
|
-
"type": "stdio",
|
|
166
|
+
"my-local": {
|
|
158
167
|
"command": "node",
|
|
159
|
-
"args": ["./mcp-server.js"],
|
|
160
|
-
"env": {
|
|
168
|
+
"args": ["./mcp-server.js", "--port", "3000"],
|
|
169
|
+
"env": {
|
|
170
|
+
"API_KEY": "secret"
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
"npm-package": {
|
|
174
|
+
"command": "npx",
|
|
175
|
+
"args": ["-y", "@some/mcp-server"]
|
|
161
176
|
}
|
|
162
|
-
}
|
|
163
|
-
"disabled": []
|
|
177
|
+
}
|
|
164
178
|
}
|
|
165
179
|
```
|
|
166
180
|
|
|
167
|
-
|
|
181
|
+
Server types:
|
|
182
|
+
|
|
183
|
+
| Field | When to use |
|
|
184
|
+
|---|---|
|
|
185
|
+
| `type: "http"` + `url` | Remote HTTP/SSE server |
|
|
186
|
+
| `command` (no type) | Local stdio process |
|
|
187
|
+
| `headers` | Auth headers for remote servers (e.g. `Authorization`) |
|
|
188
|
+
| `args` | Command-line arguments |
|
|
189
|
+
| `env` | Environment variables injected into the process |
|
|
190
|
+
|
|
191
|
+
Remote servers are written differently per agent:
|
|
192
|
+
|
|
193
|
+
| Agent file | Remote format |
|
|
194
|
+
|---|---|
|
|
195
|
+
| `.mcp.json` (Claude Code, Codex) | Wrapped in `mcp-remote` with `--header` args |
|
|
196
|
+
| `.junie/mcp.json` | URL referenced directly |
|
|
197
|
+
|
|
198
|
+
### `.js-boost.json` — per-developer config
|
|
199
|
+
|
|
200
|
+
Gitignored. Created by `js-boost init`, updated by `js-boost agents` and `js-boost mcp`.
|
|
168
201
|
|
|
169
202
|
```json
|
|
170
203
|
{
|
|
171
|
-
"
|
|
172
|
-
"
|
|
173
|
-
"
|
|
204
|
+
"agents": ["claude_code", "cursor", "codex"],
|
|
205
|
+
"guidelines": true,
|
|
206
|
+
"skills": ["example-skill"],
|
|
207
|
+
"disabledMcpServers": ["my-remote"]
|
|
174
208
|
}
|
|
175
209
|
```
|
|
176
210
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
|
180
|
-
|
|
181
|
-
|
|
|
182
|
-
|
|
|
211
|
+
| Field | Description |
|
|
212
|
+
|---|---|
|
|
213
|
+
| `agents` | Which agents to generate files for |
|
|
214
|
+
| `guidelines` | Set to `true` after first successful generate |
|
|
215
|
+
| `skills` | Snapshot of skill names at last generate |
|
|
216
|
+
| `disabledMcpServers` | Server keys to exclude from your generated files |
|
|
183
217
|
|
|
184
218
|
---
|
|
185
219
|
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -7,13 +7,13 @@ import { watch } from './watch.js';
|
|
|
7
7
|
|
|
8
8
|
program
|
|
9
9
|
.name('js-boost')
|
|
10
|
-
.description('Generate agent files
|
|
11
|
-
.version('1.
|
|
10
|
+
.description('Generate agent files from your .ai/ folder')
|
|
11
|
+
.version('1.2.0');
|
|
12
12
|
|
|
13
13
|
// ─── js-boost init ───────────────────────────────────────────────────────────
|
|
14
14
|
program
|
|
15
15
|
.command('init')
|
|
16
|
-
.description('Scaffold
|
|
16
|
+
.description('Scaffold .ai/ folder, select agents, create .js-boost.json')
|
|
17
17
|
.option('--force', 'Overwrite existing files')
|
|
18
18
|
.option('--dir <path>', 'Project directory', process.cwd())
|
|
19
19
|
.action(async (options) => {
|
|
@@ -25,13 +25,14 @@ program
|
|
|
25
25
|
program
|
|
26
26
|
.command('generate')
|
|
27
27
|
.alias('gen')
|
|
28
|
-
.description('Generate
|
|
28
|
+
.description('Generate agent files from .ai/guidelines/ and .ai/skills/')
|
|
29
29
|
.option('--dir <path>', 'Project directory', process.cwd())
|
|
30
|
+
.option('--agents <list>', 'Comma-separated agent keys — one-off override, not saved (e.g. claude_code,cursor)')
|
|
30
31
|
.option('--verbose', 'Show skipped files')
|
|
31
32
|
.action(async (options) => {
|
|
32
33
|
const projectDir = path.resolve(options.dir);
|
|
33
34
|
try {
|
|
34
|
-
await generate(projectDir, { verbose: options.verbose });
|
|
35
|
+
await generate(projectDir, { verbose: options.verbose, agents: options.agents });
|
|
35
36
|
} catch (err) {
|
|
36
37
|
console.error(chalk.red('Error:'), err.message);
|
|
37
38
|
process.exit(1);
|
|
@@ -51,29 +52,26 @@ program
|
|
|
51
52
|
// ─── js-boost agents ─────────────────────────────────────────────────────────
|
|
52
53
|
program
|
|
53
54
|
.command('agents')
|
|
54
|
-
.description('
|
|
55
|
+
.description('Re-select AI agents and update .js-boost.json')
|
|
55
56
|
.option('--dir <path>', 'Project directory', process.cwd())
|
|
56
57
|
.action(async (options) => {
|
|
57
58
|
const projectDir = path.resolve(options.dir);
|
|
58
|
-
const {
|
|
59
|
-
const
|
|
60
|
-
const config = readConfig(projectDir);
|
|
59
|
+
const { readLocalConfig, writeLocalConfig } = await import('./utils/reader.js');
|
|
60
|
+
const localConfig = readLocalConfig(projectDir);
|
|
61
61
|
|
|
62
|
-
const selected = await selectAgents(projectDir,
|
|
63
|
-
|
|
62
|
+
const selected = await selectAgents(projectDir, localConfig.agents ?? null);
|
|
63
|
+
writeLocalConfig(projectDir, { ...localConfig, agents: selected });
|
|
64
64
|
|
|
65
|
-
writeFile(configPath, JSON.stringify(config, null, 2));
|
|
66
65
|
console.log('');
|
|
67
|
-
console.log(chalk.green(' ✓
|
|
68
|
-
console.log('');
|
|
69
|
-
console.log(chalk.dim(` Selected: ${selected.join(', ')}`));
|
|
66
|
+
console.log(chalk.green(' ✓ Saved to .js-boost.json'));
|
|
67
|
+
console.log(chalk.dim(` Agents: ${selected.join(', ')}`));
|
|
70
68
|
console.log('');
|
|
71
69
|
});
|
|
72
70
|
|
|
73
71
|
// ─── js-boost mcp ────────────────────────────────────────────────────────────
|
|
74
72
|
program
|
|
75
73
|
.command('mcp')
|
|
76
|
-
.description('Configure MCP servers — add
|
|
74
|
+
.description('Configure MCP servers — add/remove team servers or toggle built-in defaults')
|
|
77
75
|
.option('--dir <path>', 'Project directory', process.cwd())
|
|
78
76
|
.action(async (options) => {
|
|
79
77
|
const projectDir = path.resolve(options.dir);
|
|
@@ -84,22 +82,22 @@ program
|
|
|
84
82
|
// ─── js-boost status ─────────────────────────────────────────────────────────
|
|
85
83
|
program
|
|
86
84
|
.command('status')
|
|
87
|
-
.description('Show
|
|
85
|
+
.description('Show configured agents, guidelines, skills, MCP servers, and output files')
|
|
88
86
|
.option('--dir <path>', 'Project directory', process.cwd())
|
|
89
87
|
.action(async (options) => {
|
|
90
88
|
const projectDir = path.resolve(options.dir);
|
|
91
|
-
const { readGuidelines, readSkills,
|
|
89
|
+
const { readGuidelines, readSkills, readLocalConfig, readMcpConfig } = await import('./utils/reader.js');
|
|
92
90
|
const { buildMcpServers } = await import('./utils/mcp.js');
|
|
93
91
|
const { AGENTS, AGENTS_MD_CONSUMERS, MCP_JSON_CONSUMERS } = await import('./agents.js');
|
|
94
92
|
const aiDir = path.join(projectDir, '.ai');
|
|
95
|
-
const config = readConfig(projectDir);
|
|
96
93
|
|
|
94
|
+
const localConfig = readLocalConfig(projectDir);
|
|
97
95
|
const guidelines = await readGuidelines(aiDir);
|
|
98
96
|
const skills = await readSkills(aiDir);
|
|
99
|
-
const mcpServers = buildMcpServers(readMcpConfig(aiDir));
|
|
97
|
+
const mcpServers = buildMcpServers(readMcpConfig(aiDir), localConfig);
|
|
100
98
|
|
|
101
99
|
const allAgentKeys = Object.keys(AGENTS);
|
|
102
|
-
const activeAgents = new Set(
|
|
100
|
+
const activeAgents = new Set(localConfig.agents ?? allAgentKeys);
|
|
103
101
|
const has = (key) => activeAgents.has(key);
|
|
104
102
|
const hasAny = (keys) => keys.some(k => activeAgents.has(k));
|
|
105
103
|
|
|
@@ -113,12 +111,12 @@ program
|
|
|
113
111
|
const active = activeAgents.has(key);
|
|
114
112
|
const icon = active ? chalk.green('✓') : chalk.dim('–');
|
|
115
113
|
const label = active ? chalk.cyan(agent.name) : chalk.dim(agent.name);
|
|
116
|
-
|
|
117
|
-
console.log(` ${icon} ${label} ${hint}`);
|
|
114
|
+
console.log(` ${icon} ${label} ${chalk.dim(`(${agent.hint})`)}`);
|
|
118
115
|
}
|
|
119
|
-
if (
|
|
116
|
+
if (!localConfig.agents) console.log(chalk.dim(' (none configured — run `js-boost init` or `js-boost agents`)'));
|
|
120
117
|
console.log('');
|
|
121
118
|
|
|
119
|
+
// Guidelines
|
|
122
120
|
console.log(chalk.bold(' Guidelines') + chalk.dim(` (${guidelines.length})`));
|
|
123
121
|
for (const g of guidelines) {
|
|
124
122
|
console.log(` ${chalk.green('•')} ${chalk.cyan(g.filename)} — ${chalk.dim(g.title)}`);
|
|
@@ -126,6 +124,7 @@ program
|
|
|
126
124
|
if (guidelines.length === 0) console.log(chalk.dim(' none — add .ai/guidelines/*.md'));
|
|
127
125
|
console.log('');
|
|
128
126
|
|
|
127
|
+
// Skills
|
|
129
128
|
console.log(chalk.bold(' Skills') + chalk.dim(` (${skills.length})`));
|
|
130
129
|
for (const s of skills) {
|
|
131
130
|
console.log(` ${chalk.green('•')} ${chalk.cyan(s.name)} — ${chalk.dim(s.description || s.dir)}`);
|
|
@@ -133,21 +132,24 @@ program
|
|
|
133
132
|
if (skills.length === 0) console.log(chalk.dim(' none — add .ai/skills/<name>/SKILL.md'));
|
|
134
133
|
console.log('');
|
|
135
134
|
|
|
135
|
+
// MCP servers
|
|
136
136
|
console.log(chalk.bold(' MCP Servers') + chalk.dim(` (${Object.keys(mcpServers).length})`));
|
|
137
137
|
for (const [key, srv] of Object.entries(mcpServers)) {
|
|
138
|
-
const
|
|
139
|
-
console.log(` ${chalk.green('•')} ${chalk.cyan(key)} — ${chalk.dim(
|
|
138
|
+
const addr = srv.type === 'http' ? srv.url : `${srv.command} ${(srv.args || []).join(' ')}`;
|
|
139
|
+
console.log(` ${chalk.green('•')} ${chalk.cyan(key)} — ${chalk.dim(addr)}`);
|
|
140
140
|
}
|
|
141
|
+
if (Object.keys(mcpServers).length === 0) console.log(chalk.dim(' none — run `js-boost mcp` to configure'));
|
|
141
142
|
console.log('');
|
|
142
143
|
|
|
144
|
+
// Will generate
|
|
143
145
|
console.log(chalk.bold(' Will generate:'));
|
|
144
146
|
const willGenerate = [
|
|
145
|
-
hasAny(AGENTS_MD_CONSUMERS) && ['AGENTS.md',
|
|
146
|
-
has('claude_code') && ['CLAUDE.md',
|
|
147
|
-
hasAny(MCP_JSON_CONSUMERS) && ['.mcp.json',
|
|
147
|
+
hasAny(AGENTS_MD_CONSUMERS) && ['AGENTS.md', 'Amp, Codex, Copilot, Gemini, OpenCode'],
|
|
148
|
+
has('claude_code') && ['CLAUDE.md', 'Claude Code'],
|
|
149
|
+
hasAny(MCP_JSON_CONSUMERS) && ['.mcp.json', 'Claude Code + Codex'],
|
|
148
150
|
has('junie') && ['.junie/guidelines.md + .junie/mcp.json', 'JetBrains Junie'],
|
|
149
151
|
has('cursor') && ['.cursor/rules/js-boost.mdc + .cursorrules', 'Cursor'],
|
|
150
|
-
has('kiro') && ['.kiro/steering/guidelines.md',
|
|
152
|
+
has('kiro') && ['.kiro/steering/guidelines.md', 'Kiro'],
|
|
151
153
|
].filter(Boolean);
|
|
152
154
|
|
|
153
155
|
for (const [file, label] of willGenerate) {
|
package/src/configure/mcp.js
CHANGED
|
@@ -2,11 +2,11 @@ import path from 'path';
|
|
|
2
2
|
import chalk from 'chalk';
|
|
3
3
|
import { text, select, multiselect, isCancel } from '@clack/prompts';
|
|
4
4
|
import { DEFAULT_MCP_SERVERS } from '../utils/mcp.js';
|
|
5
|
-
import { readMcpConfig, writeMcpConfig } from '../utils/reader.js';
|
|
5
|
+
import { readMcpConfig, writeMcpConfig, readLocalConfig, writeLocalConfig } from '../utils/reader.js';
|
|
6
6
|
|
|
7
|
-
function displayServers(mcpConfig) {
|
|
8
|
-
const userServers = mcpConfig.
|
|
9
|
-
const disabled = new Set(
|
|
7
|
+
function displayServers(mcpConfig, localConfig) {
|
|
8
|
+
const userServers = mcpConfig.mcpServers || {};
|
|
9
|
+
const disabled = new Set(localConfig.disabledMcpServers || []);
|
|
10
10
|
const builtinKeys = Object.keys(DEFAULT_MCP_SERVERS);
|
|
11
11
|
|
|
12
12
|
if (builtinKeys.length > 0) {
|
|
@@ -19,16 +19,17 @@ function displayServers(mcpConfig) {
|
|
|
19
19
|
console.log('');
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
console.log(chalk.bold(' Custom'));
|
|
22
|
+
console.log(chalk.bold(' Custom') + chalk.dim(' → .ai/mcp/mcp.json'));
|
|
23
23
|
const userEntries = Object.entries(userServers);
|
|
24
24
|
if (userEntries.length === 0) {
|
|
25
25
|
console.log(chalk.dim(' (none)'));
|
|
26
26
|
} else {
|
|
27
27
|
for (const [key, srv] of userEntries) {
|
|
28
|
-
const addr = srv.type === '
|
|
28
|
+
const addr = srv.type === 'http'
|
|
29
29
|
? srv.url
|
|
30
30
|
: `${srv.command}${srv.args?.length ? ' ' + srv.args.join(' ') : ''}`;
|
|
31
|
-
|
|
31
|
+
const type = srv.type === 'http' ? 'remote' : 'stdio';
|
|
32
|
+
console.log(` ${chalk.cyan(key.padEnd(18))} ${chalk.dim(addr)} ${chalk.dim(`(${type})`)}`);
|
|
32
33
|
}
|
|
33
34
|
}
|
|
34
35
|
console.log('');
|
|
@@ -40,7 +41,7 @@ async function addServer(mcpConfig) {
|
|
|
40
41
|
placeholder: 'my-api',
|
|
41
42
|
validate: (v) => {
|
|
42
43
|
if (!v.trim()) return 'Key is required';
|
|
43
|
-
if ((mcpConfig.
|
|
44
|
+
if ((mcpConfig.mcpServers || {})[v.trim()]) return `"${v.trim()}" already exists`;
|
|
44
45
|
if (DEFAULT_MCP_SERVERS[v.trim()]) return `"${v.trim()}" is a built-in — use "Toggle built-ins" to enable/disable it`;
|
|
45
46
|
if (!/^[a-z0-9_-]+$/.test(v.trim())) return 'Use only lowercase letters, numbers, hyphens, underscores';
|
|
46
47
|
},
|
|
@@ -50,15 +51,15 @@ async function addServer(mcpConfig) {
|
|
|
50
51
|
const type = await select({
|
|
51
52
|
message: 'Server type',
|
|
52
53
|
options: [
|
|
53
|
-
{ value: '
|
|
54
|
-
{ value: 'stdio',
|
|
54
|
+
{ value: 'http', label: 'Remote', hint: 'HTTP / SSE url' },
|
|
55
|
+
{ value: 'stdio', label: 'Local', hint: 'stdio process (node, python, etc.)' },
|
|
55
56
|
],
|
|
56
57
|
});
|
|
57
58
|
if (isCancel(type)) return;
|
|
58
59
|
|
|
59
60
|
const key = name.trim();
|
|
60
61
|
|
|
61
|
-
if (type === '
|
|
62
|
+
if (type === 'http') {
|
|
62
63
|
const url = await text({
|
|
63
64
|
message: 'URL',
|
|
64
65
|
placeholder: 'https://my-mcp.com/mcp',
|
|
@@ -66,15 +67,34 @@ async function addServer(mcpConfig) {
|
|
|
66
67
|
});
|
|
67
68
|
if (isCancel(url)) return;
|
|
68
69
|
|
|
70
|
+
const headersRaw = await text({
|
|
71
|
+
message: 'Headers (Key: Value, comma-separated, optional)',
|
|
72
|
+
placeholder: 'Authorization: Bearer TOKEN',
|
|
73
|
+
});
|
|
74
|
+
if (isCancel(headersRaw)) return;
|
|
75
|
+
|
|
69
76
|
const description = await text({
|
|
70
77
|
message: 'Description (optional)',
|
|
71
78
|
placeholder: 'What does this server provide?',
|
|
72
79
|
});
|
|
73
80
|
if (isCancel(description)) return;
|
|
74
81
|
|
|
75
|
-
|
|
76
|
-
|
|
82
|
+
const headers = {};
|
|
83
|
+
if (headersRaw.trim()) {
|
|
84
|
+
for (const pair of headersRaw.trim().split(',')) {
|
|
85
|
+
const colon = pair.indexOf(':');
|
|
86
|
+
if (colon > 0) {
|
|
87
|
+
const k = pair.slice(0, colon).trim();
|
|
88
|
+
const v = pair.slice(colon + 1).trim();
|
|
89
|
+
if (k) headers[k] = v;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
mcpConfig.mcpServers[key] = {
|
|
95
|
+
type: 'http',
|
|
77
96
|
url: url.trim(),
|
|
97
|
+
...(Object.keys(headers).length ? { headers } : {}),
|
|
78
98
|
...(description.trim() ? { description: description.trim() } : {}),
|
|
79
99
|
};
|
|
80
100
|
} else {
|
|
@@ -111,19 +131,18 @@ async function addServer(mcpConfig) {
|
|
|
111
131
|
}
|
|
112
132
|
}
|
|
113
133
|
|
|
114
|
-
mcpConfig.
|
|
115
|
-
type: 'stdio',
|
|
134
|
+
mcpConfig.mcpServers[key] = {
|
|
116
135
|
command: command.trim(),
|
|
117
136
|
...(args.length ? { args } : {}),
|
|
118
137
|
...(Object.keys(env).length ? { env } : {}),
|
|
119
138
|
};
|
|
120
139
|
}
|
|
121
140
|
|
|
122
|
-
console.log(` ${chalk.green('✓')} Added ${chalk.cyan(key)}`);
|
|
141
|
+
console.log(` ${chalk.green('✓')} Added ${chalk.cyan(key)} → ${chalk.dim('.ai/mcp/mcp.json')}`);
|
|
123
142
|
}
|
|
124
143
|
|
|
125
144
|
async function removeServer(mcpConfig) {
|
|
126
|
-
const keys = Object.keys(mcpConfig.
|
|
145
|
+
const keys = Object.keys(mcpConfig.mcpServers);
|
|
127
146
|
if (keys.length === 0) {
|
|
128
147
|
console.log(chalk.dim(' No custom servers to remove.'));
|
|
129
148
|
return;
|
|
@@ -132,8 +151,8 @@ async function removeServer(mcpConfig) {
|
|
|
132
151
|
const toRemove = await multiselect({
|
|
133
152
|
message: 'Select servers to remove',
|
|
134
153
|
options: keys.map((k) => {
|
|
135
|
-
const srv = mcpConfig.
|
|
136
|
-
const addr = srv.type === '
|
|
154
|
+
const srv = mcpConfig.mcpServers[k];
|
|
155
|
+
const addr = srv.type === 'http'
|
|
137
156
|
? srv.url
|
|
138
157
|
: `${srv.command}${srv.args?.length ? ' ' + srv.args.join(' ') : ''}`;
|
|
139
158
|
return { value: k, label: k, hint: addr };
|
|
@@ -142,43 +161,56 @@ async function removeServer(mcpConfig) {
|
|
|
142
161
|
});
|
|
143
162
|
if (isCancel(toRemove)) return;
|
|
144
163
|
|
|
145
|
-
for (const k of toRemove)
|
|
146
|
-
delete mcpConfig.servers[k];
|
|
147
|
-
}
|
|
164
|
+
for (const k of toRemove) delete mcpConfig.mcpServers[k];
|
|
148
165
|
|
|
149
166
|
if (toRemove.length) {
|
|
150
167
|
console.log(` ${chalk.green('✓')} Removed: ${toRemove.map(k => chalk.cyan(k)).join(', ')}`);
|
|
151
168
|
}
|
|
152
169
|
}
|
|
153
170
|
|
|
154
|
-
async function
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
171
|
+
async function toggleServers(mcpConfig, localConfig) {
|
|
172
|
+
const builtinKeys = Object.keys(DEFAULT_MCP_SERVERS);
|
|
173
|
+
const customKeys = Object.keys(mcpConfig.mcpServers || {});
|
|
174
|
+
const allKeys = [...builtinKeys, ...customKeys];
|
|
175
|
+
|
|
176
|
+
if (allKeys.length === 0) {
|
|
177
|
+
console.log(chalk.dim(' No servers to toggle.'));
|
|
158
178
|
return;
|
|
159
179
|
}
|
|
160
180
|
|
|
161
|
-
const disabled = new Set(
|
|
181
|
+
const disabled = new Set(localConfig.disabledMcpServers || []);
|
|
162
182
|
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
options: keys.map((k) => ({
|
|
183
|
+
const options = [
|
|
184
|
+
...builtinKeys.map((k) => ({
|
|
166
185
|
value: k,
|
|
167
186
|
label: k,
|
|
168
|
-
hint: DEFAULT_MCP_SERVERS[k].url
|
|
187
|
+
hint: `${DEFAULT_MCP_SERVERS[k].url} (built-in)`,
|
|
169
188
|
})),
|
|
170
|
-
|
|
189
|
+
...customKeys.map((k) => {
|
|
190
|
+
const srv = mcpConfig.mcpServers[k];
|
|
191
|
+
const addr = srv.type === 'http' ? srv.url : `${srv.command}${srv.args?.length ? ' ' + srv.args.join(' ') : ''}`;
|
|
192
|
+
return { value: k, label: k, hint: addr };
|
|
193
|
+
}),
|
|
194
|
+
];
|
|
195
|
+
|
|
196
|
+
const enabled = await multiselect({
|
|
197
|
+
message: 'Which servers should be enabled locally?',
|
|
198
|
+
options,
|
|
199
|
+
initialValues: allKeys.filter((k) => !disabled.has(k)),
|
|
171
200
|
required: false,
|
|
172
201
|
});
|
|
173
202
|
if (isCancel(enabled)) return;
|
|
174
203
|
|
|
175
204
|
const enabledSet = new Set(enabled);
|
|
176
|
-
|
|
205
|
+
localConfig.disabledMcpServers = allKeys.filter((k) => !enabledSet.has(k));
|
|
206
|
+
console.log(` ${chalk.green('✓')} Updated → ${chalk.dim('.js-boost.json')}`);
|
|
177
207
|
}
|
|
178
208
|
|
|
179
209
|
export async function configureMcp(projectDir) {
|
|
180
210
|
const aiDir = path.join(projectDir, '.ai');
|
|
181
211
|
const mcpConfig = readMcpConfig(aiDir);
|
|
212
|
+
const localConfig = readLocalConfig(projectDir);
|
|
213
|
+
localConfig.disabledMcpServers = localConfig.disabledMcpServers || [];
|
|
182
214
|
|
|
183
215
|
console.log('');
|
|
184
216
|
console.log(chalk.bold.blue('⚡ js-boost') + chalk.dim(' — MCP server configuration'));
|
|
@@ -187,16 +219,16 @@ export async function configureMcp(projectDir) {
|
|
|
187
219
|
let running = true;
|
|
188
220
|
|
|
189
221
|
while (running) {
|
|
190
|
-
displayServers(mcpConfig);
|
|
222
|
+
displayServers(mcpConfig, localConfig);
|
|
191
223
|
|
|
192
|
-
const hasCustom = Object.keys(mcpConfig.
|
|
193
|
-
const
|
|
224
|
+
const hasCustom = Object.keys(mcpConfig.mcpServers).length > 0;
|
|
225
|
+
const hasAny = hasCustom || Object.keys(DEFAULT_MCP_SERVERS).length > 0;
|
|
194
226
|
|
|
195
227
|
const options = [
|
|
196
|
-
{ value: 'add',
|
|
197
|
-
...(hasCustom
|
|
198
|
-
...(
|
|
199
|
-
{ value: 'done',
|
|
228
|
+
{ value: 'add', label: 'Add a server' },
|
|
229
|
+
...(hasCustom ? [{ value: 'remove', label: 'Remove a server' }] : []),
|
|
230
|
+
...(hasAny ? [{ value: 'toggle', label: 'Enable / disable servers locally' }] : []),
|
|
231
|
+
{ value: 'done', label: 'Save and exit' },
|
|
200
232
|
];
|
|
201
233
|
|
|
202
234
|
const action = await select({ message: 'What would you like to do?', options });
|
|
@@ -209,13 +241,16 @@ export async function configureMcp(projectDir) {
|
|
|
209
241
|
console.log('');
|
|
210
242
|
if (action === 'add') await addServer(mcpConfig);
|
|
211
243
|
if (action === 'remove') await removeServer(mcpConfig);
|
|
212
|
-
if (action === 'toggle') await
|
|
244
|
+
if (action === 'toggle') await toggleServers(mcpConfig, localConfig);
|
|
213
245
|
console.log('');
|
|
214
246
|
}
|
|
215
247
|
|
|
216
248
|
writeMcpConfig(aiDir, mcpConfig);
|
|
249
|
+
writeLocalConfig(projectDir, localConfig);
|
|
217
250
|
console.log('');
|
|
218
|
-
console.log(chalk.green(' ✓ Saved
|
|
219
|
-
console.log(chalk.dim('
|
|
251
|
+
console.log(chalk.green(' ✓ Saved'));
|
|
252
|
+
console.log(chalk.dim(' team config → .ai/mcp/mcp.json'));
|
|
253
|
+
console.log(chalk.dim(' local config → .js-boost.json'));
|
|
254
|
+
console.log(chalk.dim(' Run `npx @1tool/js-boost generate` to apply changes.'));
|
|
220
255
|
console.log('');
|
|
221
256
|
}
|
package/src/generators/agents.js
CHANGED
|
@@ -1,27 +1,13 @@
|
|
|
1
1
|
import { buildMcpMarkdownSection } from '../utils/mcp.js';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
* Generate AGENTS.md — the universal standard read by Codex, Cursor,
|
|
5
|
-
* GitHub Copilot, Gemini CLI, and most other agents.
|
|
6
|
-
*
|
|
7
|
-
* This is the single source of truth. All other agent files reference this.
|
|
8
|
-
*/
|
|
9
|
-
export function generateAgentsMd(guidelines, skills, mcpServers, config = {}) {
|
|
10
|
-
const projectName = config.projectName || 'this project';
|
|
11
|
-
const projectDescription = config.projectDescription || '';
|
|
3
|
+
export function generateAgentsMd(guidelines, skills, mcpServers) {
|
|
12
4
|
const sections = [];
|
|
13
5
|
|
|
14
|
-
|
|
15
|
-
sections.push(`# AGENTS.md`);
|
|
16
|
-
sections.push(`> Agent instructions for ${projectName}. Generated by [js-boost](https://github.com/your-org/js-boost) — do not edit manually.`);
|
|
17
|
-
if (projectDescription) sections.push(`\n${projectDescription}`);
|
|
6
|
+
sections.push('# Project Guidelines');
|
|
18
7
|
sections.push('');
|
|
19
|
-
|
|
20
|
-
// Table of contents hint
|
|
21
8
|
sections.push('---');
|
|
22
9
|
sections.push('');
|
|
23
10
|
|
|
24
|
-
// Guidelines — inline all content from .ai/guidelines/
|
|
25
11
|
if (guidelines.length > 0) {
|
|
26
12
|
sections.push('## Guidelines');
|
|
27
13
|
sections.push('');
|
|
@@ -29,13 +15,11 @@ export function generateAgentsMd(guidelines, skills, mcpServers, config = {}) {
|
|
|
29
15
|
sections.push('');
|
|
30
16
|
|
|
31
17
|
for (const g of guidelines) {
|
|
32
|
-
// Include the full content of each guideline file
|
|
33
18
|
sections.push(g.content);
|
|
34
19
|
sections.push('');
|
|
35
20
|
}
|
|
36
21
|
}
|
|
37
22
|
|
|
38
|
-
// Skills — reference section with name + description
|
|
39
23
|
if (skills.length > 0) {
|
|
40
24
|
sections.push('## Agent Skills');
|
|
41
25
|
sections.push('');
|
|
@@ -50,15 +34,9 @@ export function generateAgentsMd(guidelines, skills, mcpServers, config = {}) {
|
|
|
50
34
|
}
|
|
51
35
|
}
|
|
52
36
|
|
|
53
|
-
// MCP section
|
|
54
37
|
if (Object.keys(mcpServers).length > 0) {
|
|
55
38
|
sections.push(buildMcpMarkdownSection(mcpServers));
|
|
56
39
|
}
|
|
57
40
|
|
|
58
|
-
// Footer
|
|
59
|
-
sections.push('---');
|
|
60
|
-
sections.push('');
|
|
61
|
-
sections.push('*This file is auto-generated by `js-boost`. Run `npx js-boost generate` to regenerate.*');
|
|
62
|
-
|
|
63
41
|
return sections.join('\n');
|
|
64
42
|
}
|
package/src/generators/claude.js
CHANGED
|
@@ -7,13 +7,10 @@ import { buildMcpMarkdownSection } from '../utils/mcp.js';
|
|
|
7
7
|
* guidelines content here (Claude benefits from having everything upfront),
|
|
8
8
|
* plus Claude-specific directives for loading skills.
|
|
9
9
|
*/
|
|
10
|
-
export function generateClaudeMd(guidelines, skills, mcpServers
|
|
11
|
-
const projectName = config.projectName || 'this project';
|
|
10
|
+
export function generateClaudeMd(guidelines, skills, mcpServers) {
|
|
12
11
|
const sections = [];
|
|
13
12
|
|
|
14
|
-
sections.push(
|
|
15
|
-
sections.push('');
|
|
16
|
-
sections.push('> Generated by [js-boost](https://github.com/your-org/js-boost). Do not edit manually — edit `.ai/guidelines/` and `.ai/skills/` instead, then run `npx js-boost generate`.');
|
|
13
|
+
sections.push('# Project Guidelines');
|
|
17
14
|
sections.push('');
|
|
18
15
|
sections.push('---');
|
|
19
16
|
sections.push('');
|
package/src/generators/cursor.js
CHANGED
|
@@ -1,11 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* Generate .cursor/rules/js-boost.mdc for Cursor.
|
|
3
|
-
*
|
|
4
|
-
* Cursor reads .cursorrules (legacy) or .cursor/rules/*.mdc (v0.43+).
|
|
5
|
-
* We generate the modern .cursor/rules/ format.
|
|
6
|
-
* Each guideline becomes its own rule file for better organization.
|
|
7
|
-
*/
|
|
8
|
-
export function generateCursorRules(guidelines, skills, config = {}) {
|
|
1
|
+
export function generateCursorRules(guidelines, skills) {
|
|
9
2
|
const sections = [];
|
|
10
3
|
|
|
11
4
|
sections.push('---');
|
|
@@ -14,8 +7,9 @@ export function generateCursorRules(guidelines, skills, config = {}) {
|
|
|
14
7
|
sections.push('alwaysApply: true');
|
|
15
8
|
sections.push('---');
|
|
16
9
|
sections.push('');
|
|
10
|
+
sections.push('# Project Guidelines');
|
|
11
|
+
sections.push('');
|
|
17
12
|
|
|
18
|
-
// Inline all guidelines
|
|
19
13
|
if (guidelines.length > 0) {
|
|
20
14
|
for (const g of guidelines) {
|
|
21
15
|
sections.push(g.content);
|
|
@@ -23,7 +17,6 @@ export function generateCursorRules(guidelines, skills, config = {}) {
|
|
|
23
17
|
}
|
|
24
18
|
}
|
|
25
19
|
|
|
26
|
-
// Skills reference
|
|
27
20
|
if (skills.length > 0) {
|
|
28
21
|
sections.push('## Available Skills');
|
|
29
22
|
sections.push('');
|
|
@@ -34,25 +27,17 @@ export function generateCursorRules(guidelines, skills, config = {}) {
|
|
|
34
27
|
sections.push('');
|
|
35
28
|
}
|
|
36
29
|
|
|
37
|
-
sections.push('---');
|
|
38
|
-
sections.push('*Auto-generated by `js-boost`*');
|
|
39
|
-
|
|
40
30
|
return sections.join('\n');
|
|
41
31
|
}
|
|
42
32
|
|
|
43
|
-
|
|
44
|
-
* Also generate legacy .cursorrules for older Cursor versions
|
|
45
|
-
*/
|
|
46
|
-
export function generateCursorRulesLegacy(guidelines, skills, config = {}) {
|
|
33
|
+
export function generateCursorRulesLegacy(guidelines, skills) {
|
|
47
34
|
const sections = [];
|
|
48
35
|
|
|
49
|
-
sections.push('#
|
|
50
|
-
sections.push('# Generated by js-boost — edit .ai/guidelines/ instead');
|
|
36
|
+
sections.push('# Project Guidelines');
|
|
51
37
|
sections.push('');
|
|
52
38
|
|
|
53
39
|
if (guidelines.length > 0) {
|
|
54
40
|
for (const g of guidelines) {
|
|
55
|
-
// Strip markdown headings for legacy format compatibility
|
|
56
41
|
sections.push(g.content);
|
|
57
42
|
sections.push('');
|
|
58
43
|
}
|
|
@@ -60,8 +45,10 @@ export function generateCursorRulesLegacy(guidelines, skills, config = {}) {
|
|
|
60
45
|
|
|
61
46
|
if (skills.length > 0) {
|
|
62
47
|
sections.push('## Skills');
|
|
48
|
+
sections.push('');
|
|
63
49
|
for (const skill of skills) {
|
|
64
|
-
sections.push(`-
|
|
50
|
+
sections.push(`- **${skill.name}**: .ai/skills/${skill.dir}/SKILL.md`);
|
|
51
|
+
if (skill.description) sections.push(` ${skill.description}`);
|
|
65
52
|
}
|
|
66
53
|
}
|
|
67
54
|
|
package/src/generators/junie.js
CHANGED
|
@@ -1,20 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
* Generate .junie/guidelines.md for JetBrains Junie.
|
|
3
|
-
*
|
|
4
|
-
* Junie reads .junie/guidelines.md and uses it as persistent context
|
|
5
|
-
* for every task. Skills need to be referenced explicitly since Junie
|
|
6
|
-
* doesn't auto-load them from directories.
|
|
7
|
-
*/
|
|
8
|
-
export function generateJunieGuidelines(guidelines, skills, config = {}) {
|
|
9
|
-
const projectName = config.projectName || 'this project';
|
|
1
|
+
export function generateJunieGuidelines(guidelines, skills) {
|
|
10
2
|
const sections = [];
|
|
11
3
|
|
|
12
|
-
sections.push(
|
|
13
|
-
sections.push('');
|
|
14
|
-
sections.push('> Generated by [js-boost](https://github.com/your-org/js-boost). Do not edit manually.');
|
|
4
|
+
sections.push('# Project Guidelines');
|
|
15
5
|
sections.push('');
|
|
16
6
|
|
|
17
|
-
// Inline all guidelines
|
|
18
7
|
if (guidelines.length > 0) {
|
|
19
8
|
for (const g of guidelines) {
|
|
20
9
|
sections.push(g.content);
|
|
@@ -22,7 +11,6 @@ export function generateJunieGuidelines(guidelines, skills, config = {}) {
|
|
|
22
11
|
}
|
|
23
12
|
}
|
|
24
13
|
|
|
25
|
-
// Skills reference
|
|
26
14
|
if (skills.length > 0) {
|
|
27
15
|
sections.push('## Available Skills');
|
|
28
16
|
sections.push('');
|
|
@@ -35,8 +23,5 @@ export function generateJunieGuidelines(guidelines, skills, config = {}) {
|
|
|
35
23
|
sections.push('');
|
|
36
24
|
}
|
|
37
25
|
|
|
38
|
-
sections.push('---');
|
|
39
|
-
sections.push('*Auto-generated by `js-boost`. Source: `.ai/`*');
|
|
40
|
-
|
|
41
26
|
return sections.join('\n');
|
|
42
27
|
}
|
package/src/generators/kiro.js
CHANGED
|
@@ -1,16 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
* Generate .kiro/steering/guidelines.md
|
|
3
|
-
* Kiro uses steering documents in .kiro/steering/ for project context.
|
|
4
|
-
*/
|
|
5
|
-
export function generateKiroSteering(guidelines, skills, config) {
|
|
6
|
-
const projectName = config.projectName || 'This project';
|
|
1
|
+
export function generateKiroSteering(guidelines, skills) {
|
|
7
2
|
const lines = [];
|
|
8
3
|
|
|
9
4
|
lines.push('---');
|
|
10
5
|
lines.push('inclusion: always');
|
|
11
6
|
lines.push('---');
|
|
12
7
|
lines.push('');
|
|
13
|
-
lines.push(
|
|
8
|
+
lines.push('# Project Guidelines');
|
|
14
9
|
lines.push('');
|
|
15
10
|
|
|
16
11
|
if (guidelines.length > 0) {
|
|
@@ -21,15 +16,16 @@ export function generateKiroSteering(guidelines, skills, config) {
|
|
|
21
16
|
}
|
|
22
17
|
|
|
23
18
|
if (skills.length > 0) {
|
|
24
|
-
lines.push('## Skills');
|
|
19
|
+
lines.push('## Available Skills');
|
|
25
20
|
lines.push('');
|
|
26
|
-
lines.push('The following
|
|
21
|
+
lines.push('The following skill files contain detailed patterns. Read the relevant SKILL.md before working on tasks in that domain:');
|
|
27
22
|
lines.push('');
|
|
28
23
|
for (const skill of skills) {
|
|
29
|
-
lines.push(`- **${skill.name}**:
|
|
24
|
+
lines.push(`- **${skill.name}**: \`.ai/skills/${skill.dir}/SKILL.md\``);
|
|
25
|
+
if (skill.description) lines.push(` ${skill.description}`);
|
|
30
26
|
}
|
|
31
27
|
lines.push('');
|
|
32
28
|
}
|
|
33
29
|
|
|
34
30
|
return lines.join('\n');
|
|
35
|
-
}
|
|
31
|
+
}
|
package/src/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
|
-
import { readGuidelines, readSkills,
|
|
3
|
+
import { readGuidelines, readSkills, readLocalConfig, writeLocalConfig, readMcpConfig, writeFile } from './utils/reader.js';
|
|
4
4
|
import { buildMcpServers, generateMcpJson, generateJunieMcpJson } from './utils/mcp.js';
|
|
5
5
|
import { AGENTS_MD_CONSUMERS, MCP_JSON_CONSUMERS } from './agents.js';
|
|
6
6
|
import { generateAgentsMd } from './generators/agents.js';
|
|
@@ -11,29 +11,43 @@ import { generateKiroSteering } from './generators/kiro.js';
|
|
|
11
11
|
|
|
12
12
|
export async function generate(projectDir, options = {}) {
|
|
13
13
|
const aiDir = path.join(projectDir, '.ai');
|
|
14
|
-
const config = readConfig(projectDir);
|
|
15
14
|
const verbose = options.verbose ?? false;
|
|
16
15
|
|
|
17
|
-
// Determine active agents — fall back to all agents if none configured
|
|
18
|
-
const activeAgents = new Set(config.agents ?? Object.keys(
|
|
19
|
-
{ amp: 1, claude_code: 1, codex: 1, copilot: 1, cursor: 1, gemini: 1, junie: 1, kiro: 1, opencode: 1 }
|
|
20
|
-
));
|
|
21
|
-
|
|
22
|
-
const has = (key) => activeAgents.has(key);
|
|
23
|
-
const hasAny = (keys) => keys.some(k => activeAgents.has(k));
|
|
24
|
-
|
|
25
16
|
const log = (msg) => console.log(msg);
|
|
26
17
|
const info = (label, file) => log(` ${chalk.green('✓')} ${chalk.dim(label.padEnd(30))} ${chalk.cyan(file)}`);
|
|
27
18
|
const skip = (label, reason) => verbose && log(` ${chalk.yellow('–')} ${chalk.dim(label.padEnd(30))} ${chalk.yellow(reason)}`);
|
|
28
19
|
|
|
20
|
+
// Resolve agents — flag (one-off) > local config > inline prompt
|
|
21
|
+
let localConfig = readLocalConfig(projectDir);
|
|
22
|
+
let activeAgentsList;
|
|
23
|
+
let isOneOff = false;
|
|
24
|
+
|
|
25
|
+
if (options.agents) {
|
|
26
|
+
activeAgentsList = options.agents.split(',').map(a => a.trim()).filter(Boolean);
|
|
27
|
+
isOneOff = true;
|
|
28
|
+
} else if (localConfig.agents?.length) {
|
|
29
|
+
activeAgentsList = localConfig.agents;
|
|
30
|
+
} else {
|
|
31
|
+
log('');
|
|
32
|
+
log(chalk.bold.blue('⚡ js-boost') + chalk.dim(' — first run setup'));
|
|
33
|
+
const { selectAgents } = await import('./init.js');
|
|
34
|
+
activeAgentsList = await selectAgents(projectDir, null);
|
|
35
|
+
localConfig.agents = activeAgentsList;
|
|
36
|
+
writeLocalConfig(projectDir, localConfig);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const activeAgents = new Set(activeAgentsList);
|
|
40
|
+
const has = (key) => activeAgents.has(key);
|
|
41
|
+
const hasAny = (keys) => keys.some(k => activeAgents.has(k));
|
|
42
|
+
|
|
29
43
|
log('');
|
|
30
44
|
log(chalk.bold.blue('⚡ js-boost') + chalk.dim(' — generating agent files'));
|
|
31
45
|
log('');
|
|
32
46
|
|
|
33
|
-
//
|
|
47
|
+
// Read source files
|
|
34
48
|
const guidelines = await readGuidelines(aiDir);
|
|
35
49
|
const skills = await readSkills(aiDir);
|
|
36
|
-
const mcpServers = buildMcpServers(readMcpConfig(aiDir));
|
|
50
|
+
const mcpServers = buildMcpServers(readMcpConfig(aiDir), localConfig);
|
|
37
51
|
|
|
38
52
|
if (guidelines.length === 0 && skills.length === 0) {
|
|
39
53
|
log(chalk.yellow(' ⚠ No guidelines or skills found in .ai/'));
|
|
@@ -41,15 +55,15 @@ export async function generate(projectDir, options = {}) {
|
|
|
41
55
|
log('');
|
|
42
56
|
} else {
|
|
43
57
|
log(chalk.dim(` Found ${guidelines.length} guideline(s), ${skills.length} skill(s), ${Object.keys(mcpServers).length} MCP server(s)`));
|
|
44
|
-
log(chalk.dim(` Agents: ${
|
|
58
|
+
log(chalk.dim(` Agents: ${activeAgentsList.join(', ')}`));
|
|
45
59
|
log('');
|
|
46
60
|
}
|
|
47
61
|
|
|
48
62
|
const generatedFiles = [];
|
|
49
63
|
|
|
50
|
-
//
|
|
64
|
+
// AGENTS.md — Amp, Codex, Copilot, Gemini, OpenCode
|
|
51
65
|
if (hasAny(AGENTS_MD_CONSUMERS)) {
|
|
52
|
-
const agentsMd = generateAgentsMd(guidelines, skills, mcpServers,
|
|
66
|
+
const agentsMd = generateAgentsMd(guidelines, skills, mcpServers, {});
|
|
53
67
|
writeFile(path.join(projectDir, 'AGENTS.md'), agentsMd);
|
|
54
68
|
info('AGENTS.md', 'AGENTS.md');
|
|
55
69
|
generatedFiles.push('AGENTS.md');
|
|
@@ -57,9 +71,9 @@ export async function generate(projectDir, options = {}) {
|
|
|
57
71
|
skip('AGENTS.md', 'no AGENTS.md consumers selected');
|
|
58
72
|
}
|
|
59
73
|
|
|
60
|
-
//
|
|
74
|
+
// CLAUDE.md — Claude Code
|
|
61
75
|
if (has('claude_code')) {
|
|
62
|
-
const claudeMd = generateClaudeMd(guidelines, skills, mcpServers,
|
|
76
|
+
const claudeMd = generateClaudeMd(guidelines, skills, mcpServers, {});
|
|
63
77
|
writeFile(path.join(projectDir, 'CLAUDE.md'), claudeMd);
|
|
64
78
|
info('Claude Code', 'CLAUDE.md');
|
|
65
79
|
generatedFiles.push('CLAUDE.md');
|
|
@@ -67,7 +81,7 @@ export async function generate(projectDir, options = {}) {
|
|
|
67
81
|
skip('Claude Code', 'not selected');
|
|
68
82
|
}
|
|
69
83
|
|
|
70
|
-
//
|
|
84
|
+
// .mcp.json — Claude Code + Codex
|
|
71
85
|
if (hasAny(MCP_JSON_CONSUMERS)) {
|
|
72
86
|
const mcpJson = generateMcpJson(mcpServers);
|
|
73
87
|
writeFile(path.join(projectDir, '.mcp.json'), mcpJson);
|
|
@@ -77,9 +91,9 @@ export async function generate(projectDir, options = {}) {
|
|
|
77
91
|
skip('MCP', 'no MCP consumers selected');
|
|
78
92
|
}
|
|
79
93
|
|
|
80
|
-
//
|
|
94
|
+
// .junie/ — JetBrains Junie
|
|
81
95
|
if (has('junie')) {
|
|
82
|
-
const junieGuidelines = generateJunieGuidelines(guidelines, skills,
|
|
96
|
+
const junieGuidelines = generateJunieGuidelines(guidelines, skills, {});
|
|
83
97
|
writeFile(path.join(projectDir, '.junie', 'guidelines.md'), junieGuidelines);
|
|
84
98
|
info('Junie guidelines', '.junie/guidelines.md');
|
|
85
99
|
generatedFiles.push('.junie/guidelines.md');
|
|
@@ -92,14 +106,14 @@ export async function generate(projectDir, options = {}) {
|
|
|
92
106
|
skip('Junie', 'not selected');
|
|
93
107
|
}
|
|
94
108
|
|
|
95
|
-
//
|
|
109
|
+
// Cursor
|
|
96
110
|
if (has('cursor')) {
|
|
97
|
-
const cursorRules = generateCursorRules(guidelines, skills,
|
|
111
|
+
const cursorRules = generateCursorRules(guidelines, skills, {});
|
|
98
112
|
writeFile(path.join(projectDir, '.cursor', 'rules', 'js-boost.mdc'), cursorRules);
|
|
99
113
|
info('Cursor (modern)', '.cursor/rules/js-boost.mdc');
|
|
100
114
|
generatedFiles.push('.cursor/rules/js-boost.mdc');
|
|
101
115
|
|
|
102
|
-
const cursorLegacy = generateCursorRulesLegacy(guidelines, skills,
|
|
116
|
+
const cursorLegacy = generateCursorRulesLegacy(guidelines, skills, {});
|
|
103
117
|
writeFile(path.join(projectDir, '.cursorrules'), cursorLegacy);
|
|
104
118
|
info('Cursor (legacy)', '.cursorrules');
|
|
105
119
|
generatedFiles.push('.cursorrules');
|
|
@@ -107,9 +121,9 @@ export async function generate(projectDir, options = {}) {
|
|
|
107
121
|
skip('Cursor', 'not selected');
|
|
108
122
|
}
|
|
109
123
|
|
|
110
|
-
//
|
|
124
|
+
// Kiro
|
|
111
125
|
if (has('kiro')) {
|
|
112
|
-
const kiroSteering = generateKiroSteering(guidelines, skills,
|
|
126
|
+
const kiroSteering = generateKiroSteering(guidelines, skills, {});
|
|
113
127
|
writeFile(path.join(projectDir, '.kiro', 'steering', 'guidelines.md'), kiroSteering);
|
|
114
128
|
info('Kiro', '.kiro/steering/guidelines.md');
|
|
115
129
|
generatedFiles.push('.kiro/steering/guidelines.md');
|
|
@@ -121,5 +135,12 @@ export async function generate(projectDir, options = {}) {
|
|
|
121
135
|
log(chalk.green.bold(` ✓ Generated ${generatedFiles.length} files successfully`));
|
|
122
136
|
log('');
|
|
123
137
|
|
|
138
|
+
// Write state back to local config (skip for one-off --agents flag)
|
|
139
|
+
if (!isOneOff) {
|
|
140
|
+
localConfig.guidelines = true;
|
|
141
|
+
localConfig.skills = skills.map(s => s.name);
|
|
142
|
+
writeLocalConfig(projectDir, localConfig);
|
|
143
|
+
}
|
|
144
|
+
|
|
124
145
|
return generatedFiles;
|
|
125
|
-
}
|
|
146
|
+
}
|
package/src/init.js
CHANGED
|
@@ -2,7 +2,7 @@ import path from 'path';
|
|
|
2
2
|
import fs from 'fs';
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import { multiselect, isCancel, cancel } from '@clack/prompts';
|
|
5
|
-
import { writeFile, writeMcpConfig } from './utils/reader.js';
|
|
5
|
+
import { writeFile, writeMcpConfig, writeLocalConfig } from './utils/reader.js';
|
|
6
6
|
import { AGENTS } from './agents.js';
|
|
7
7
|
import { detectInstalledAgents } from './utils/detect.js';
|
|
8
8
|
|
|
@@ -39,6 +39,15 @@ TODO: describe the steps, patterns, or conventions the agent should follow.
|
|
|
39
39
|
`
|
|
40
40
|
};
|
|
41
41
|
|
|
42
|
+
function addToGitignore(projectDir, entry) {
|
|
43
|
+
const gitignorePath = path.join(projectDir, '.gitignore');
|
|
44
|
+
if (!fs.existsSync(gitignorePath)) return false;
|
|
45
|
+
const content = fs.readFileSync(gitignorePath, 'utf8');
|
|
46
|
+
if (content.split('\n').some(line => line.trim() === entry)) return false;
|
|
47
|
+
fs.appendFileSync(gitignorePath, `\n# js-boost local config\n${entry}\n`, 'utf8');
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
|
|
42
51
|
export async function selectAgents(projectDir, currentAgents = null) {
|
|
43
52
|
const detected = detectInstalledAgents(projectDir);
|
|
44
53
|
|
|
@@ -102,31 +111,33 @@ export async function init(projectDir, options = {}) {
|
|
|
102
111
|
}
|
|
103
112
|
}
|
|
104
113
|
|
|
105
|
-
// Create .ai/mcp/mcp.json
|
|
114
|
+
// Create .ai/mcp/mcp.json
|
|
106
115
|
const mcpJsonPath = path.join(aiDir, 'mcp', 'mcp.json');
|
|
107
116
|
if (!fs.existsSync(mcpJsonPath) || force) {
|
|
108
|
-
writeMcpConfig(aiDir, {
|
|
117
|
+
writeMcpConfig(aiDir, { mcpServers: {} });
|
|
109
118
|
console.log(` ${chalk.green('✓')} ${chalk.cyan('.ai/mcp/mcp.json')}`);
|
|
110
119
|
}
|
|
111
120
|
|
|
112
|
-
//
|
|
113
|
-
const
|
|
114
|
-
let
|
|
115
|
-
if (fs.existsSync(
|
|
116
|
-
try {
|
|
121
|
+
// Agent selection
|
|
122
|
+
const localConfigPath = path.join(projectDir, '.js-boost.json');
|
|
123
|
+
let existingLocal = {};
|
|
124
|
+
if (fs.existsSync(localConfigPath)) {
|
|
125
|
+
try { existingLocal = JSON.parse(fs.readFileSync(localConfigPath, 'utf8')); } catch {}
|
|
117
126
|
}
|
|
118
127
|
|
|
119
|
-
|
|
120
|
-
const selectedAgents = await selectAgents(projectDir, existingConfig.agents);
|
|
128
|
+
const selectedAgents = await selectAgents(projectDir, existingLocal.agents ?? null);
|
|
121
129
|
|
|
122
|
-
|
|
123
|
-
projectName: existingConfig.projectName || path.basename(projectDir),
|
|
124
|
-
projectDescription: existingConfig.projectDescription || '',
|
|
130
|
+
writeLocalConfig(projectDir, {
|
|
125
131
|
agents: selectedAgents,
|
|
126
|
-
|
|
132
|
+
disabledMcpServers: existingLocal.disabledMcpServers ?? [],
|
|
133
|
+
});
|
|
134
|
+
console.log(` ${chalk.green('✓')} ${chalk.cyan('.js-boost.json')}`);
|
|
127
135
|
|
|
128
|
-
|
|
129
|
-
|
|
136
|
+
// Ensure .js-boost.json is gitignored
|
|
137
|
+
const added = addToGitignore(projectDir, '.js-boost.json');
|
|
138
|
+
if (added) {
|
|
139
|
+
console.log(` ${chalk.green('✓')} ${chalk.cyan('.js-boost.json')} added to ${chalk.cyan('.gitignore')}`);
|
|
140
|
+
}
|
|
130
141
|
|
|
131
142
|
console.log('');
|
|
132
143
|
console.log(chalk.green.bold(' ✓ .ai/ folder initialized'));
|
|
@@ -134,6 +145,6 @@ export async function init(projectDir, options = {}) {
|
|
|
134
145
|
console.log(chalk.dim(' Next steps:'));
|
|
135
146
|
console.log(chalk.dim(' 1. Edit .ai/guidelines/*.md with your project conventions'));
|
|
136
147
|
console.log(chalk.dim(' 2. Add skills in .ai/skills/<name>/SKILL.md'));
|
|
137
|
-
console.log(chalk.dim(' 3. Run: npx js-boost generate'));
|
|
148
|
+
console.log(chalk.dim(' 3. Run: npx @1tool/js-boost generate'));
|
|
138
149
|
console.log('');
|
|
139
150
|
}
|
package/src/utils/mcp.js
CHANGED
|
@@ -1,53 +1,50 @@
|
|
|
1
|
-
|
|
2
|
-
* Default MCP servers pre-configured for js-boost
|
|
3
|
-
*/
|
|
4
|
-
export const DEFAULT_MCP_SERVERS = {
|
|
5
|
-
};
|
|
1
|
+
export const DEFAULT_MCP_SERVERS = {};
|
|
6
2
|
|
|
7
3
|
/**
|
|
8
|
-
*
|
|
9
|
-
* from js-boost.config.json
|
|
4
|
+
* Merge team servers (.ai/mcp/mcp.json) with per-developer disabled list (.js-boost.json)
|
|
10
5
|
*/
|
|
11
|
-
export function buildMcpServers(mcpConfig = {}) {
|
|
12
|
-
const userServers = mcpConfig.
|
|
13
|
-
const
|
|
6
|
+
export function buildMcpServers(mcpConfig = {}, localConfig = {}) {
|
|
7
|
+
const userServers = mcpConfig.mcpServers || {};
|
|
8
|
+
const disabled = new Set(localConfig.disabledMcpServers || []);
|
|
14
9
|
|
|
15
10
|
const servers = {};
|
|
16
11
|
|
|
17
|
-
// Add default servers (unless disabled)
|
|
18
12
|
for (const [key, server] of Object.entries(DEFAULT_MCP_SERVERS)) {
|
|
19
|
-
if (!
|
|
13
|
+
if (!disabled.has(key)) {
|
|
20
14
|
servers[key] = server;
|
|
21
15
|
}
|
|
22
16
|
}
|
|
23
17
|
|
|
24
|
-
// Merge user-defined servers (can override defaults)
|
|
25
18
|
for (const [key, server] of Object.entries(userServers)) {
|
|
26
|
-
|
|
19
|
+
if (!disabled.has(key)) {
|
|
20
|
+
servers[key] = server;
|
|
21
|
+
}
|
|
27
22
|
}
|
|
28
23
|
|
|
29
24
|
return servers;
|
|
30
25
|
}
|
|
31
26
|
|
|
32
27
|
/**
|
|
33
|
-
* Generate .mcp.json (
|
|
34
|
-
*
|
|
28
|
+
* Generate .mcp.json (Claude Code + Codex)
|
|
29
|
+
* - stdio: detected by presence of `command` (no type field)
|
|
30
|
+
* - remote: type === 'http', wrapped in mcp-remote, headers passed as --header args
|
|
35
31
|
*/
|
|
36
32
|
export function generateMcpJson(servers) {
|
|
37
33
|
const mcpServers = {};
|
|
38
34
|
|
|
39
35
|
for (const [key, server] of Object.entries(servers)) {
|
|
40
|
-
if (server.type === '
|
|
36
|
+
if (server.type === 'http') {
|
|
41
37
|
mcpServers[key] = {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
...(server.
|
|
38
|
+
type: 'http',
|
|
39
|
+
url: server.url,
|
|
40
|
+
...(server.headers ? { headers: server.headers } : {}),
|
|
45
41
|
};
|
|
46
|
-
} else if (server.
|
|
47
|
-
// Claude Code uses mcp-remote wrapper for HTTP/SSE servers
|
|
42
|
+
} else if (server.command) {
|
|
48
43
|
mcpServers[key] = {
|
|
49
|
-
|
|
50
|
-
|
|
44
|
+
type: 'stdio',
|
|
45
|
+
command: server.command,
|
|
46
|
+
args: server.args || [],
|
|
47
|
+
env: server.env || {},
|
|
51
48
|
};
|
|
52
49
|
}
|
|
53
50
|
}
|
|
@@ -56,21 +53,19 @@ export function generateMcpJson(servers) {
|
|
|
56
53
|
}
|
|
57
54
|
|
|
58
55
|
/**
|
|
59
|
-
* Generate .junie/mcp.json
|
|
56
|
+
* Generate .junie/mcp.json — remote servers referenced by URL directly
|
|
60
57
|
*/
|
|
61
58
|
export function generateJunieMcpJson(servers) {
|
|
62
59
|
const mcpServers = {};
|
|
63
60
|
|
|
64
61
|
for (const [key, server] of Object.entries(servers)) {
|
|
65
|
-
if (server.type === '
|
|
62
|
+
if (server.type === 'http') {
|
|
63
|
+
mcpServers[key] = { url: server.url };
|
|
64
|
+
} else if (server.command) {
|
|
66
65
|
mcpServers[key] = {
|
|
67
66
|
command: server.command,
|
|
68
67
|
args: server.args || [],
|
|
69
|
-
...(server.env ? { env: server.env } : {})
|
|
70
|
-
};
|
|
71
|
-
} else if (server.type === 'remote') {
|
|
72
|
-
mcpServers[key] = {
|
|
73
|
-
url: server.url
|
|
68
|
+
...(server.env ? { env: server.env } : {}),
|
|
74
69
|
};
|
|
75
70
|
}
|
|
76
71
|
}
|
|
@@ -88,8 +83,8 @@ export function buildMcpMarkdownSection(servers) {
|
|
|
88
83
|
for (const [key, server] of Object.entries(servers)) {
|
|
89
84
|
lines.push(`### ${key}`);
|
|
90
85
|
if (server.description) lines.push(`> ${server.description}`);
|
|
91
|
-
if (server.type === '
|
|
92
|
-
if (server.
|
|
86
|
+
if (server.type === 'http') lines.push(`- **URL:** \`${server.url}\``);
|
|
87
|
+
if (server.command) lines.push(`- **Command:** \`${server.command} ${(server.args || []).join(' ')}\``);
|
|
93
88
|
lines.push('');
|
|
94
89
|
}
|
|
95
90
|
|
package/src/utils/reader.js
CHANGED
|
@@ -41,7 +41,6 @@ export async function readSkills(aiDir) {
|
|
|
41
41
|
const content = fs.readFileSync(fullPath, 'utf8').trim();
|
|
42
42
|
const dir = path.dirname(file);
|
|
43
43
|
|
|
44
|
-
// Parse YAML frontmatter: ---\nname: ...\ndescription: ...\n---
|
|
45
44
|
let name = dir;
|
|
46
45
|
let description = '';
|
|
47
46
|
const fmMatch = content.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
@@ -52,7 +51,6 @@ export async function readSkills(aiDir) {
|
|
|
52
51
|
if (nameMatch) name = nameMatch[1].trim();
|
|
53
52
|
if (descMatch) description = descMatch[1].trim();
|
|
54
53
|
} else {
|
|
55
|
-
// Fallback: try first heading
|
|
56
54
|
const headingMatch = content.match(/^#+\s*(.+)$/m);
|
|
57
55
|
if (headingMatch) name = headingMatch[1].trim();
|
|
58
56
|
}
|
|
@@ -62,10 +60,11 @@ export async function readSkills(aiDir) {
|
|
|
62
60
|
}
|
|
63
61
|
|
|
64
62
|
/**
|
|
65
|
-
* Read js-boost.
|
|
63
|
+
* Read .js-boost.json — per-developer local config (gitignored)
|
|
64
|
+
* Returns { agents, guidelines, skills, disabledMcpServers }
|
|
66
65
|
*/
|
|
67
|
-
export function
|
|
68
|
-
const configPath = path.join(projectDir, 'js-boost.
|
|
66
|
+
export function readLocalConfig(projectDir) {
|
|
67
|
+
const configPath = path.join(projectDir, '.js-boost.json');
|
|
69
68
|
if (!fs.existsSync(configPath)) return {};
|
|
70
69
|
try {
|
|
71
70
|
return JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
@@ -75,20 +74,24 @@ export function readConfig(projectDir) {
|
|
|
75
74
|
}
|
|
76
75
|
|
|
77
76
|
/**
|
|
78
|
-
*
|
|
79
|
-
|
|
77
|
+
* Write .js-boost.json
|
|
78
|
+
*/
|
|
79
|
+
export function writeLocalConfig(projectDir, config) {
|
|
80
|
+
writeFile(path.join(projectDir, '.js-boost.json'), JSON.stringify(config, null, 2));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Read .ai/mcp/mcp.json — team MCP server definitions
|
|
85
|
+
* Returns { mcpServers: {} }
|
|
80
86
|
*/
|
|
81
87
|
export function readMcpConfig(aiDir) {
|
|
82
88
|
const mcpPath = path.join(aiDir, 'mcp', 'mcp.json');
|
|
83
|
-
if (!fs.existsSync(mcpPath)) return {
|
|
89
|
+
if (!fs.existsSync(mcpPath)) return { mcpServers: {} };
|
|
84
90
|
try {
|
|
85
91
|
const parsed = JSON.parse(fs.readFileSync(mcpPath, 'utf8'));
|
|
86
|
-
return {
|
|
87
|
-
servers: parsed.servers || {},
|
|
88
|
-
disabled: parsed.disabled || [],
|
|
89
|
-
};
|
|
92
|
+
return { mcpServers: parsed.mcpServers || {} };
|
|
90
93
|
} catch {
|
|
91
|
-
return {
|
|
94
|
+
return { mcpServers: {} };
|
|
92
95
|
}
|
|
93
96
|
}
|
|
94
97
|
|
package/src/watch.js
CHANGED
|
@@ -5,7 +5,7 @@ import { generate } from './index.js';
|
|
|
5
5
|
|
|
6
6
|
export function watch(projectDir) {
|
|
7
7
|
const aiDir = path.join(projectDir, '.ai');
|
|
8
|
-
const configPath = path.join(projectDir, 'js-boost.
|
|
8
|
+
const configPath = path.join(projectDir, '.js-boost.json');
|
|
9
9
|
|
|
10
10
|
console.log('');
|
|
11
11
|
console.log(chalk.bold.blue('⚡ js-boost') + chalk.dim(' — watch mode'));
|