@girardmedia/bootspring 2.5.6 → 2.5.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@girardmedia/bootspring",
3
- "version": "2.5.6",
3
+ "version": "2.5.7",
4
4
  "description": "Thin client for Bootspring cloud MCP, hosted agents, and paywalled workflow intelligence",
5
5
  "keywords": [
6
6
  "ai",
@@ -7,10 +7,13 @@ const path = require('path');
7
7
  const c = {
8
8
  reset: '\x1b[0m',
9
9
  green: '\x1b[32m',
10
+ yellow: '\x1b[33m',
10
11
  cyan: '\x1b[36m',
11
12
  dim: '\x1b[2m'
12
13
  };
13
14
 
15
+ const HOME = os.homedir();
16
+
14
17
  function resolveCommandsSource() {
15
18
  const fromEnv = process.env.BOOTSPRING_COMMANDS_SOURCE;
16
19
  const candidates = [
@@ -108,6 +111,102 @@ function installBootspringSkill(skillsDir) {
108
111
  }
109
112
  }
110
113
 
114
+ // ── MCP Server Registration ───────────────────────────────────────────
115
+ //
116
+ // Each assistant has its own MCP config file format. We register the
117
+ // `bootspring` MCP server so that bootspring_build, bootspring_context,
118
+ // bootspring_quality, etc. are exposed as tools in every project — no
119
+ // per-project .mcp.json required.
120
+ //
121
+ // All registrations are idempotent (skip if already configured) and
122
+ // failure-tolerant (silently skip on permission/IO errors so a broken
123
+ // assistant config never blocks `npm install`).
124
+
125
+ function registerClaudeMcp() {
126
+ // Claude Code v2.x reads user-scope MCP servers from ~/.claude.json
127
+ // (the file at home root, NOT inside the ~/.claude/ directory).
128
+ const configPath = path.join(HOME, '.claude.json');
129
+ try {
130
+ let config = {};
131
+ if (fs.existsSync(configPath)) {
132
+ const raw = fs.readFileSync(configPath, 'utf8');
133
+ config = raw.trim() ? JSON.parse(raw) : {};
134
+ }
135
+ if (!config.mcpServers || typeof config.mcpServers !== 'object') {
136
+ config.mcpServers = {};
137
+ }
138
+ // Skip if ANY bootspring entry exists — never clobber user customizations.
139
+ if (config.mcpServers.bootspring) {
140
+ return { name: 'Claude Code', status: 'unchanged', path: configPath };
141
+ }
142
+ config.mcpServers.bootspring = {
143
+ type: 'stdio',
144
+ command: 'bootspring',
145
+ args: ['mcp'],
146
+ env: { BOOTSPRING_ASSISTANT: 'claude' }
147
+ };
148
+ fs.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`, 'utf8');
149
+ return { name: 'Claude Code', status: 'updated', path: configPath };
150
+ } catch (err) {
151
+ return { name: 'Claude Code', status: 'skipped', path: configPath, reason: err.message };
152
+ }
153
+ }
154
+
155
+ function registerCodexMcp() {
156
+ // Codex reads MCP servers from ~/.codex/config.toml under [mcp_servers.<name>]
157
+ const configPath = path.join(HOME, '.codex', 'config.toml');
158
+ try {
159
+ const dir = path.dirname(configPath);
160
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
161
+
162
+ const existing = fs.existsSync(configPath) ? fs.readFileSync(configPath, 'utf8') : '';
163
+ if (existing.includes('[mcp_servers.bootspring]')) {
164
+ return { name: 'Codex', status: 'unchanged', path: configPath };
165
+ }
166
+ const block = '[mcp_servers.bootspring]\ncommand = "bootspring"\nargs = ["mcp"]\nenv = { BOOTSPRING_ASSISTANT = "codex" }\n';
167
+ const content = existing.length > 0 ? `${existing.trimEnd()}\n\n${block}` : block;
168
+ fs.writeFileSync(configPath, content, 'utf8');
169
+ return { name: 'Codex', status: 'updated', path: configPath };
170
+ } catch (err) {
171
+ return { name: 'Codex', status: 'skipped', path: configPath, reason: err.message };
172
+ }
173
+ }
174
+
175
+ function registerGeminiMcp() {
176
+ // Gemini CLI reads MCP servers from ~/.gemini/settings.json under "mcpServers"
177
+ const configPath = path.join(HOME, '.gemini', 'settings.json');
178
+ try {
179
+ const dir = path.dirname(configPath);
180
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
181
+
182
+ let settings = {};
183
+ if (fs.existsSync(configPath)) {
184
+ const raw = fs.readFileSync(configPath, 'utf8');
185
+ settings = raw.trim() ? JSON.parse(raw) : {};
186
+ }
187
+ if (!settings.mcpServers || typeof settings.mcpServers !== 'object') {
188
+ settings.mcpServers = {};
189
+ }
190
+ // Skip if ANY bootspring entry exists — never clobber user customizations.
191
+ if (settings.mcpServers.bootspring) {
192
+ return { name: 'Gemini CLI', status: 'unchanged', path: configPath };
193
+ }
194
+ settings.mcpServers.bootspring = {
195
+ command: 'bootspring',
196
+ args: ['mcp'],
197
+ env: { BOOTSPRING_ASSISTANT: 'gemini' }
198
+ };
199
+ fs.writeFileSync(configPath, `${JSON.stringify(settings, null, 2)}\n`, 'utf8');
200
+ return { name: 'Gemini CLI', status: 'updated', path: configPath };
201
+ } catch (err) {
202
+ return { name: 'Gemini CLI', status: 'skipped', path: configPath, reason: err.message };
203
+ }
204
+ }
205
+
206
+ function registerAllMcp() {
207
+ return [registerClaudeMcp(), registerCodexMcp(), registerGeminiMcp()];
208
+ }
209
+
111
210
  function main() {
112
211
  if (process.env.CI || process.env.BOOTSPRING_SKIP_POSTINSTALL) {
113
212
  return;
@@ -128,15 +227,41 @@ function main() {
128
227
  }
129
228
  }
130
229
 
131
- if (totalCommandsInstalled > 0 || totalSkillsInstalled > 0) {
230
+ // Register the Bootspring MCP server with each assistant so its tools
231
+ // (bootspring_build, bootspring_context, bootspring_quality, etc.) are
232
+ // available everywhere — no per-project .mcp.json needed.
233
+ const mcpResults = registerAllMcp();
234
+ const mcpUpdated = mcpResults.filter((r) => r.status === 'updated');
235
+ const mcpUnchanged = mcpResults.filter((r) => r.status === 'unchanged');
236
+ const mcpSkipped = mcpResults.filter((r) => r.status === 'skipped');
237
+
238
+ const didAnything =
239
+ totalCommandsInstalled > 0 ||
240
+ totalSkillsInstalled > 0 ||
241
+ mcpUpdated.length > 0;
242
+
243
+ if (didAnything) {
132
244
  console.log(`
133
245
  ${c.cyan}⚡ Bootspring${c.reset} assistant integrations installed!
134
246
 
135
247
  ${c.dim}Installed:${c.reset}
136
248
  ${c.green}${totalCommandsInstalled}${c.reset} slash command templates
137
249
  ${c.green}${totalSkillsInstalled}${c.reset} Bootspring base skills
250
+ ${c.green}${mcpUpdated.length}${c.reset} MCP server registrations${mcpUnchanged.length > 0 ? ` ${c.dim}(${mcpUnchanged.length} already configured)${c.reset}` : ''}
251
+ `);
252
+
253
+ if (mcpUpdated.length > 0) {
254
+ console.log(`${c.dim}MCP registered for:${c.reset} ${mcpUpdated.map((r) => r.name).join(', ')}`);
255
+ }
256
+ if (installedTo.length > 0) {
257
+ console.log(`${c.dim}Commands & skills installed to:${c.reset} ${installedTo.join(', ')}`);
258
+ }
259
+ if (mcpSkipped.length > 0) {
260
+ 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}`);
261
+ }
138
262
 
139
- ${c.dim}Installed to: ${installedTo.join(', ')}${c.reset}
263
+ console.log(`
264
+ ${c.dim}Restart your assistant (Claude Code, Codex, or Gemini CLI) to load the bootspring MCP tools.${c.reset}
140
265
  `);
141
266
  }
142
267
  }