@1tool/js-boost 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +200 -0
- package/bin/js-boost.js +2 -0
- package/package.json +35 -0
- package/src/agents.js +62 -0
- package/src/cli.js +162 -0
- package/src/configure/mcp.js +223 -0
- package/src/generators/agents.js +64 -0
- package/src/generators/claude.js +60 -0
- package/src/generators/cursor.js +69 -0
- package/src/generators/junie.js +42 -0
- package/src/generators/kiro.js +35 -0
- package/src/index.js +125 -0
- package/src/init.js +134 -0
- package/src/utils/detect.js +58 -0
- package/src/utils/mcp.js +97 -0
- package/src/utils/reader.js +92 -0
- package/src/watch.js +58 -0
|
@@ -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
|
+
}
|
package/src/utils/mcp.js
ADDED
|
@@ -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
|
+
}
|