@girardmedia/bootspring 2.5.6 → 2.5.8
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/dist/cli/index.js +17688 -5524
- package/dist/cli-launcher.js +51 -17
- package/dist/core/index.d.ts +153 -172
- package/dist/core.js +3735 -3463
- package/dist/mcp/index.d.ts +168 -1
- package/dist/mcp-server.js +1933 -1541
- package/package.json +1 -1
- package/scripts/postinstall.cjs +159 -2
package/package.json
CHANGED
package/scripts/postinstall.cjs
CHANGED
|
@@ -6,11 +6,15 @@ const path = require('path');
|
|
|
6
6
|
|
|
7
7
|
const c = {
|
|
8
8
|
reset: '\x1b[0m',
|
|
9
|
+
bold: '\x1b[1m',
|
|
9
10
|
green: '\x1b[32m',
|
|
11
|
+
yellow: '\x1b[33m',
|
|
10
12
|
cyan: '\x1b[36m',
|
|
11
13
|
dim: '\x1b[2m'
|
|
12
14
|
};
|
|
13
15
|
|
|
16
|
+
const HOME = os.homedir();
|
|
17
|
+
|
|
14
18
|
function resolveCommandsSource() {
|
|
15
19
|
const fromEnv = process.env.BOOTSPRING_COMMANDS_SOURCE;
|
|
16
20
|
const candidates = [
|
|
@@ -108,11 +112,127 @@ function installBootspringSkill(skillsDir) {
|
|
|
108
112
|
}
|
|
109
113
|
}
|
|
110
114
|
|
|
115
|
+
// ── MCP Server Registration ───────────────────────────────────────────
|
|
116
|
+
//
|
|
117
|
+
// Each assistant has its own MCP config file format. We register the
|
|
118
|
+
// `bootspring` MCP server so that bootspring_build, bootspring_context,
|
|
119
|
+
// bootspring_quality, etc. are exposed as tools in every project — no
|
|
120
|
+
// per-project .mcp.json required.
|
|
121
|
+
//
|
|
122
|
+
// All registrations are idempotent (skip if already configured) and
|
|
123
|
+
// failure-tolerant (silently skip on permission/IO errors so a broken
|
|
124
|
+
// assistant config never blocks `npm install`).
|
|
125
|
+
|
|
126
|
+
function registerClaudeMcp() {
|
|
127
|
+
// Claude Code v2.x reads user-scope MCP servers from ~/.claude.json
|
|
128
|
+
// (the file at home root, NOT inside the ~/.claude/ directory).
|
|
129
|
+
const configPath = path.join(HOME, '.claude.json');
|
|
130
|
+
try {
|
|
131
|
+
let config = {};
|
|
132
|
+
if (fs.existsSync(configPath)) {
|
|
133
|
+
const raw = fs.readFileSync(configPath, 'utf8');
|
|
134
|
+
config = raw.trim() ? JSON.parse(raw) : {};
|
|
135
|
+
}
|
|
136
|
+
if (!config.mcpServers || typeof config.mcpServers !== 'object') {
|
|
137
|
+
config.mcpServers = {};
|
|
138
|
+
}
|
|
139
|
+
// Skip if ANY bootspring entry exists — never clobber user customizations.
|
|
140
|
+
if (config.mcpServers.bootspring) {
|
|
141
|
+
return { name: 'Claude Code', status: 'unchanged', path: configPath };
|
|
142
|
+
}
|
|
143
|
+
config.mcpServers.bootspring = {
|
|
144
|
+
type: 'stdio',
|
|
145
|
+
command: 'bootspring',
|
|
146
|
+
args: ['mcp'],
|
|
147
|
+
env: { BOOTSPRING_ASSISTANT: 'claude' }
|
|
148
|
+
};
|
|
149
|
+
fs.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`, 'utf8');
|
|
150
|
+
return { name: 'Claude Code', status: 'updated', path: configPath };
|
|
151
|
+
} catch (err) {
|
|
152
|
+
return { name: 'Claude Code', status: 'skipped', path: configPath, reason: err.message };
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function registerCodexMcp() {
|
|
157
|
+
// Codex reads MCP servers from ~/.codex/config.toml under [mcp_servers.<name>]
|
|
158
|
+
const configPath = path.join(HOME, '.codex', 'config.toml');
|
|
159
|
+
try {
|
|
160
|
+
const dir = path.dirname(configPath);
|
|
161
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
162
|
+
|
|
163
|
+
const existing = fs.existsSync(configPath) ? fs.readFileSync(configPath, 'utf8') : '';
|
|
164
|
+
if (existing.includes('[mcp_servers.bootspring]')) {
|
|
165
|
+
return { name: 'Codex', status: 'unchanged', path: configPath };
|
|
166
|
+
}
|
|
167
|
+
const block = '[mcp_servers.bootspring]\ncommand = "bootspring"\nargs = ["mcp"]\nenv = { BOOTSPRING_ASSISTANT = "codex" }\n';
|
|
168
|
+
const content = existing.length > 0 ? `${existing.trimEnd()}\n\n${block}` : block;
|
|
169
|
+
fs.writeFileSync(configPath, content, 'utf8');
|
|
170
|
+
return { name: 'Codex', status: 'updated', path: configPath };
|
|
171
|
+
} catch (err) {
|
|
172
|
+
return { name: 'Codex', status: 'skipped', path: configPath, reason: err.message };
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function registerGeminiMcp() {
|
|
177
|
+
// Gemini CLI reads MCP servers from ~/.gemini/settings.json under "mcpServers"
|
|
178
|
+
const configPath = path.join(HOME, '.gemini', 'settings.json');
|
|
179
|
+
try {
|
|
180
|
+
const dir = path.dirname(configPath);
|
|
181
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
182
|
+
|
|
183
|
+
let settings = {};
|
|
184
|
+
if (fs.existsSync(configPath)) {
|
|
185
|
+
const raw = fs.readFileSync(configPath, 'utf8');
|
|
186
|
+
settings = raw.trim() ? JSON.parse(raw) : {};
|
|
187
|
+
}
|
|
188
|
+
if (!settings.mcpServers || typeof settings.mcpServers !== 'object') {
|
|
189
|
+
settings.mcpServers = {};
|
|
190
|
+
}
|
|
191
|
+
// Skip if ANY bootspring entry exists — never clobber user customizations.
|
|
192
|
+
if (settings.mcpServers.bootspring) {
|
|
193
|
+
return { name: 'Gemini CLI', status: 'unchanged', path: configPath };
|
|
194
|
+
}
|
|
195
|
+
settings.mcpServers.bootspring = {
|
|
196
|
+
command: 'bootspring',
|
|
197
|
+
args: ['mcp'],
|
|
198
|
+
env: { BOOTSPRING_ASSISTANT: 'gemini' }
|
|
199
|
+
};
|
|
200
|
+
fs.writeFileSync(configPath, `${JSON.stringify(settings, null, 2)}\n`, 'utf8');
|
|
201
|
+
return { name: 'Gemini CLI', status: 'updated', path: configPath };
|
|
202
|
+
} catch (err) {
|
|
203
|
+
return { name: 'Gemini CLI', status: 'skipped', path: configPath, reason: err.message };
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function registerAllMcp() {
|
|
208
|
+
return [registerClaudeMcp(), registerCodexMcp(), registerGeminiMcp()];
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function isFirstInstall() {
|
|
212
|
+
// Heuristic: a first install is one where Claude Code config does NOT yet
|
|
213
|
+
// have a bootspring entry. Updates to an existing install will already
|
|
214
|
+
// have one and shouldn't see the welcome screen.
|
|
215
|
+
try {
|
|
216
|
+
const claudeConfig = path.join(HOME, '.claude.json');
|
|
217
|
+
if (!fs.existsSync(claudeConfig)) return true;
|
|
218
|
+
const raw = fs.readFileSync(claudeConfig, 'utf8');
|
|
219
|
+
if (!raw.trim()) return true;
|
|
220
|
+
const parsed = JSON.parse(raw);
|
|
221
|
+
return !parsed?.mcpServers?.bootspring;
|
|
222
|
+
} catch {
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
111
227
|
function main() {
|
|
112
228
|
if (process.env.CI || process.env.BOOTSPRING_SKIP_POSTINSTALL) {
|
|
113
229
|
return;
|
|
114
230
|
}
|
|
115
231
|
|
|
232
|
+
// Capture this BEFORE we run any registration so the welcome message
|
|
233
|
+
// only fires the first time bootspring is installed on this machine.
|
|
234
|
+
const firstInstall = isFirstInstall();
|
|
235
|
+
|
|
116
236
|
let totalCommandsInstalled = 0;
|
|
117
237
|
let totalSkillsInstalled = 0;
|
|
118
238
|
const installedTo = [];
|
|
@@ -128,16 +248,53 @@ function main() {
|
|
|
128
248
|
}
|
|
129
249
|
}
|
|
130
250
|
|
|
131
|
-
|
|
251
|
+
// Register the Bootspring MCP server with each assistant so its tools
|
|
252
|
+
// (bootspring_build, bootspring_context, bootspring_quality, etc.) are
|
|
253
|
+
// available everywhere — no per-project .mcp.json needed.
|
|
254
|
+
const mcpResults = registerAllMcp();
|
|
255
|
+
const mcpUpdated = mcpResults.filter((r) => r.status === 'updated');
|
|
256
|
+
const mcpUnchanged = mcpResults.filter((r) => r.status === 'unchanged');
|
|
257
|
+
const mcpSkipped = mcpResults.filter((r) => r.status === 'skipped');
|
|
258
|
+
|
|
259
|
+
const didAnything =
|
|
260
|
+
totalCommandsInstalled > 0 ||
|
|
261
|
+
totalSkillsInstalled > 0 ||
|
|
262
|
+
mcpUpdated.length > 0;
|
|
263
|
+
|
|
264
|
+
if (didAnything) {
|
|
132
265
|
console.log(`
|
|
133
266
|
${c.cyan}⚡ Bootspring${c.reset} assistant integrations installed!
|
|
134
267
|
|
|
135
268
|
${c.dim}Installed:${c.reset}
|
|
136
269
|
${c.green}${totalCommandsInstalled}${c.reset} slash command templates
|
|
137
270
|
${c.green}${totalSkillsInstalled}${c.reset} Bootspring base skills
|
|
271
|
+
${c.green}${mcpUpdated.length}${c.reset} MCP server registrations${mcpUnchanged.length > 0 ? ` ${c.dim}(${mcpUnchanged.length} already configured)${c.reset}` : ''}
|
|
272
|
+
`);
|
|
273
|
+
|
|
274
|
+
if (mcpUpdated.length > 0) {
|
|
275
|
+
console.log(`${c.dim}MCP registered for:${c.reset} ${mcpUpdated.map((r) => r.name).join(', ')}`);
|
|
276
|
+
}
|
|
277
|
+
if (installedTo.length > 0) {
|
|
278
|
+
console.log(`${c.dim}Commands & skills installed to:${c.reset} ${installedTo.join(', ')}`);
|
|
279
|
+
}
|
|
280
|
+
if (mcpSkipped.length > 0) {
|
|
281
|
+
console.log(`${c.yellow}Note:${c.reset} ${c.dim}MCP registration skipped for ${mcpSkipped.map((r) => r.name).join(', ')} — run \`bootspring setup\` to retry.${c.reset}`);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (firstInstall) {
|
|
285
|
+
console.log(`
|
|
286
|
+
${c.bold}Next steps:${c.reset}
|
|
287
|
+
${c.cyan}1.${c.reset} Restart your assistant (Claude Code / Codex / Gemini) so MCP loads
|
|
288
|
+
${c.cyan}2.${c.reset} ${c.green}bootspring auth login${c.reset} ${c.dim}# authenticate (opens browser)${c.reset}
|
|
289
|
+
${c.cyan}3.${c.reset} ${c.green}cd${c.reset} into a project, then ${c.green}bootspring go${c.reset} ${c.dim}# one-shot setup${c.reset}
|
|
138
290
|
|
|
139
|
-
${c.dim}
|
|
291
|
+
${c.dim}Run ${c.reset}${c.green}bootspring${c.reset}${c.dim} with no args to see the welcome screen.${c.reset}
|
|
140
292
|
`);
|
|
293
|
+
} else {
|
|
294
|
+
console.log(`
|
|
295
|
+
${c.dim}Restart your assistant (Claude Code, Codex, or Gemini CLI) to load the bootspring MCP tools.${c.reset}
|
|
296
|
+
`);
|
|
297
|
+
}
|
|
141
298
|
}
|
|
142
299
|
}
|
|
143
300
|
|