@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.
@@ -0,0 +1,64 @@
1
+ import { buildMcpMarkdownSection } from '../utils/mcp.js';
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 || '';
12
+ const sections = [];
13
+
14
+ // Header
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}`);
18
+ sections.push('');
19
+
20
+ // Table of contents hint
21
+ sections.push('---');
22
+ sections.push('');
23
+
24
+ // Guidelines — inline all content from .ai/guidelines/
25
+ if (guidelines.length > 0) {
26
+ sections.push('## Guidelines');
27
+ sections.push('');
28
+ sections.push('These guidelines define conventions, patterns, and best practices for this project.');
29
+ sections.push('');
30
+
31
+ for (const g of guidelines) {
32
+ // Include the full content of each guideline file
33
+ sections.push(g.content);
34
+ sections.push('');
35
+ }
36
+ }
37
+
38
+ // Skills — reference section with name + description
39
+ if (skills.length > 0) {
40
+ sections.push('## Agent Skills');
41
+ sections.push('');
42
+ sections.push('The following skills are available in `.ai/skills/`. Load a skill when the task matches its description.');
43
+ sections.push('');
44
+
45
+ for (const skill of skills) {
46
+ sections.push(`### ${skill.name}`);
47
+ if (skill.description) sections.push(`${skill.description}`);
48
+ sections.push(`- **Skill file:** \`.ai/skills/${skill.dir}/SKILL.md\``);
49
+ sections.push('');
50
+ }
51
+ }
52
+
53
+ // MCP section
54
+ if (Object.keys(mcpServers).length > 0) {
55
+ sections.push(buildMcpMarkdownSection(mcpServers));
56
+ }
57
+
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
+ return sections.join('\n');
64
+ }
@@ -0,0 +1,60 @@
1
+ import { buildMcpMarkdownSection } from '../utils/mcp.js';
2
+
3
+ /**
4
+ * Generate CLAUDE.md for Claude Code.
5
+ *
6
+ * Claude Code reads CLAUDE.md from the project root. We include the full
7
+ * guidelines content here (Claude benefits from having everything upfront),
8
+ * plus Claude-specific directives for loading skills.
9
+ */
10
+ export function generateClaudeMd(guidelines, skills, mcpServers, config = {}) {
11
+ const projectName = config.projectName || 'this project';
12
+ const sections = [];
13
+
14
+ sections.push(`# CLAUDE.md — ${projectName}`);
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`.');
17
+ sections.push('');
18
+ sections.push('---');
19
+ sections.push('');
20
+
21
+ // Claude Code: Skills loading instruction
22
+ if (skills.length > 0) {
23
+ sections.push('## Skills');
24
+ sections.push('');
25
+ sections.push('When starting work on a task, check if any of the following skills apply and load the relevant `SKILL.md` file before proceeding:');
26
+ sections.push('');
27
+ for (const skill of skills) {
28
+ sections.push(`- **${skill.name}** — ${skill.description || 'see SKILL.md'}`);
29
+ sections.push(` - Load: \`.ai/skills/${skill.dir}/SKILL.md\``);
30
+ }
31
+ sections.push('');
32
+ sections.push('---');
33
+ sections.push('');
34
+ }
35
+
36
+ // Include all guidelines inline
37
+ if (guidelines.length > 0) {
38
+ sections.push('## Project Guidelines');
39
+ sections.push('');
40
+ for (const g of guidelines) {
41
+ sections.push(g.content);
42
+ sections.push('');
43
+ }
44
+ sections.push('---');
45
+ sections.push('');
46
+ }
47
+
48
+ // MCP section
49
+ if (Object.keys(mcpServers).length > 0) {
50
+ sections.push(buildMcpMarkdownSection(mcpServers));
51
+ sections.push('');
52
+ sections.push('> MCP servers are configured in `.mcp.json`. Claude Code will pick them up automatically.');
53
+ sections.push('');
54
+ }
55
+
56
+ sections.push('---');
57
+ sections.push('*Auto-generated by `js-boost`. Source: `.ai/`*');
58
+
59
+ return sections.join('\n');
60
+ }
@@ -0,0 +1,69 @@
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 = {}) {
9
+ const sections = [];
10
+
11
+ sections.push('---');
12
+ sections.push('description: Project guidelines and conventions');
13
+ sections.push('globs: ["**/*"]');
14
+ sections.push('alwaysApply: true');
15
+ sections.push('---');
16
+ sections.push('');
17
+
18
+ // Inline all guidelines
19
+ if (guidelines.length > 0) {
20
+ for (const g of guidelines) {
21
+ sections.push(g.content);
22
+ sections.push('');
23
+ }
24
+ }
25
+
26
+ // Skills reference
27
+ if (skills.length > 0) {
28
+ sections.push('## Available Skills');
29
+ sections.push('');
30
+ for (const skill of skills) {
31
+ sections.push(`- **${skill.name}**: \`.ai/skills/${skill.dir}/SKILL.md\``);
32
+ if (skill.description) sections.push(` ${skill.description}`);
33
+ }
34
+ sections.push('');
35
+ }
36
+
37
+ sections.push('---');
38
+ sections.push('*Auto-generated by `js-boost`*');
39
+
40
+ return sections.join('\n');
41
+ }
42
+
43
+ /**
44
+ * Also generate legacy .cursorrules for older Cursor versions
45
+ */
46
+ export function generateCursorRulesLegacy(guidelines, skills, config = {}) {
47
+ const sections = [];
48
+
49
+ sections.push('# Cursor Rules');
50
+ sections.push('# Generated by js-boost — edit .ai/guidelines/ instead');
51
+ sections.push('');
52
+
53
+ if (guidelines.length > 0) {
54
+ for (const g of guidelines) {
55
+ // Strip markdown headings for legacy format compatibility
56
+ sections.push(g.content);
57
+ sections.push('');
58
+ }
59
+ }
60
+
61
+ if (skills.length > 0) {
62
+ sections.push('## Skills');
63
+ for (const skill of skills) {
64
+ sections.push(`- ${skill.name}: .ai/skills/${skill.dir}/SKILL.md`);
65
+ }
66
+ }
67
+
68
+ return sections.join('\n');
69
+ }
@@ -0,0 +1,42 @@
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';
10
+ const sections = [];
11
+
12
+ sections.push(`# Junie Guidelines — ${projectName}`);
13
+ sections.push('');
14
+ sections.push('> Generated by [js-boost](https://github.com/your-org/js-boost). Do not edit manually.');
15
+ sections.push('');
16
+
17
+ // Inline all guidelines
18
+ if (guidelines.length > 0) {
19
+ for (const g of guidelines) {
20
+ sections.push(g.content);
21
+ sections.push('');
22
+ }
23
+ }
24
+
25
+ // Skills reference
26
+ if (skills.length > 0) {
27
+ sections.push('## Available Skills');
28
+ sections.push('');
29
+ sections.push('The following skill files contain detailed patterns. Read the relevant SKILL.md before working on tasks in that domain:');
30
+ sections.push('');
31
+ for (const skill of skills) {
32
+ sections.push(`- **${skill.name}**: \`.ai/skills/${skill.dir}/SKILL.md\``);
33
+ if (skill.description) sections.push(` ${skill.description}`);
34
+ }
35
+ sections.push('');
36
+ }
37
+
38
+ sections.push('---');
39
+ sections.push('*Auto-generated by `js-boost`. Source: `.ai/`*');
40
+
41
+ return sections.join('\n');
42
+ }
@@ -0,0 +1,35 @@
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';
7
+ const lines = [];
8
+
9
+ lines.push('---');
10
+ lines.push('inclusion: always');
11
+ lines.push('---');
12
+ lines.push('');
13
+ lines.push(`# ${projectName} — AI Guidelines`);
14
+ lines.push('');
15
+
16
+ if (guidelines.length > 0) {
17
+ for (const g of guidelines) {
18
+ lines.push(g.content);
19
+ lines.push('');
20
+ }
21
+ }
22
+
23
+ if (skills.length > 0) {
24
+ lines.push('## Skills');
25
+ lines.push('');
26
+ lines.push('The following skills are available for this project:');
27
+ lines.push('');
28
+ for (const skill of skills) {
29
+ lines.push(`- **${skill.name}**: ${skill.description || skill.dir}`);
30
+ }
31
+ lines.push('');
32
+ }
33
+
34
+ return lines.join('\n');
35
+ }
package/src/index.js ADDED
@@ -0,0 +1,125 @@
1
+ import path from 'path';
2
+ import chalk from 'chalk';
3
+ import { readGuidelines, readSkills, readConfig, writeFile } from './utils/reader.js';
4
+ import { buildMcpServers, generateMcpJson, generateJunieMcpJson } from './utils/mcp.js';
5
+ import { AGENTS_MD_CONSUMERS, MCP_JSON_CONSUMERS } from './agents.js';
6
+ import { generateAgentsMd } from './generators/agents.js';
7
+ import { generateClaudeMd } from './generators/claude.js';
8
+ import { generateJunieGuidelines } from './generators/junie.js';
9
+ import { generateCursorRules, generateCursorRulesLegacy } from './generators/cursor.js';
10
+ import { generateKiroSteering } from './generators/kiro.js';
11
+
12
+ export async function generate(projectDir, options = {}) {
13
+ const aiDir = path.join(projectDir, '.ai');
14
+ const config = readConfig(projectDir);
15
+ const verbose = options.verbose ?? false;
16
+
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
+ const log = (msg) => console.log(msg);
26
+ const info = (label, file) => log(` ${chalk.green('✓')} ${chalk.dim(label.padEnd(30))} ${chalk.cyan(file)}`);
27
+ const skip = (label, reason) => verbose && log(` ${chalk.yellow('–')} ${chalk.dim(label.padEnd(30))} ${chalk.yellow(reason)}`);
28
+
29
+ log('');
30
+ log(chalk.bold.blue('⚡ js-boost') + chalk.dim(' — generating agent files'));
31
+ log('');
32
+
33
+ // 1. Read source files
34
+ const guidelines = await readGuidelines(aiDir);
35
+ const skills = await readSkills(aiDir);
36
+ const mcpServers = buildMcpServers(config);
37
+
38
+ if (guidelines.length === 0 && skills.length === 0) {
39
+ log(chalk.yellow(' ⚠ No guidelines or skills found in .ai/'));
40
+ log(chalk.dim(' Create .ai/guidelines/*.md or .ai/skills/*/SKILL.md to get started'));
41
+ log('');
42
+ } else {
43
+ log(chalk.dim(` Found ${guidelines.length} guideline(s), ${skills.length} skill(s), ${Object.keys(mcpServers).length} MCP server(s)`));
44
+ log(chalk.dim(` Agents: ${[...activeAgents].join(', ')}`));
45
+ log('');
46
+ }
47
+
48
+ const generatedFiles = [];
49
+
50
+ // 2. AGENTS.md — shared format for Codex, Copilot, Gemini, Amp, OpenCode
51
+ if (hasAny(AGENTS_MD_CONSUMERS)) {
52
+ const agentsMd = generateAgentsMd(guidelines, skills, mcpServers, config);
53
+ writeFile(path.join(projectDir, 'AGENTS.md'), agentsMd);
54
+ info('AGENTS.md', 'AGENTS.md');
55
+ generatedFiles.push('AGENTS.md');
56
+ } else {
57
+ skip('AGENTS.md', 'no AGENTS.md consumers selected');
58
+ }
59
+
60
+ // 3. CLAUDE.md — Claude Code
61
+ if (has('claude_code')) {
62
+ const claudeMd = generateClaudeMd(guidelines, skills, mcpServers, config);
63
+ writeFile(path.join(projectDir, 'CLAUDE.md'), claudeMd);
64
+ info('Claude Code', 'CLAUDE.md');
65
+ generatedFiles.push('CLAUDE.md');
66
+ } else {
67
+ skip('Claude Code', 'not selected');
68
+ }
69
+
70
+ // 4. .mcp.json — Claude Code + Codex
71
+ if (hasAny(MCP_JSON_CONSUMERS)) {
72
+ const mcpJson = generateMcpJson(mcpServers);
73
+ writeFile(path.join(projectDir, '.mcp.json'), mcpJson);
74
+ info('MCP (Claude/Codex)', '.mcp.json');
75
+ generatedFiles.push('.mcp.json');
76
+ } else {
77
+ skip('MCP', 'no MCP consumers selected');
78
+ }
79
+
80
+ // 5. .junie/ — JetBrains Junie
81
+ if (has('junie')) {
82
+ const junieGuidelines = generateJunieGuidelines(guidelines, skills, config);
83
+ writeFile(path.join(projectDir, '.junie', 'guidelines.md'), junieGuidelines);
84
+ info('Junie guidelines', '.junie/guidelines.md');
85
+ generatedFiles.push('.junie/guidelines.md');
86
+
87
+ const junieMcpJson = generateJunieMcpJson(mcpServers);
88
+ writeFile(path.join(projectDir, '.junie', 'mcp.json'), junieMcpJson);
89
+ info('Junie MCP', '.junie/mcp.json');
90
+ generatedFiles.push('.junie/mcp.json');
91
+ } else {
92
+ skip('Junie', 'not selected');
93
+ }
94
+
95
+ // 6. Cursor — .cursor/rules/ + legacy .cursorrules
96
+ if (has('cursor')) {
97
+ const cursorRules = generateCursorRules(guidelines, skills, config);
98
+ writeFile(path.join(projectDir, '.cursor', 'rules', 'js-boost.mdc'), cursorRules);
99
+ info('Cursor (modern)', '.cursor/rules/js-boost.mdc');
100
+ generatedFiles.push('.cursor/rules/js-boost.mdc');
101
+
102
+ const cursorLegacy = generateCursorRulesLegacy(guidelines, skills, config);
103
+ writeFile(path.join(projectDir, '.cursorrules'), cursorLegacy);
104
+ info('Cursor (legacy)', '.cursorrules');
105
+ generatedFiles.push('.cursorrules');
106
+ } else {
107
+ skip('Cursor', 'not selected');
108
+ }
109
+
110
+ // 7. Kiro — .kiro/steering/guidelines.md
111
+ if (has('kiro')) {
112
+ const kiroSteering = generateKiroSteering(guidelines, skills, config);
113
+ writeFile(path.join(projectDir, '.kiro', 'steering', 'guidelines.md'), kiroSteering);
114
+ info('Kiro', '.kiro/steering/guidelines.md');
115
+ generatedFiles.push('.kiro/steering/guidelines.md');
116
+ } else {
117
+ skip('Kiro', 'not selected');
118
+ }
119
+
120
+ log('');
121
+ log(chalk.green.bold(` ✓ Generated ${generatedFiles.length} files successfully`));
122
+ log('');
123
+
124
+ return generatedFiles;
125
+ }
package/src/init.js ADDED
@@ -0,0 +1,134 @@
1
+ import path from 'path';
2
+ import fs from 'fs';
3
+ import chalk from 'chalk';
4
+ import { multiselect, isCancel, cancel } from '@clack/prompts';
5
+ import { writeFile } from './utils/reader.js';
6
+ import { AGENTS } from './agents.js';
7
+ import { detectInstalledAgents } from './utils/detect.js';
8
+
9
+ const EXAMPLE_GUIDELINES = {
10
+ 'general.md': `# General Guidelines
11
+
12
+ ## Code Style
13
+ - TODO: describe your preferred language, style, and formatting conventions
14
+
15
+ ## Project Conventions
16
+ - TODO: describe your folder structure and naming conventions
17
+
18
+ ## Git
19
+ - TODO: describe your branching and commit message conventions
20
+ `,
21
+
22
+ 'testing.md': `# Testing Guidelines
23
+
24
+ ## Conventions
25
+ - TODO: describe your test framework and file naming conventions
26
+ - TODO: describe what should and should not be mocked
27
+ `,
28
+ };
29
+
30
+ const EXAMPLE_SKILL = {
31
+ 'SKILL.md': `---
32
+ name: example-skill
33
+ description: TODO: describe when the agent should use this skill.
34
+ ---
35
+
36
+ # Example Skill
37
+
38
+ TODO: describe the steps, patterns, or conventions the agent should follow.
39
+ `
40
+ };
41
+
42
+ export async function selectAgents(projectDir, currentAgents = null) {
43
+ const detected = detectInstalledAgents(projectDir);
44
+
45
+ const options = Object.values(AGENTS).map(agent => ({
46
+ value: agent.key,
47
+ label: agent.name,
48
+ hint: agent.hint,
49
+ }));
50
+
51
+ const defaults = currentAgents
52
+ ? currentAgents
53
+ : detected.length > 0
54
+ ? detected
55
+ : Object.keys(AGENTS);
56
+
57
+ console.log('');
58
+ const selected = await multiselect({
59
+ message: 'Which AI agents would you like to configure?',
60
+ options,
61
+ initialValues: defaults,
62
+ required: true,
63
+ });
64
+
65
+ if (isCancel(selected)) {
66
+ cancel('Operation cancelled.');
67
+ process.exit(0);
68
+ }
69
+
70
+ return selected;
71
+ }
72
+
73
+ export async function init(projectDir, options = {}) {
74
+ const aiDir = path.join(projectDir, '.ai');
75
+ const force = options.force ?? false;
76
+
77
+ console.log('');
78
+ console.log(chalk.bold.blue('⚡ js-boost') + chalk.dim(' — initializing .ai/ folder'));
79
+ console.log('');
80
+
81
+ if (fs.existsSync(aiDir) && !force) {
82
+ console.log(chalk.yellow(' ⚠ .ai/ folder already exists. Use --force to overwrite.'));
83
+ console.log('');
84
+ return;
85
+ }
86
+
87
+ // Create .ai/guidelines/ with example files
88
+ for (const [filename, content] of Object.entries(EXAMPLE_GUIDELINES)) {
89
+ const filePath = path.join(aiDir, 'guidelines', filename);
90
+ if (!fs.existsSync(filePath) || force) {
91
+ writeFile(filePath, content);
92
+ console.log(` ${chalk.green('✓')} ${chalk.cyan(`.ai/guidelines/${filename}`)}`);
93
+ }
94
+ }
95
+
96
+ // Create .ai/skills/example-skill/ placeholder
97
+ for (const [filename, content] of Object.entries(EXAMPLE_SKILL)) {
98
+ const filePath = path.join(aiDir, 'skills', 'example-skill', filename);
99
+ if (!fs.existsSync(filePath) || force) {
100
+ writeFile(filePath, content);
101
+ console.log(` ${chalk.green('✓')} ${chalk.cyan(`.ai/skills/example-skill/${filename}`)}`);
102
+ }
103
+ }
104
+
105
+ // Create js-boost.config.json with defaults (or read existing)
106
+ const configPath = path.join(projectDir, 'js-boost.config.json');
107
+ let existingConfig = {};
108
+ if (fs.existsSync(configPath) && !force) {
109
+ try { existingConfig = JSON.parse(fs.readFileSync(configPath, 'utf8')); } catch {}
110
+ }
111
+
112
+ // Agent selection
113
+ const selectedAgents = await selectAgents(projectDir, existingConfig.agents);
114
+
115
+ const config = {
116
+ projectName: existingConfig.projectName || path.basename(projectDir),
117
+ projectDescription: existingConfig.projectDescription || '',
118
+ agents: selectedAgents,
119
+ mcpServers: existingConfig.mcpServers || {},
120
+ disableMcpServers: existingConfig.disableMcpServers || [],
121
+ };
122
+
123
+ writeFile(configPath, JSON.stringify(config, null, 2));
124
+ console.log(` ${chalk.green('✓')} ${chalk.cyan('js-boost.config.json')}`);
125
+
126
+ console.log('');
127
+ console.log(chalk.green.bold(' ✓ .ai/ folder initialized'));
128
+ console.log('');
129
+ console.log(chalk.dim(' Next steps:'));
130
+ console.log(chalk.dim(' 1. Edit .ai/guidelines/*.md with your project conventions'));
131
+ console.log(chalk.dim(' 2. Add skills in .ai/skills/<name>/SKILL.md'));
132
+ console.log(chalk.dim(' 3. Run: npx js-boost generate'));
133
+ console.log('');
134
+ }
@@ -0,0 +1,58 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import os from 'os';
4
+ import { execSync } from 'child_process';
5
+
6
+ function commandExists(cmd) {
7
+ try {
8
+ execSync(`command -v ${cmd}`, { stdio: 'ignore' });
9
+ return true;
10
+ } catch {
11
+ return false;
12
+ }
13
+ }
14
+
15
+ function dirExists(...segments) {
16
+ return fs.existsSync(path.join(...segments));
17
+ }
18
+
19
+ /**
20
+ * Detect which agents are installed on the system or present in the project.
21
+ * Returns an array of agent keys.
22
+ */
23
+ export function detectInstalledAgents(projectDir = process.cwd()) {
24
+ const home = os.homedir();
25
+ const detected = [];
26
+
27
+ if (dirExists(home, '.claude') || dirExists(home, '.config', 'claude')) {
28
+ detected.push('claude_code');
29
+ }
30
+
31
+ if (dirExists(home, '.cursor') || dirExists(projectDir, '.cursor')) {
32
+ detected.push('cursor');
33
+ }
34
+
35
+ if (dirExists(projectDir, '.junie') || dirExists(home, '.junie')) {
36
+ detected.push('junie');
37
+ }
38
+
39
+ if (commandExists('codex')) {
40
+ detected.push('codex');
41
+ }
42
+
43
+ if (commandExists('amp') || dirExists(home, '.amp')) {
44
+ detected.push('amp');
45
+ }
46
+
47
+ if (dirExists(home, '.kiro') || dirExists(projectDir, '.kiro')) {
48
+ detected.push('kiro');
49
+ }
50
+
51
+ if (commandExists('opencode') || dirExists(home, '.opencode')) {
52
+ detected.push('opencode');
53
+ }
54
+
55
+ // Copilot and Gemini are harder to detect — skip auto-detection
56
+
57
+ return detected;
58
+ }
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Default MCP servers pre-configured for js-boost
3
+ */
4
+ export const DEFAULT_MCP_SERVERS = {
5
+ };
6
+
7
+ /**
8
+ * Build the MCP servers object, merging defaults with user-defined servers
9
+ * from js-boost.config.json
10
+ */
11
+ export function buildMcpServers(config = {}) {
12
+ const userServers = config.mcpServers || {};
13
+ const disabledDefaults = config.disableMcpServers || [];
14
+
15
+ const servers = {};
16
+
17
+ // Add default servers (unless disabled)
18
+ for (const [key, server] of Object.entries(DEFAULT_MCP_SERVERS)) {
19
+ if (!disabledDefaults.includes(key)) {
20
+ servers[key] = server;
21
+ }
22
+ }
23
+
24
+ // Merge user-defined servers (can override defaults)
25
+ for (const [key, server] of Object.entries(userServers)) {
26
+ servers[key] = server;
27
+ }
28
+
29
+ return servers;
30
+ }
31
+
32
+ /**
33
+ * Generate .mcp.json (used by Claude Code and Codex)
34
+ * Supports both stdio and remote (HTTP/SSE) server types
35
+ */
36
+ export function generateMcpJson(servers) {
37
+ const mcpServers = {};
38
+
39
+ for (const [key, server] of Object.entries(servers)) {
40
+ if (server.type === 'stdio') {
41
+ mcpServers[key] = {
42
+ command: server.command,
43
+ args: server.args || [],
44
+ ...(server.env ? { env: server.env } : {})
45
+ };
46
+ } else if (server.type === 'remote') {
47
+ // Claude Code uses mcp-remote wrapper for HTTP/SSE servers
48
+ mcpServers[key] = {
49
+ command: 'npx',
50
+ args: ['-y', 'mcp-remote', server.url]
51
+ };
52
+ }
53
+ }
54
+
55
+ return JSON.stringify({ mcpServers }, null, 2);
56
+ }
57
+
58
+ /**
59
+ * Generate .junie/mcp.json (Junie format — supports URL directly)
60
+ */
61
+ export function generateJunieMcpJson(servers) {
62
+ const mcpServers = {};
63
+
64
+ for (const [key, server] of Object.entries(servers)) {
65
+ if (server.type === 'stdio') {
66
+ mcpServers[key] = {
67
+ command: server.command,
68
+ args: server.args || [],
69
+ ...(server.env ? { env: server.env } : {})
70
+ };
71
+ } else if (server.type === 'remote') {
72
+ mcpServers[key] = {
73
+ url: server.url
74
+ };
75
+ }
76
+ }
77
+
78
+ return JSON.stringify({ mcpServers }, null, 2);
79
+ }
80
+
81
+ /**
82
+ * Generate human-readable MCP section for markdown files
83
+ */
84
+ export function buildMcpMarkdownSection(servers) {
85
+ const lines = ['## MCP Servers', ''];
86
+ lines.push('The following MCP servers are configured for this project:\n');
87
+
88
+ for (const [key, server] of Object.entries(servers)) {
89
+ lines.push(`### ${key}`);
90
+ if (server.description) lines.push(`> ${server.description}`);
91
+ if (server.type === 'remote') lines.push(`- **URL:** \`${server.url}\``);
92
+ if (server.type === 'stdio') lines.push(`- **Command:** \`${server.command} ${(server.args || []).join(' ')}\``);
93
+ lines.push('');
94
+ }
95
+
96
+ return lines.join('\n');
97
+ }