@girardmedia/bootspring 1.1.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/LICENSE +21 -0
- package/README.md +255 -0
- package/agents/README.md +93 -0
- package/agents/api-expert/context.md +416 -0
- package/agents/architecture-expert/context.md +454 -0
- package/agents/backend-expert/context.md +483 -0
- package/agents/code-review-expert/context.md +365 -0
- package/agents/database-expert/context.md +250 -0
- package/agents/devops-expert/context.md +446 -0
- package/agents/frontend-expert/context.md +364 -0
- package/agents/index.js +140 -0
- package/agents/performance-expert/context.md +377 -0
- package/agents/security-expert/context.md +343 -0
- package/agents/testing-expert/context.md +414 -0
- package/agents/ui-ux-expert/context.md +448 -0
- package/agents/vercel-expert/context.md +426 -0
- package/bin/bootspring.js +310 -0
- package/cli/agent.js +337 -0
- package/cli/context.js +194 -0
- package/cli/dashboard.js +150 -0
- package/cli/generate.js +294 -0
- package/cli/init.js +410 -0
- package/cli/loop.js +421 -0
- package/cli/mcp.js +241 -0
- package/cli/memory.js +303 -0
- package/cli/orchestrator.js +400 -0
- package/cli/plugin.js +451 -0
- package/cli/quality.js +332 -0
- package/cli/skill.js +369 -0
- package/cli/task.js +628 -0
- package/cli/telemetry.js +114 -0
- package/cli/todo.js +614 -0
- package/cli/update.js +312 -0
- package/core/config.js +245 -0
- package/core/context.js +329 -0
- package/core/entitlements.js +209 -0
- package/core/index.js +43 -0
- package/core/policies.js +68 -0
- package/core/telemetry.js +247 -0
- package/core/utils.js +380 -0
- package/dashboard/server.js +818 -0
- package/docs/integrations/claude-code.md +42 -0
- package/docs/integrations/codex.md +42 -0
- package/docs/mcp-api-platform.md +102 -0
- package/generators/generate.js +598 -0
- package/generators/index.js +18 -0
- package/hooks/context-detector.js +177 -0
- package/hooks/index.js +35 -0
- package/hooks/prompt-enhancer.js +289 -0
- package/intelligence/git-memory.js +551 -0
- package/intelligence/index.js +59 -0
- package/intelligence/orchestrator.js +964 -0
- package/intelligence/prd.js +447 -0
- package/intelligence/recommendation-weights.json +18 -0
- package/intelligence/recommendations.js +234 -0
- package/mcp/capabilities.js +71 -0
- package/mcp/contracts/mcp-contract.v1.json +497 -0
- package/mcp/registry.js +213 -0
- package/mcp/response-formatter.js +462 -0
- package/mcp/server.js +99 -0
- package/mcp/tools/agent-tool.js +137 -0
- package/mcp/tools/capabilities-tool.js +54 -0
- package/mcp/tools/context-tool.js +49 -0
- package/mcp/tools/dashboard-tool.js +58 -0
- package/mcp/tools/generate-tool.js +46 -0
- package/mcp/tools/loop-tool.js +134 -0
- package/mcp/tools/memory-tool.js +180 -0
- package/mcp/tools/orchestrator-tool.js +232 -0
- package/mcp/tools/plugin-tool.js +76 -0
- package/mcp/tools/quality-tool.js +47 -0
- package/mcp/tools/skill-tool.js +233 -0
- package/mcp/tools/telemetry-tool.js +95 -0
- package/mcp/tools/todo-tool.js +133 -0
- package/package.json +98 -0
- package/plugins/index.js +141 -0
- package/quality/index.js +380 -0
- package/quality/lint-budgets.json +19 -0
- package/skills/index.js +787 -0
- package/skills/patterns/README.md +163 -0
- package/skills/patterns/api/route-handler.md +217 -0
- package/skills/patterns/api/server-action.md +249 -0
- package/skills/patterns/auth/clerk.md +132 -0
- package/skills/patterns/database/prisma.md +180 -0
- package/skills/patterns/payments/stripe.md +272 -0
- package/skills/patterns/security/validation.md +268 -0
- package/skills/patterns/testing/vitest.md +307 -0
- package/templates/bootspring.config.js +83 -0
- package/templates/mcp.json +9 -0
package/cli/quality.js
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bootspring Quality Command
|
|
3
|
+
* Run quality gates and checks
|
|
4
|
+
*
|
|
5
|
+
* @package bootspring
|
|
6
|
+
* @command quality
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const { execSync, spawn } = require('child_process');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const config = require('../core/config');
|
|
12
|
+
const utils = require('../core/utils');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Quality gate definitions
|
|
16
|
+
*/
|
|
17
|
+
const GATES = {
|
|
18
|
+
'pre-commit': {
|
|
19
|
+
name: 'Pre-Commit',
|
|
20
|
+
description: 'Quick checks before committing code',
|
|
21
|
+
checks: [
|
|
22
|
+
{
|
|
23
|
+
name: 'TypeScript',
|
|
24
|
+
command: 'npx tsc --noEmit',
|
|
25
|
+
description: 'Type checking'
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: 'ESLint',
|
|
29
|
+
command: 'npx eslint . --ext .ts,.tsx,.js,.jsx --max-warnings 0',
|
|
30
|
+
description: 'Linting'
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: 'Prettier',
|
|
34
|
+
command: 'npx prettier --check .',
|
|
35
|
+
description: 'Format checking'
|
|
36
|
+
}
|
|
37
|
+
]
|
|
38
|
+
},
|
|
39
|
+
'pre-push': {
|
|
40
|
+
name: 'Pre-Push',
|
|
41
|
+
description: 'Thorough checks before pushing to remote',
|
|
42
|
+
checks: [
|
|
43
|
+
{
|
|
44
|
+
name: 'TypeScript',
|
|
45
|
+
command: 'npx tsc --noEmit',
|
|
46
|
+
description: 'Type checking'
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: 'ESLint',
|
|
50
|
+
command: 'npx eslint . --ext .ts,.tsx,.js,.jsx',
|
|
51
|
+
description: 'Linting'
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: 'Unit Tests',
|
|
55
|
+
command: 'npm test',
|
|
56
|
+
description: 'Running tests'
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: 'Build',
|
|
60
|
+
command: 'npm run build',
|
|
61
|
+
description: 'Production build'
|
|
62
|
+
}
|
|
63
|
+
]
|
|
64
|
+
},
|
|
65
|
+
'pre-deploy': {
|
|
66
|
+
name: 'Pre-Deploy',
|
|
67
|
+
description: 'Full audit before deployment',
|
|
68
|
+
checks: [
|
|
69
|
+
{
|
|
70
|
+
name: 'TypeScript',
|
|
71
|
+
command: 'npx tsc --noEmit',
|
|
72
|
+
description: 'Type checking'
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
name: 'ESLint',
|
|
76
|
+
command: 'npx eslint . --ext .ts,.tsx,.js,.jsx',
|
|
77
|
+
description: 'Linting'
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
name: 'Unit Tests',
|
|
81
|
+
command: 'npm test',
|
|
82
|
+
description: 'Running tests'
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
name: 'Build',
|
|
86
|
+
command: 'npm run build',
|
|
87
|
+
description: 'Production build'
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
name: 'Security Audit',
|
|
91
|
+
command: 'npm audit --audit-level=high',
|
|
92
|
+
description: 'Checking for vulnerabilities'
|
|
93
|
+
}
|
|
94
|
+
]
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Run a single check
|
|
100
|
+
* @param {object} check - Check definition
|
|
101
|
+
* @param {string} projectRoot - Project root path
|
|
102
|
+
* @returns {object} Check result
|
|
103
|
+
*/
|
|
104
|
+
function runCheck(check, projectRoot) {
|
|
105
|
+
const spinner = utils.createSpinner(`${check.name}: ${check.description}`).start();
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
execSync(check.command, {
|
|
109
|
+
cwd: projectRoot,
|
|
110
|
+
stdio: 'pipe',
|
|
111
|
+
encoding: 'utf-8',
|
|
112
|
+
timeout: 300000 // 5 minute timeout
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
spinner.succeed(`${check.name}: passed`);
|
|
116
|
+
return { name: check.name, status: 'pass', message: 'Passed' };
|
|
117
|
+
} catch (error) {
|
|
118
|
+
const output = error.stdout || error.stderr || error.message;
|
|
119
|
+
spinner.fail(`${check.name}: failed`);
|
|
120
|
+
|
|
121
|
+
// Show truncated output
|
|
122
|
+
if (output) {
|
|
123
|
+
const lines = output.split('\n').slice(0, 10);
|
|
124
|
+
console.log(`${utils.COLORS.dim}${lines.join('\n')}${utils.COLORS.reset}`);
|
|
125
|
+
if (output.split('\n').length > 10) {
|
|
126
|
+
console.log(`${utils.COLORS.dim}... (truncated)${utils.COLORS.reset}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return { name: check.name, status: 'fail', message: output.slice(0, 500) };
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Run a quality gate
|
|
136
|
+
* @param {string} gateName - Gate name
|
|
137
|
+
* @param {object} options - Options
|
|
138
|
+
*/
|
|
139
|
+
async function runGate(gateName, options = {}) {
|
|
140
|
+
const gate = GATES[gateName];
|
|
141
|
+
|
|
142
|
+
if (!gate) {
|
|
143
|
+
utils.print.error(`Unknown gate: ${gateName}`);
|
|
144
|
+
utils.print.dim(`Available gates: ${Object.keys(GATES).join(', ')}`);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const cfg = config.load();
|
|
149
|
+
const projectRoot = cfg._projectRoot;
|
|
150
|
+
|
|
151
|
+
console.log(`
|
|
152
|
+
${utils.COLORS.cyan}${utils.COLORS.bold}⚡ ${gate.name} Quality Gate${utils.COLORS.reset}
|
|
153
|
+
${utils.COLORS.dim}${gate.description}${utils.COLORS.reset}
|
|
154
|
+
`);
|
|
155
|
+
|
|
156
|
+
const results = [];
|
|
157
|
+
let passed = 0;
|
|
158
|
+
let failed = 0;
|
|
159
|
+
|
|
160
|
+
for (const check of gate.checks) {
|
|
161
|
+
if (options.skip && options.skip.includes(check.name.toLowerCase())) {
|
|
162
|
+
console.log(`${utils.COLORS.yellow}⊘${utils.COLORS.reset} ${check.name}: skipped`);
|
|
163
|
+
results.push({ name: check.name, status: 'skip', message: 'Skipped' });
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const result = runCheck(check, projectRoot);
|
|
168
|
+
results.push(result);
|
|
169
|
+
|
|
170
|
+
if (result.status === 'pass') {
|
|
171
|
+
passed++;
|
|
172
|
+
} else {
|
|
173
|
+
failed++;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Stop on first failure if strict mode
|
|
177
|
+
if (options.strict && result.status === 'fail') {
|
|
178
|
+
console.log(`\n${utils.COLORS.red}Stopping on first failure (strict mode)${utils.COLORS.reset}`);
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Summary
|
|
184
|
+
console.log();
|
|
185
|
+
if (failed === 0) {
|
|
186
|
+
console.log(`${utils.COLORS.green}${utils.COLORS.bold}✓ All checks passed!${utils.COLORS.reset}`);
|
|
187
|
+
console.log(`${utils.COLORS.dim}${passed} check(s) passed${utils.COLORS.reset}`);
|
|
188
|
+
} else {
|
|
189
|
+
console.log(`${utils.COLORS.red}${utils.COLORS.bold}✗ Quality gate failed${utils.COLORS.reset}`);
|
|
190
|
+
console.log(`${utils.COLORS.dim}${passed} passed, ${failed} failed${utils.COLORS.reset}`);
|
|
191
|
+
process.exitCode = 1;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return { gate: gateName, passed, failed, results };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Run quick lint check
|
|
199
|
+
*/
|
|
200
|
+
function runQuickCheck() {
|
|
201
|
+
const cfg = config.load();
|
|
202
|
+
const projectRoot = cfg._projectRoot;
|
|
203
|
+
|
|
204
|
+
console.log(`
|
|
205
|
+
${utils.COLORS.cyan}${utils.COLORS.bold}⚡ Quick Quality Check${utils.COLORS.reset}
|
|
206
|
+
`);
|
|
207
|
+
|
|
208
|
+
// Just run TypeScript check
|
|
209
|
+
const spinner = utils.createSpinner('Type checking...').start();
|
|
210
|
+
|
|
211
|
+
try {
|
|
212
|
+
execSync('npx tsc --noEmit', {
|
|
213
|
+
cwd: projectRoot,
|
|
214
|
+
stdio: 'pipe',
|
|
215
|
+
timeout: 60000
|
|
216
|
+
});
|
|
217
|
+
spinner.succeed('No type errors');
|
|
218
|
+
} catch (error) {
|
|
219
|
+
spinner.fail('Type errors found');
|
|
220
|
+
console.log(`${utils.COLORS.dim}Run 'npx tsc --noEmit' for details${utils.COLORS.reset}`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Show quality status
|
|
226
|
+
*/
|
|
227
|
+
function showStatus() {
|
|
228
|
+
const cfg = config.load();
|
|
229
|
+
|
|
230
|
+
console.log(`
|
|
231
|
+
${utils.COLORS.cyan}${utils.COLORS.bold}⚡ Quality Gate Configuration${utils.COLORS.reset}
|
|
232
|
+
${utils.COLORS.dim}Project: ${cfg._projectRoot}${utils.COLORS.reset}
|
|
233
|
+
|
|
234
|
+
${utils.COLORS.bold}Available Gates${utils.COLORS.reset}
|
|
235
|
+
`);
|
|
236
|
+
|
|
237
|
+
for (const [id, gate] of Object.entries(GATES)) {
|
|
238
|
+
console.log(` ${utils.COLORS.cyan}${id}${utils.COLORS.reset}`);
|
|
239
|
+
console.log(` ${gate.description}`);
|
|
240
|
+
console.log(` ${utils.COLORS.dim}Checks: ${gate.checks.map(c => c.name).join(', ')}${utils.COLORS.reset}`);
|
|
241
|
+
console.log();
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const userConfig = cfg.quality || {};
|
|
245
|
+
console.log(`${utils.COLORS.bold}Project Settings${utils.COLORS.reset}`);
|
|
246
|
+
console.log(` Pre-commit: ${userConfig.preCommit !== false ? 'enabled' : 'disabled'}`);
|
|
247
|
+
console.log(` Pre-push: ${userConfig.prePush ? 'enabled' : 'disabled'}`);
|
|
248
|
+
console.log(` Strict mode: ${userConfig.strictMode ? 'enabled' : 'disabled'}`);
|
|
249
|
+
console.log();
|
|
250
|
+
|
|
251
|
+
console.log(`${utils.COLORS.bold}Git Hooks${utils.COLORS.reset}`);
|
|
252
|
+
console.log(` ${utils.COLORS.dim}To enable git hooks, use husky or lefthook:${utils.COLORS.reset}`);
|
|
253
|
+
console.log(` ${utils.COLORS.dim}npx husky add .husky/pre-commit "bootspring quality pre-commit"${utils.COLORS.reset}`);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Show quality help
|
|
258
|
+
*/
|
|
259
|
+
function showHelp() {
|
|
260
|
+
console.log(`
|
|
261
|
+
${utils.COLORS.cyan}${utils.COLORS.bold}⚡ Bootspring Quality${utils.COLORS.reset}
|
|
262
|
+
${utils.COLORS.dim}Run quality gates and checks${utils.COLORS.reset}
|
|
263
|
+
|
|
264
|
+
${utils.COLORS.bold}Usage:${utils.COLORS.reset}
|
|
265
|
+
bootspring quality <gate> [options]
|
|
266
|
+
|
|
267
|
+
${utils.COLORS.bold}Gates:${utils.COLORS.reset}
|
|
268
|
+
${utils.COLORS.cyan}pre-commit${utils.COLORS.reset} Quick checks (types, lint, format)
|
|
269
|
+
${utils.COLORS.cyan}pre-push${utils.COLORS.reset} Thorough checks (tests, build)
|
|
270
|
+
${utils.COLORS.cyan}pre-deploy${utils.COLORS.reset} Full audit (security, everything)
|
|
271
|
+
|
|
272
|
+
${utils.COLORS.bold}Options:${utils.COLORS.reset}
|
|
273
|
+
--strict Stop on first failure
|
|
274
|
+
--skip <check> Skip specific check
|
|
275
|
+
|
|
276
|
+
${utils.COLORS.bold}Commands:${utils.COLORS.reset}
|
|
277
|
+
${utils.COLORS.cyan}quick${utils.COLORS.reset} Run quick type check only
|
|
278
|
+
${utils.COLORS.cyan}status${utils.COLORS.reset} Show quality configuration
|
|
279
|
+
|
|
280
|
+
${utils.COLORS.bold}Examples:${utils.COLORS.reset}
|
|
281
|
+
bootspring quality pre-commit
|
|
282
|
+
bootspring quality pre-push --strict
|
|
283
|
+
bootspring quality pre-deploy --skip security
|
|
284
|
+
bootspring quality quick
|
|
285
|
+
bootspring quality status
|
|
286
|
+
`);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Run quality command
|
|
291
|
+
*/
|
|
292
|
+
async function run(args) {
|
|
293
|
+
const parsedArgs = utils.parseArgs(args);
|
|
294
|
+
const subcommand = parsedArgs._[0];
|
|
295
|
+
|
|
296
|
+
if (!subcommand) {
|
|
297
|
+
showHelp();
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
switch (subcommand) {
|
|
302
|
+
case 'pre-commit':
|
|
303
|
+
case 'pre-push':
|
|
304
|
+
case 'pre-deploy':
|
|
305
|
+
await runGate(subcommand, {
|
|
306
|
+
strict: parsedArgs.strict,
|
|
307
|
+
skip: parsedArgs.skip ? [parsedArgs.skip] : []
|
|
308
|
+
});
|
|
309
|
+
break;
|
|
310
|
+
|
|
311
|
+
case 'quick':
|
|
312
|
+
case 'check':
|
|
313
|
+
runQuickCheck();
|
|
314
|
+
break;
|
|
315
|
+
|
|
316
|
+
case 'status':
|
|
317
|
+
showStatus();
|
|
318
|
+
break;
|
|
319
|
+
|
|
320
|
+
case 'help':
|
|
321
|
+
case '-h':
|
|
322
|
+
case '--help':
|
|
323
|
+
showHelp();
|
|
324
|
+
break;
|
|
325
|
+
|
|
326
|
+
default:
|
|
327
|
+
utils.print.error(`Unknown gate: ${subcommand}`);
|
|
328
|
+
showHelp();
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
module.exports = { run, GATES, runGate, runCheck };
|
package/cli/skill.js
ADDED
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bootspring Skill Command
|
|
3
|
+
* Browse and use skill patterns.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const utils = require('../core/utils');
|
|
7
|
+
const entitlements = require('../core/entitlements');
|
|
8
|
+
const telemetry = require('../core/telemetry');
|
|
9
|
+
const skills = require('../skills');
|
|
10
|
+
|
|
11
|
+
function trackTelemetry(event, payload) {
|
|
12
|
+
try {
|
|
13
|
+
telemetry.emitEvent(event, payload);
|
|
14
|
+
} catch {
|
|
15
|
+
// Do not block CLI flow on telemetry issues.
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function printBuiltInSkills() {
|
|
20
|
+
const categories = skills.getCategories();
|
|
21
|
+
for (const category of categories) {
|
|
22
|
+
const patterns = skills.getPatternsByCategory(category);
|
|
23
|
+
if (patterns.length === 0) continue;
|
|
24
|
+
|
|
25
|
+
console.log(`${utils.COLORS.bold}${category}${utils.COLORS.reset}`);
|
|
26
|
+
for (const pattern of patterns) {
|
|
27
|
+
const id = `${category}/${pattern}`;
|
|
28
|
+
const metadata = skills.getSkillMetadata(id);
|
|
29
|
+
const description = metadata?.description ? ` - ${metadata.description}` : '';
|
|
30
|
+
console.log(` ${utils.COLORS.cyan}${id}${utils.COLORS.reset}${description}`);
|
|
31
|
+
}
|
|
32
|
+
console.log('');
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function printExternalSkills(options = {}) {
|
|
37
|
+
const {
|
|
38
|
+
limit = 20,
|
|
39
|
+
accessOptions = {}
|
|
40
|
+
} = options;
|
|
41
|
+
|
|
42
|
+
if (!skills.hasExternalSkillLibrary()) {
|
|
43
|
+
console.log(`${utils.COLORS.dim}No external skills catalog found${utils.COLORS.reset}\n`);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const externalIds = skills.listExternalSkills();
|
|
48
|
+
const { allowed: accessibleIds, denied } = entitlements.filterAccessibleSkills(externalIds, accessOptions);
|
|
49
|
+
const shown = accessibleIds.slice(0, limit);
|
|
50
|
+
|
|
51
|
+
if (shown.length === 0) {
|
|
52
|
+
console.log(`${utils.COLORS.bold}external${utils.COLORS.reset}`);
|
|
53
|
+
console.log(` ${utils.COLORS.dim}No external skills available for current entitlement${utils.COLORS.reset}\n`);
|
|
54
|
+
if (denied.length > 0) {
|
|
55
|
+
utils.print.dim(denied[0].reason);
|
|
56
|
+
}
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
console.log(`${utils.COLORS.bold}external${utils.COLORS.reset}`);
|
|
61
|
+
for (const id of shown) {
|
|
62
|
+
const metadata = skills.getSkillMetadata(id);
|
|
63
|
+
const description = metadata?.description ? ` - ${metadata.description}` : '';
|
|
64
|
+
console.log(` ${utils.COLORS.yellow}${id}${utils.COLORS.reset}${description}`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (accessibleIds.length > shown.length) {
|
|
68
|
+
const remaining = accessibleIds.length - shown.length;
|
|
69
|
+
console.log(`\n${utils.COLORS.dim}${remaining} more external skills hidden.${utils.COLORS.reset}`);
|
|
70
|
+
console.log(`${utils.COLORS.dim}Use --limit <n> or search with --external.${utils.COLORS.reset}`);
|
|
71
|
+
}
|
|
72
|
+
if (denied.length > 0) {
|
|
73
|
+
console.log(`\n${utils.COLORS.dim}${denied.length} external skills locked by entitlement policy${utils.COLORS.reset}`);
|
|
74
|
+
}
|
|
75
|
+
console.log('');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function listSkills(options = {}) {
|
|
79
|
+
const {
|
|
80
|
+
includeExternal = false,
|
|
81
|
+
externalOnly = false,
|
|
82
|
+
limit = 20,
|
|
83
|
+
accessOptions = {}
|
|
84
|
+
} = options;
|
|
85
|
+
|
|
86
|
+
console.log(`
|
|
87
|
+
${utils.COLORS.cyan}${utils.COLORS.bold}⚡ Bootspring Skills${utils.COLORS.reset}
|
|
88
|
+
${utils.COLORS.dim}Built-in patterns + optional external skill catalog${utils.COLORS.reset}
|
|
89
|
+
`);
|
|
90
|
+
|
|
91
|
+
if (!externalOnly) {
|
|
92
|
+
printBuiltInSkills();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (includeExternal || externalOnly) {
|
|
96
|
+
printExternalSkills({ limit, accessOptions });
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const builtInCount = skills.listSkills().length;
|
|
100
|
+
const externalCount = skills.hasExternalSkillLibrary() ? skills.listExternalSkills().length : 0;
|
|
101
|
+
console.log(`${utils.COLORS.dim}${builtInCount} built-in skills available${utils.COLORS.reset}`);
|
|
102
|
+
if (skills.hasExternalSkillLibrary()) {
|
|
103
|
+
console.log(`${utils.COLORS.dim}${externalCount} external skills indexed${utils.COLORS.reset}`);
|
|
104
|
+
}
|
|
105
|
+
console.log(`${utils.COLORS.dim}Use "bootspring skill show <id>" to view a skill${utils.COLORS.reset}`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function showSkill(skillId, options = {}) {
|
|
109
|
+
const {
|
|
110
|
+
includeExternal = false,
|
|
111
|
+
summary = false,
|
|
112
|
+
sections,
|
|
113
|
+
maxChars,
|
|
114
|
+
accessOptions = {}
|
|
115
|
+
} = options;
|
|
116
|
+
|
|
117
|
+
const metadata = skills.getSkillMetadata(skillId);
|
|
118
|
+
const resolvedSkillId = metadata?.id || skillId;
|
|
119
|
+
const access = entitlements.checkSkillAccess(resolvedSkillId, accessOptions);
|
|
120
|
+
|
|
121
|
+
if (!access.allowed) {
|
|
122
|
+
trackTelemetry('premium_prompted', {
|
|
123
|
+
capability: 'external_skill',
|
|
124
|
+
skillId: resolvedSkillId,
|
|
125
|
+
mode: access.context?.mode,
|
|
126
|
+
tier: access.context?.tier,
|
|
127
|
+
reason: access.code
|
|
128
|
+
});
|
|
129
|
+
utils.print.error(access.reason);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const rawContent = skills.loadSkill(skillId, { includeExternal });
|
|
134
|
+
|
|
135
|
+
if (!rawContent) {
|
|
136
|
+
utils.print.error(`Skill not found: ${skillId}`);
|
|
137
|
+
utils.print.dim('Try: bootspring skill list');
|
|
138
|
+
utils.print.dim('Or: bootspring skill search <query> --external');
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const content = skills.formatSkillContent(rawContent, {
|
|
143
|
+
summary,
|
|
144
|
+
sections,
|
|
145
|
+
maxChars
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
const headerName = metadata?.name || skillId;
|
|
149
|
+
const source = metadata?.source || 'built-in';
|
|
150
|
+
if (String(source).startsWith('external')) {
|
|
151
|
+
trackTelemetry('premium_unlocked', {
|
|
152
|
+
capability: 'external_skill',
|
|
153
|
+
skillId: metadata?.id || skillId,
|
|
154
|
+
mode: access.context?.mode,
|
|
155
|
+
tier: access.context?.tier,
|
|
156
|
+
reason: access.code
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
console.log(`
|
|
161
|
+
${utils.COLORS.cyan}${utils.COLORS.bold}${headerName}${utils.COLORS.reset}
|
|
162
|
+
${utils.COLORS.dim}ID: ${metadata?.id || skillId} | Source: ${source}${utils.COLORS.reset}
|
|
163
|
+
`);
|
|
164
|
+
|
|
165
|
+
console.log(content);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function searchSkills(query, options = {}) {
|
|
169
|
+
const {
|
|
170
|
+
includeExternal = false,
|
|
171
|
+
externalOnly = false,
|
|
172
|
+
limit = 20,
|
|
173
|
+
accessOptions = {}
|
|
174
|
+
} = options;
|
|
175
|
+
|
|
176
|
+
let results = skills.searchSkills(query, {
|
|
177
|
+
includeExternal: includeExternal || externalOnly,
|
|
178
|
+
limit
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
if (externalOnly) {
|
|
182
|
+
results = results.filter(id => id.startsWith('external/'));
|
|
183
|
+
}
|
|
184
|
+
const filtered = entitlements.filterAccessibleSkills(results, accessOptions);
|
|
185
|
+
results = filtered.allowed;
|
|
186
|
+
|
|
187
|
+
console.log(`
|
|
188
|
+
${utils.COLORS.cyan}${utils.COLORS.bold}Search Results${utils.COLORS.reset}
|
|
189
|
+
${utils.COLORS.dim}Query: "${query}"${utils.COLORS.reset}
|
|
190
|
+
`);
|
|
191
|
+
|
|
192
|
+
if (results.length === 0) {
|
|
193
|
+
utils.print.warning('No matching skills found');
|
|
194
|
+
utils.print.dim('Try: bootspring skill list');
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
for (const id of results) {
|
|
199
|
+
const metadata = skills.getSkillMetadata(id);
|
|
200
|
+
const source = String(metadata?.source || '').startsWith('external') ? 'external' : 'built-in';
|
|
201
|
+
const description = metadata?.description ? ` - ${metadata.description}` : '';
|
|
202
|
+
const color = source === 'external' ? utils.COLORS.yellow : utils.COLORS.cyan;
|
|
203
|
+
console.log(` ${color}${id}${utils.COLORS.reset} ${utils.COLORS.dim}[${source}]${utils.COLORS.reset}${description}`);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
console.log(`\n${utils.COLORS.dim}${results.length} match(es)${utils.COLORS.reset}`);
|
|
207
|
+
if (filtered.denied.length > 0) {
|
|
208
|
+
console.log(`${utils.COLORS.dim}${filtered.denied.length} result(s) hidden by entitlement policy${utils.COLORS.reset}`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async function syncCatalog(options = {}) {
|
|
213
|
+
const {
|
|
214
|
+
manifestUrl,
|
|
215
|
+
contentBaseUrl,
|
|
216
|
+
token,
|
|
217
|
+
timeoutMs,
|
|
218
|
+
cacheDir,
|
|
219
|
+
manifestPublicKey,
|
|
220
|
+
requireManifestSignature
|
|
221
|
+
} = options;
|
|
222
|
+
|
|
223
|
+
const spinner = utils.createSpinner('Syncing external skill catalog').start();
|
|
224
|
+
try {
|
|
225
|
+
const result = await skills.syncExternalCatalog({
|
|
226
|
+
manifestUrl,
|
|
227
|
+
contentBaseUrl,
|
|
228
|
+
token,
|
|
229
|
+
timeoutMs,
|
|
230
|
+
cacheDir,
|
|
231
|
+
manifestPublicKey,
|
|
232
|
+
requireManifestSignature
|
|
233
|
+
});
|
|
234
|
+
spinner.succeed(`Synced ${result.fetched} external skills`);
|
|
235
|
+
if (result.skipped.length > 0) {
|
|
236
|
+
utils.print.warning(`Skipped ${result.skipped.length} skill(s): ${result.skipped.join(', ')}`);
|
|
237
|
+
}
|
|
238
|
+
utils.print.dim(`Cache: ${result.manifestPath}`);
|
|
239
|
+
} catch (error) {
|
|
240
|
+
spinner.fail(`Sync failed: ${error.message}`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function showHelp() {
|
|
245
|
+
console.log(`
|
|
246
|
+
${utils.COLORS.cyan}${utils.COLORS.bold}Bootspring Skill Command${utils.COLORS.reset}
|
|
247
|
+
|
|
248
|
+
${utils.COLORS.cyan}Usage:${utils.COLORS.reset}
|
|
249
|
+
bootspring skill <command> [args] [options]
|
|
250
|
+
|
|
251
|
+
${utils.COLORS.cyan}Commands:${utils.COLORS.reset}
|
|
252
|
+
${utils.COLORS.cyan}list${utils.COLORS.reset} List built-in skills
|
|
253
|
+
${utils.COLORS.cyan}show${utils.COLORS.reset} <id> Show skill content
|
|
254
|
+
${utils.COLORS.cyan}search${utils.COLORS.reset} <query> Search skills
|
|
255
|
+
${utils.COLORS.cyan}sync${utils.COLORS.reset} Sync external catalog from remote manifest
|
|
256
|
+
|
|
257
|
+
${utils.COLORS.cyan}Options:${utils.COLORS.reset}
|
|
258
|
+
${utils.COLORS.cyan}--external${utils.COLORS.reset} Include external skills catalog
|
|
259
|
+
${utils.COLORS.cyan}--external-only${utils.COLORS.reset} External skills only
|
|
260
|
+
${utils.COLORS.cyan}--limit <n>${utils.COLORS.reset} Limit results (default: 20)
|
|
261
|
+
${utils.COLORS.cyan}--summary${utils.COLORS.reset} Show concise summary for skill show
|
|
262
|
+
${utils.COLORS.cyan}--sections a,b${utils.COLORS.reset} Show matching sections for skill show
|
|
263
|
+
${utils.COLORS.cyan}--max-chars <n>${utils.COLORS.reset} Cap output size for skill show
|
|
264
|
+
${utils.COLORS.cyan}--access-mode <mode>${utils.COLORS.reset} Access mode: local (default) or server
|
|
265
|
+
${utils.COLORS.cyan}--entitled <bool>${utils.COLORS.reset} Entitlement override for external skills
|
|
266
|
+
${utils.COLORS.cyan}--tier <tier>${utils.COLORS.reset} Tier override: free, pro, team, enterprise
|
|
267
|
+
${utils.COLORS.cyan}--manifest-url <url>${utils.COLORS.reset} Remote manifest URL for skill sync
|
|
268
|
+
${utils.COLORS.cyan}--content-base-url <url>${utils.COLORS.reset} Base URL for SKILL.md files when missing contentUrl
|
|
269
|
+
${utils.COLORS.cyan}--token <value>${utils.COLORS.reset} Bearer token for remote catalog APIs
|
|
270
|
+
${utils.COLORS.cyan}--cache-dir <path>${utils.COLORS.reset} Override local skill cache directory
|
|
271
|
+
${utils.COLORS.cyan}--manifest-public-key <pem>${utils.COLORS.reset} Public key for manifest signature verification
|
|
272
|
+
${utils.COLORS.cyan}--require-signature <bool>${utils.COLORS.reset} Require remote manifest signature validation
|
|
273
|
+
|
|
274
|
+
${utils.COLORS.cyan}Examples:${utils.COLORS.reset}
|
|
275
|
+
bootspring skill list
|
|
276
|
+
bootspring skill list --external
|
|
277
|
+
bootspring skill search auth
|
|
278
|
+
bootspring skill search vercel --external
|
|
279
|
+
bootspring skill show auth/clerk
|
|
280
|
+
bootspring skill show external/vercel-automation
|
|
281
|
+
bootspring skill sync --manifest-url https://api.bootspring.com/skills/manifest.json
|
|
282
|
+
`);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Run skill command
|
|
287
|
+
*/
|
|
288
|
+
async function run(args) {
|
|
289
|
+
const parsedArgs = utils.parseArgs(args);
|
|
290
|
+
const subcommand = parsedArgs._[0] || 'list';
|
|
291
|
+
const subargs = parsedArgs._.slice(1);
|
|
292
|
+
|
|
293
|
+
const externalOnly = Boolean(parsedArgs['external-only']);
|
|
294
|
+
const includeExternal = externalOnly || Boolean(parsedArgs.external);
|
|
295
|
+
const parsedLimit = parsedArgs.limit ? Number(parsedArgs.limit) : 20;
|
|
296
|
+
const limit = Number.isFinite(parsedLimit) && parsedLimit > 0 ? parsedLimit : 20;
|
|
297
|
+
const maxChars = parsedArgs['max-chars'] ? Number(parsedArgs['max-chars']) : undefined;
|
|
298
|
+
const accessOptions = {
|
|
299
|
+
mode: parsedArgs['access-mode'],
|
|
300
|
+
entitled: parsedArgs.entitled,
|
|
301
|
+
tier: parsedArgs.tier
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
switch (subcommand) {
|
|
305
|
+
case 'list':
|
|
306
|
+
listSkills({ includeExternal, externalOnly, limit, accessOptions });
|
|
307
|
+
break;
|
|
308
|
+
|
|
309
|
+
case 'show':
|
|
310
|
+
if (!subargs[0]) {
|
|
311
|
+
utils.print.error('Please specify a skill ID');
|
|
312
|
+
utils.print.dim('Usage: bootspring skill show <id>');
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
showSkill(subargs[0], {
|
|
316
|
+
includeExternal: true,
|
|
317
|
+
summary: Boolean(parsedArgs.summary),
|
|
318
|
+
sections: parsedArgs.sections,
|
|
319
|
+
maxChars,
|
|
320
|
+
accessOptions
|
|
321
|
+
});
|
|
322
|
+
break;
|
|
323
|
+
|
|
324
|
+
case 'search':
|
|
325
|
+
if (!subargs[0]) {
|
|
326
|
+
utils.print.error('Please specify a search query');
|
|
327
|
+
utils.print.dim('Usage: bootspring skill search <query>');
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
searchSkills(subargs.join(' '), { includeExternal, externalOnly, limit, accessOptions });
|
|
331
|
+
break;
|
|
332
|
+
|
|
333
|
+
case 'sync':
|
|
334
|
+
await syncCatalog({
|
|
335
|
+
manifestUrl: parsedArgs['manifest-url'],
|
|
336
|
+
contentBaseUrl: parsedArgs['content-base-url'],
|
|
337
|
+
token: parsedArgs.token,
|
|
338
|
+
timeoutMs: parsedArgs.timeout,
|
|
339
|
+
cacheDir: parsedArgs['cache-dir'],
|
|
340
|
+
manifestPublicKey: parsedArgs['manifest-public-key'],
|
|
341
|
+
requireManifestSignature: parsedArgs['require-signature']
|
|
342
|
+
});
|
|
343
|
+
break;
|
|
344
|
+
|
|
345
|
+
case 'help':
|
|
346
|
+
case '-h':
|
|
347
|
+
case '--help':
|
|
348
|
+
showHelp();
|
|
349
|
+
break;
|
|
350
|
+
|
|
351
|
+
default:
|
|
352
|
+
// Shortcut: `bootspring skill auth/clerk`
|
|
353
|
+
showSkill(subcommand, {
|
|
354
|
+
includeExternal: true,
|
|
355
|
+
summary: Boolean(parsedArgs.summary),
|
|
356
|
+
sections: parsedArgs.sections,
|
|
357
|
+
maxChars,
|
|
358
|
+
accessOptions
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
module.exports = {
|
|
364
|
+
run,
|
|
365
|
+
listSkills,
|
|
366
|
+
showSkill,
|
|
367
|
+
searchSkills,
|
|
368
|
+
syncCatalog
|
|
369
|
+
};
|