@girardmedia/bootspring 1.2.0 → 2.0.3
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 +107 -14
- package/bin/bootspring.js +166 -27
- package/cli/agent.js +189 -17
- package/cli/analyze.js +499 -0
- package/cli/audit.js +557 -0
- package/cli/auth.js +495 -38
- package/cli/billing.js +302 -0
- package/cli/build.js +695 -0
- package/cli/business.js +109 -26
- package/cli/checkpoint-utils.js +168 -0
- package/cli/checkpoint.js +639 -0
- package/cli/cloud-sync.js +447 -0
- package/cli/content.js +198 -0
- package/cli/context.js +1 -1
- package/cli/deploy.js +543 -0
- package/cli/fundraise.js +112 -50
- package/cli/github-cmd.js +435 -0
- package/cli/health.js +477 -0
- package/cli/init.js +84 -13
- package/cli/legal.js +107 -95
- package/cli/log.js +2 -2
- package/cli/loop.js +976 -73
- package/cli/manager.js +711 -0
- package/cli/metrics.js +480 -0
- package/cli/monitor.js +812 -0
- package/cli/onboard.js +521 -0
- package/cli/orchestrator.js +12 -24
- package/cli/prd.js +594 -0
- package/cli/preseed-start.js +1483 -0
- package/cli/preseed.js +2302 -0
- package/cli/project.js +436 -0
- package/cli/quality.js +233 -0
- package/cli/security.js +913 -0
- package/cli/seed.js +1441 -5
- package/cli/skill.js +273 -211
- package/cli/suggest.js +989 -0
- package/cli/switch.js +453 -0
- package/cli/visualize.js +527 -0
- package/cli/watch.js +769 -0
- package/cli/workspace.js +607 -0
- package/core/analyze-workflow.js +1134 -0
- package/core/api-client.js +535 -22
- package/core/audit-workflow.js +1350 -0
- package/core/build-orchestrator.js +480 -0
- package/core/build-state.js +577 -0
- package/core/checkpoint-engine.js +408 -0
- package/core/config.js +1109 -26
- package/core/context-loader.js +21 -1
- package/core/deploy-workflow.js +836 -0
- package/core/entitlements.js +93 -22
- package/core/github-sync.js +610 -0
- package/core/index.js +8 -1
- package/core/ingest.js +1111 -0
- package/core/metrics-engine.js +768 -0
- package/core/onboard-workflow.js +1007 -0
- package/core/preseed-workflow.js +934 -0
- package/core/preseed.js +1617 -0
- package/core/project-context.js +325 -0
- package/core/project-state.js +694 -0
- package/core/r2-sync.js +583 -0
- package/core/scaffold.js +525 -7
- package/core/session.js +258 -0
- package/core/task-extractor.js +758 -0
- package/core/telemetry.js +28 -6
- package/core/tier-enforcement.js +737 -0
- package/core/utils.js +38 -14
- package/generators/questionnaire.js +15 -12
- package/generators/sections/ai.js +7 -7
- package/generators/sections/content.js +300 -0
- package/generators/sections/index.js +3 -0
- package/generators/sections/plugins.js +7 -6
- package/generators/templates/build-planning.template.js +596 -0
- package/generators/templates/content.template.js +819 -0
- package/generators/templates/index.js +2 -1
- package/hooks/git-autopilot.js +1250 -0
- package/hooks/index.js +9 -0
- package/intelligence/agent-collab.js +2057 -0
- package/intelligence/auto-suggest.js +634 -0
- package/intelligence/content-gen.js +1589 -0
- package/intelligence/cross-project.js +1647 -0
- package/intelligence/index.js +184 -0
- package/intelligence/learning/insights.json +517 -7
- package/intelligence/learning/pattern-learner.js +1008 -14
- package/intelligence/memory/decision-tracker.js +1431 -31
- package/intelligence/memory/decisions.jsonl +0 -0
- package/intelligence/orchestrator.js +2896 -1
- package/intelligence/prd.js +92 -1
- package/intelligence/recommendation-weights.json +14 -2
- package/intelligence/recommendations.js +463 -9
- package/intelligence/workflow-composer.js +1451 -0
- package/marketplace/index.d.ts +324 -0
- package/marketplace/index.js +1921 -0
- package/mcp/contracts/mcp-contract.v1.json +342 -4
- package/mcp/registry.js +680 -3
- package/mcp/response-formatter.js +23 -0
- package/mcp/tools/assist-tool.js +78 -4
- package/mcp/tools/autopilot-tool.js +408 -0
- package/mcp/tools/content-tool.js +571 -0
- package/mcp/tools/dashboard-tool.js +251 -5
- package/mcp/tools/mvp-tool.js +344 -0
- package/mcp/tools/plugin-tool.js +23 -1
- package/mcp/tools/prd-tool.js +579 -0
- package/mcp/tools/seed-tool.js +447 -0
- package/mcp/tools/skill-tool.js +43 -14
- package/mcp/tools/suggest-tool.js +147 -0
- package/package.json +15 -6
- package/agents/README.md +0 -93
- package/agents/ai-integration-expert/context.md +0 -386
- package/agents/api-expert/context.md +0 -416
- package/agents/architecture-expert/context.md +0 -454
- package/agents/auth-expert/context.md +0 -399
- package/agents/backend-expert/context.md +0 -483
- package/agents/business-strategy-expert/context.md +0 -180
- package/agents/code-review-expert/context.md +0 -365
- package/agents/competitive-analysis-expert/context.md +0 -239
- package/agents/data-modeling-expert/context.md +0 -352
- package/agents/database-expert/context.md +0 -250
- package/agents/devops-expert/context.md +0 -446
- package/agents/email-expert/context.md +0 -379
- package/agents/financial-expert/context.md +0 -213
- package/agents/frontend-expert/context.md +0 -364
- package/agents/fundraising-expert/context.md +0 -257
- package/agents/growth-expert/context.md +0 -249
- package/agents/index.js +0 -140
- package/agents/investor-relations-expert/context.md +0 -266
- package/agents/legal-expert/context.md +0 -284
- package/agents/marketing-expert/context.md +0 -236
- package/agents/monitoring-expert/context.md +0 -362
- package/agents/operations-expert/context.md +0 -279
- package/agents/partnerships-expert/context.md +0 -286
- package/agents/payment-expert/context.md +0 -340
- package/agents/performance-expert/context.md +0 -377
- package/agents/private-equity-expert/context.md +0 -246
- package/agents/railway-expert/context.md +0 -284
- package/agents/research-expert/context.md +0 -245
- package/agents/sales-expert/context.md +0 -241
- package/agents/security-expert/context.md +0 -343
- package/agents/testing-expert/context.md +0 -414
- package/agents/ui-ux-expert/context.md +0 -448
- package/agents/vercel-expert/context.md +0 -426
- package/skills/index.js +0 -787
- package/skills/patterns/README.md +0 -163
- package/skills/patterns/ai/agents.md +0 -281
- package/skills/patterns/ai/claude.md +0 -138
- package/skills/patterns/ai/embeddings.md +0 -150
- package/skills/patterns/ai/rag.md +0 -266
- package/skills/patterns/ai/streaming.md +0 -170
- package/skills/patterns/ai/structured-output.md +0 -162
- package/skills/patterns/ai/tools.md +0 -154
- package/skills/patterns/analytics/tracking.md +0 -220
- package/skills/patterns/api/errors.md +0 -296
- package/skills/patterns/api/graphql.md +0 -440
- package/skills/patterns/api/middleware.md +0 -279
- package/skills/patterns/api/openapi.md +0 -285
- package/skills/patterns/api/rate-limiting.md +0 -231
- package/skills/patterns/api/route-handler.md +0 -217
- package/skills/patterns/api/server-action.md +0 -249
- package/skills/patterns/api/versioning.md +0 -443
- package/skills/patterns/api/webhooks.md +0 -247
- package/skills/patterns/auth/clerk.md +0 -132
- package/skills/patterns/auth/mfa.md +0 -313
- package/skills/patterns/auth/nextauth.md +0 -140
- package/skills/patterns/auth/oauth.md +0 -237
- package/skills/patterns/auth/rbac.md +0 -152
- package/skills/patterns/auth/session-management.md +0 -367
- package/skills/patterns/auth/session.md +0 -120
- package/skills/patterns/database/audit.md +0 -177
- package/skills/patterns/database/migrations.md +0 -177
- package/skills/patterns/database/pagination.md +0 -230
- package/skills/patterns/database/pooling.md +0 -357
- package/skills/patterns/database/prisma.md +0 -180
- package/skills/patterns/database/relations.md +0 -187
- package/skills/patterns/database/seeding.md +0 -246
- package/skills/patterns/database/soft-delete.md +0 -153
- package/skills/patterns/database/transactions.md +0 -162
- package/skills/patterns/deployment/ci-cd.md +0 -231
- package/skills/patterns/deployment/docker.md +0 -188
- package/skills/patterns/deployment/monitoring.md +0 -387
- package/skills/patterns/deployment/vercel.md +0 -160
- package/skills/patterns/email/resend.md +0 -143
- package/skills/patterns/email/templates.md +0 -245
- package/skills/patterns/email/transactional.md +0 -503
- package/skills/patterns/email/verification.md +0 -176
- package/skills/patterns/files/download.md +0 -243
- package/skills/patterns/files/upload.md +0 -239
- package/skills/patterns/i18n/nextintl.md +0 -188
- package/skills/patterns/logging/structured.md +0 -292
- package/skills/patterns/notifications/email-queue.md +0 -248
- package/skills/patterns/notifications/push.md +0 -279
- package/skills/patterns/payments/checkout.md +0 -303
- package/skills/patterns/payments/invoices.md +0 -287
- package/skills/patterns/payments/portal.md +0 -245
- package/skills/patterns/payments/stripe.md +0 -272
- package/skills/patterns/payments/subscriptions.md +0 -300
- package/skills/patterns/payments/usage.md +0 -279
- package/skills/patterns/performance/caching.md +0 -276
- package/skills/patterns/performance/code-splitting.md +0 -233
- package/skills/patterns/performance/edge.md +0 -254
- package/skills/patterns/performance/isr.md +0 -266
- package/skills/patterns/performance/lazy-loading.md +0 -281
- package/skills/patterns/realtime/sse.md +0 -327
- package/skills/patterns/realtime/websockets.md +0 -336
- package/skills/patterns/search/filtering.md +0 -329
- package/skills/patterns/search/fulltext.md +0 -260
- package/skills/patterns/security/audit-logging.md +0 -444
- package/skills/patterns/security/csrf.md +0 -234
- package/skills/patterns/security/headers.md +0 -252
- package/skills/patterns/security/sanitization.md +0 -258
- package/skills/patterns/security/secrets.md +0 -261
- package/skills/patterns/security/validation.md +0 -268
- package/skills/patterns/security/xss.md +0 -229
- package/skills/patterns/seo/metadata.md +0 -252
- package/skills/patterns/state/context.md +0 -349
- package/skills/patterns/state/react-query.md +0 -313
- package/skills/patterns/state/url-state.md +0 -482
- package/skills/patterns/state/zustand.md +0 -262
- package/skills/patterns/testing/api.md +0 -259
- package/skills/patterns/testing/component.md +0 -233
- package/skills/patterns/testing/coverage.md +0 -207
- package/skills/patterns/testing/fixtures.md +0 -225
- package/skills/patterns/testing/integration.md +0 -436
- package/skills/patterns/testing/mocking.md +0 -177
- package/skills/patterns/testing/playwright.md +0 -162
- package/skills/patterns/testing/snapshot.md +0 -175
- package/skills/patterns/testing/vitest.md +0 -307
- package/skills/patterns/ui/accordions.md +0 -395
- package/skills/patterns/ui/cards.md +0 -299
- package/skills/patterns/ui/dropdowns.md +0 -476
- package/skills/patterns/ui/empty-states.md +0 -320
- package/skills/patterns/ui/forms.md +0 -405
- package/skills/patterns/ui/inputs.md +0 -319
- package/skills/patterns/ui/layouts.md +0 -282
- package/skills/patterns/ui/loading.md +0 -291
- package/skills/patterns/ui/modals.md +0 -338
- package/skills/patterns/ui/navigation.md +0 -374
- package/skills/patterns/ui/tables.md +0 -407
- package/skills/patterns/ui/toasts.md +0 -300
- package/skills/patterns/ui/tooltips.md +0 -396
- package/skills/patterns/utils/dates.md +0 -435
- package/skills/patterns/utils/errors.md +0 -451
- package/skills/patterns/utils/formatting.md +0 -345
- package/skills/patterns/utils/validation.md +0 -434
- package/templates/bootspring.config.js +0 -83
- package/templates/business/business-model-canvas.md +0 -246
- package/templates/business/business-plan.md +0 -266
- package/templates/business/competitive-analysis.md +0 -312
- package/templates/fundraising/data-room-checklist.md +0 -300
- package/templates/fundraising/investor-research.md +0 -243
- package/templates/fundraising/pitch-deck-outline.md +0 -253
- package/templates/legal/gdpr-checklist.md +0 -339
- package/templates/legal/privacy-policy.md +0 -285
- package/templates/legal/terms-of-service.md +0 -222
- package/templates/mcp.json +0 -9
package/core/api-client.js
CHANGED
|
@@ -8,16 +8,17 @@
|
|
|
8
8
|
const https = require('https');
|
|
9
9
|
const http = require('http');
|
|
10
10
|
const auth = require('./auth');
|
|
11
|
+
const session = require('./session');
|
|
11
12
|
|
|
12
13
|
const API_BASE = process.env.BOOTSPRING_API_URL || 'https://api.bootspring.com';
|
|
13
|
-
const API_VERSION = 'v1';
|
|
14
|
+
const API_VERSION = 'v1'; // Note: auth routes don't use version prefix
|
|
14
15
|
|
|
15
16
|
// Cache for API responses
|
|
16
17
|
const cache = new Map();
|
|
17
18
|
const CACHE_TTL = 60000; // 1 minute
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
|
-
* Make an API request
|
|
21
|
+
* Make an API request (with version prefix)
|
|
21
22
|
*/
|
|
22
23
|
async function request(method, path, data = null, options = {}) {
|
|
23
24
|
const token = auth.getToken();
|
|
@@ -29,10 +30,15 @@ async function request(method, path, data = null, options = {}) {
|
|
|
29
30
|
// Get device ID for request tracking
|
|
30
31
|
const deviceId = auth.getDeviceId();
|
|
31
32
|
|
|
33
|
+
// Get project context for tracking
|
|
34
|
+
const project = session.getEffectiveProject();
|
|
35
|
+
const projectId = project?.id || null;
|
|
36
|
+
|
|
32
37
|
const headers = {
|
|
33
38
|
'Content-Type': 'application/json',
|
|
34
39
|
'User-Agent': `bootspring-cli/${require('../package.json').version}`,
|
|
35
40
|
'X-Device-Id': deviceId,
|
|
41
|
+
...(projectId && { 'X-Project-Id': projectId }),
|
|
36
42
|
...options.headers
|
|
37
43
|
};
|
|
38
44
|
|
|
@@ -110,6 +116,86 @@ async function request(method, path, data = null, options = {}) {
|
|
|
110
116
|
});
|
|
111
117
|
}
|
|
112
118
|
|
|
119
|
+
/**
|
|
120
|
+
* Make a direct API request (without version prefix, for /api/projects etc.)
|
|
121
|
+
*/
|
|
122
|
+
async function directRequest(method, path, data = null, options = {}) {
|
|
123
|
+
const token = auth.getToken();
|
|
124
|
+
const apiKey = auth.getApiKey();
|
|
125
|
+
const url = new URL(`/api${path}`, API_BASE);
|
|
126
|
+
const isHttps = url.protocol === 'https:';
|
|
127
|
+
const httpModule = isHttps ? https : http;
|
|
128
|
+
|
|
129
|
+
const deviceId = auth.getDeviceId();
|
|
130
|
+
const project = session.getEffectiveProject();
|
|
131
|
+
const projectId = project?.id || null;
|
|
132
|
+
|
|
133
|
+
const headers = {
|
|
134
|
+
'Content-Type': 'application/json',
|
|
135
|
+
'User-Agent': `bootspring-cli/${require('../package.json').version}`,
|
|
136
|
+
'X-Device-Id': deviceId,
|
|
137
|
+
...(projectId && { 'X-Project-Id': projectId }),
|
|
138
|
+
...options.headers
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
if (apiKey) {
|
|
142
|
+
headers['X-API-Key'] = apiKey;
|
|
143
|
+
} else if (token) {
|
|
144
|
+
headers['Authorization'] = `Bearer ${token}`;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return new Promise((resolve, reject) => {
|
|
148
|
+
const req = httpModule.request(url, {
|
|
149
|
+
method,
|
|
150
|
+
headers,
|
|
151
|
+
timeout: options.timeout || 30000
|
|
152
|
+
}, (res) => {
|
|
153
|
+
let body = '';
|
|
154
|
+
res.on('data', chunk => body += chunk);
|
|
155
|
+
res.on('end', () => {
|
|
156
|
+
try {
|
|
157
|
+
const json = JSON.parse(body);
|
|
158
|
+
if (res.statusCode >= 400) {
|
|
159
|
+
const error = new Error(json.message || json.error || 'API Error');
|
|
160
|
+
error.status = res.statusCode;
|
|
161
|
+
error.code = json.error || json.code;
|
|
162
|
+
error.details = json.details;
|
|
163
|
+
reject(error);
|
|
164
|
+
} else {
|
|
165
|
+
resolve(json);
|
|
166
|
+
}
|
|
167
|
+
} catch {
|
|
168
|
+
if (res.statusCode >= 400) {
|
|
169
|
+
const error = new Error(body || 'API Error');
|
|
170
|
+
error.status = res.statusCode;
|
|
171
|
+
reject(error);
|
|
172
|
+
} else {
|
|
173
|
+
resolve(body);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
req.on('error', (err) => {
|
|
180
|
+
if (err.code === 'ECONNREFUSED') {
|
|
181
|
+
reject(new Error('Cannot connect to Bootspring API. Please check your internet connection.'));
|
|
182
|
+
} else {
|
|
183
|
+
reject(err);
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
req.on('timeout', () => {
|
|
188
|
+
req.destroy();
|
|
189
|
+
reject(new Error('Request timeout'));
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
if (data) {
|
|
193
|
+
req.write(JSON.stringify(data));
|
|
194
|
+
}
|
|
195
|
+
req.end();
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
113
199
|
/**
|
|
114
200
|
* Clear the cache
|
|
115
201
|
*/
|
|
@@ -175,6 +261,93 @@ function requireAuth() {
|
|
|
175
261
|
* API methods
|
|
176
262
|
*/
|
|
177
263
|
const api = {
|
|
264
|
+
// Device Authorization Flow
|
|
265
|
+
async requestDeviceCode() {
|
|
266
|
+
const deviceContext = auth.getDeviceContext();
|
|
267
|
+
const url = new URL('/api/v1/auth/device', API_BASE);
|
|
268
|
+
const isHttps = url.protocol === 'https:';
|
|
269
|
+
const httpModule = isHttps ? https : http;
|
|
270
|
+
|
|
271
|
+
return new Promise((resolve, reject) => {
|
|
272
|
+
const req = httpModule.request(url, {
|
|
273
|
+
method: 'POST',
|
|
274
|
+
headers: {
|
|
275
|
+
'Content-Type': 'application/json',
|
|
276
|
+
'User-Agent': `bootspring-cli/${require('../package.json').version}`,
|
|
277
|
+
},
|
|
278
|
+
timeout: 10000
|
|
279
|
+
}, (res) => {
|
|
280
|
+
let body = '';
|
|
281
|
+
res.on('data', chunk => body += chunk);
|
|
282
|
+
res.on('end', () => {
|
|
283
|
+
try {
|
|
284
|
+
const json = JSON.parse(body);
|
|
285
|
+
if (res.statusCode >= 400) {
|
|
286
|
+
const error = new Error(json.message || json.error || 'Failed to get device code');
|
|
287
|
+
error.status = res.statusCode;
|
|
288
|
+
error.code = json.error;
|
|
289
|
+
reject(error);
|
|
290
|
+
} else {
|
|
291
|
+
resolve(json);
|
|
292
|
+
}
|
|
293
|
+
} catch {
|
|
294
|
+
reject(new Error('Invalid response from API'));
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
req.on('error', reject);
|
|
299
|
+
req.on('timeout', () => {
|
|
300
|
+
req.destroy();
|
|
301
|
+
reject(new Error('Request timeout'));
|
|
302
|
+
});
|
|
303
|
+
req.write(JSON.stringify({ device: deviceContext }));
|
|
304
|
+
req.end();
|
|
305
|
+
});
|
|
306
|
+
},
|
|
307
|
+
|
|
308
|
+
async pollDeviceToken(deviceCode) {
|
|
309
|
+
const url = new URL('/api/v1/auth/device/token', API_BASE);
|
|
310
|
+
const isHttps = url.protocol === 'https:';
|
|
311
|
+
const httpModule = isHttps ? https : http;
|
|
312
|
+
|
|
313
|
+
return new Promise((resolve, reject) => {
|
|
314
|
+
const req = httpModule.request(url, {
|
|
315
|
+
method: 'POST',
|
|
316
|
+
headers: {
|
|
317
|
+
'Content-Type': 'application/json',
|
|
318
|
+
'User-Agent': `bootspring-cli/${require('../package.json').version}`,
|
|
319
|
+
},
|
|
320
|
+
timeout: 10000
|
|
321
|
+
}, (res) => {
|
|
322
|
+
let body = '';
|
|
323
|
+
res.on('data', chunk => body += chunk);
|
|
324
|
+
res.on('end', () => {
|
|
325
|
+
try {
|
|
326
|
+
const json = JSON.parse(body);
|
|
327
|
+
if (res.statusCode >= 400) {
|
|
328
|
+
const error = new Error(json.message || json.error || 'Authorization pending');
|
|
329
|
+
error.status = res.statusCode;
|
|
330
|
+
error.code = json.error;
|
|
331
|
+
error.details = json;
|
|
332
|
+
reject(error);
|
|
333
|
+
} else {
|
|
334
|
+
resolve(json);
|
|
335
|
+
}
|
|
336
|
+
} catch {
|
|
337
|
+
reject(new Error('Invalid response from API'));
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
});
|
|
341
|
+
req.on('error', reject);
|
|
342
|
+
req.on('timeout', () => {
|
|
343
|
+
req.destroy();
|
|
344
|
+
reject(new Error('Request timeout'));
|
|
345
|
+
});
|
|
346
|
+
req.write(JSON.stringify({ device_code: deviceCode }));
|
|
347
|
+
req.end();
|
|
348
|
+
});
|
|
349
|
+
},
|
|
350
|
+
|
|
178
351
|
// Auth
|
|
179
352
|
async login(email, password) {
|
|
180
353
|
const deviceContext = auth.getDeviceContext();
|
|
@@ -188,18 +361,25 @@ const api = {
|
|
|
188
361
|
},
|
|
189
362
|
|
|
190
363
|
async loginWithApiKey(apiKey) {
|
|
191
|
-
// Validate API key by calling /
|
|
192
|
-
const url = new URL(
|
|
364
|
+
// Validate API key by calling /api/keys/validate
|
|
365
|
+
const url = new URL('/api/keys/validate', API_BASE);
|
|
193
366
|
const isHttps = url.protocol === 'https:';
|
|
194
367
|
const httpModule = isHttps ? https : http;
|
|
368
|
+
const deviceContext = auth.getDeviceContext();
|
|
195
369
|
|
|
196
370
|
return new Promise((resolve, reject) => {
|
|
371
|
+
const requestBody = JSON.stringify({
|
|
372
|
+
apiKey,
|
|
373
|
+
deviceFingerprint: deviceContext.deviceId,
|
|
374
|
+
deviceName: `CLI - ${deviceContext.hostname}`
|
|
375
|
+
});
|
|
376
|
+
|
|
197
377
|
const req = httpModule.request(url, {
|
|
198
|
-
method: '
|
|
378
|
+
method: 'POST',
|
|
199
379
|
headers: {
|
|
200
380
|
'Content-Type': 'application/json',
|
|
201
381
|
'User-Agent': `bootspring-cli/${require('../package.json').version}`,
|
|
202
|
-
'
|
|
382
|
+
'Content-Length': Buffer.byteLength(requestBody)
|
|
203
383
|
},
|
|
204
384
|
timeout: 10000
|
|
205
385
|
}, (res) => {
|
|
@@ -208,15 +388,33 @@ const api = {
|
|
|
208
388
|
res.on('end', () => {
|
|
209
389
|
try {
|
|
210
390
|
const json = JSON.parse(body);
|
|
211
|
-
if (res.statusCode >= 400) {
|
|
212
|
-
const error = new Error(json.
|
|
391
|
+
if (res.statusCode >= 400 || !json.valid) {
|
|
392
|
+
const error = new Error(json.error || 'Invalid API key');
|
|
213
393
|
error.status = res.statusCode;
|
|
214
|
-
error.code = json.error
|
|
394
|
+
error.code = json.error;
|
|
215
395
|
reject(error);
|
|
216
396
|
} else {
|
|
397
|
+
// Build user info from response
|
|
398
|
+
const user = {
|
|
399
|
+
tier: json.tier,
|
|
400
|
+
scopes: json.scopes
|
|
401
|
+
};
|
|
402
|
+
|
|
217
403
|
// Save API key and user info
|
|
218
|
-
auth.loginWithApiKey(apiKey,
|
|
219
|
-
|
|
404
|
+
auth.loginWithApiKey(apiKey, user);
|
|
405
|
+
|
|
406
|
+
// If key has a project, auto-set project context
|
|
407
|
+
if (json.project) {
|
|
408
|
+
session.setCurrentProject(json.project);
|
|
409
|
+
session.addRecentProject(json.project);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
resolve({
|
|
413
|
+
user,
|
|
414
|
+
project: json.project,
|
|
415
|
+
device: json.device,
|
|
416
|
+
usage: json.usage
|
|
417
|
+
});
|
|
220
418
|
}
|
|
221
419
|
} catch {
|
|
222
420
|
reject(new Error('Invalid response from API'));
|
|
@@ -229,6 +427,7 @@ const api = {
|
|
|
229
427
|
req.destroy();
|
|
230
428
|
reject(new Error('Request timeout'));
|
|
231
429
|
});
|
|
430
|
+
req.write(requestBody);
|
|
232
431
|
req.end();
|
|
233
432
|
});
|
|
234
433
|
},
|
|
@@ -250,6 +449,61 @@ const api = {
|
|
|
250
449
|
return request('GET', '/auth/me');
|
|
251
450
|
},
|
|
252
451
|
|
|
452
|
+
/**
|
|
453
|
+
* Validate an API key and get its associated project
|
|
454
|
+
* @param {string} apiKey - The API key to validate
|
|
455
|
+
* @returns {Promise<{valid: boolean, tier: string, project: object|null}>}
|
|
456
|
+
*/
|
|
457
|
+
async validateApiKey(apiKey) {
|
|
458
|
+
const url = new URL('/api/keys/validate', API_BASE);
|
|
459
|
+
const isHttps = url.protocol === 'https:';
|
|
460
|
+
const httpModule = isHttps ? https : http;
|
|
461
|
+
const deviceContext = auth.getDeviceContext();
|
|
462
|
+
|
|
463
|
+
return new Promise((resolve, reject) => {
|
|
464
|
+
const requestBody = JSON.stringify({
|
|
465
|
+
apiKey,
|
|
466
|
+
deviceFingerprint: deviceContext.deviceId,
|
|
467
|
+
deviceName: `CLI - ${deviceContext.hostname}`
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
const req = httpModule.request(url, {
|
|
471
|
+
method: 'POST',
|
|
472
|
+
headers: {
|
|
473
|
+
'Content-Type': 'application/json',
|
|
474
|
+
'User-Agent': `bootspring-cli/${require('../package.json').version}`,
|
|
475
|
+
'Content-Length': Buffer.byteLength(requestBody)
|
|
476
|
+
},
|
|
477
|
+
timeout: 10000
|
|
478
|
+
}, (res) => {
|
|
479
|
+
let body = '';
|
|
480
|
+
res.on('data', chunk => body += chunk);
|
|
481
|
+
res.on('end', () => {
|
|
482
|
+
try {
|
|
483
|
+
const json = JSON.parse(body);
|
|
484
|
+
if (res.statusCode >= 400 || !json.valid) {
|
|
485
|
+
const error = new Error(json.error || 'Invalid API key');
|
|
486
|
+
error.status = res.statusCode;
|
|
487
|
+
reject(error);
|
|
488
|
+
} else {
|
|
489
|
+
resolve(json);
|
|
490
|
+
}
|
|
491
|
+
} catch {
|
|
492
|
+
reject(new Error('Invalid response from API'));
|
|
493
|
+
}
|
|
494
|
+
});
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
req.on('error', reject);
|
|
498
|
+
req.on('timeout', () => {
|
|
499
|
+
req.destroy();
|
|
500
|
+
reject(new Error('Request timeout'));
|
|
501
|
+
});
|
|
502
|
+
req.write(requestBody);
|
|
503
|
+
req.end();
|
|
504
|
+
});
|
|
505
|
+
},
|
|
506
|
+
|
|
253
507
|
async refreshToken() {
|
|
254
508
|
const refreshToken = auth.getRefreshToken();
|
|
255
509
|
if (!refreshToken) {
|
|
@@ -287,6 +541,16 @@ const api = {
|
|
|
287
541
|
return request('GET', `/agents/${encodeURIComponent(id)}`);
|
|
288
542
|
},
|
|
289
543
|
|
|
544
|
+
/**
|
|
545
|
+
* Get agent context content (requires auth, tier-gated)
|
|
546
|
+
* @param {string} id - Agent ID
|
|
547
|
+
* @returns {Promise<{id, name, description, context, tier}>}
|
|
548
|
+
*/
|
|
549
|
+
async getAgentContext(id) {
|
|
550
|
+
requireAuth();
|
|
551
|
+
return request('GET', `/agents/${encodeURIComponent(id)}/context`);
|
|
552
|
+
},
|
|
553
|
+
|
|
290
554
|
async invokeAgent(id, projectContext, task) {
|
|
291
555
|
requireAuth();
|
|
292
556
|
return request('POST', `/agents/${encodeURIComponent(id)}/invoke`, {
|
|
@@ -301,36 +565,68 @@ const api = {
|
|
|
301
565
|
},
|
|
302
566
|
|
|
303
567
|
// Skills
|
|
568
|
+
/**
|
|
569
|
+
* List available skills with tier information
|
|
570
|
+
* @param {object} options - Filter options
|
|
571
|
+
* @returns {Promise<{skills: Array, categories: Array, userTier: string}>}
|
|
572
|
+
*/
|
|
304
573
|
async listSkills(options = {}) {
|
|
305
574
|
requireAuth();
|
|
306
575
|
const params = new URLSearchParams();
|
|
307
576
|
if (options.category) params.append('category', options.category);
|
|
308
|
-
if (options.tag) params.append('tag', options.tag);
|
|
309
577
|
if (options.search) params.append('search', options.search);
|
|
310
|
-
if (options.external) params.append('external', 'true');
|
|
311
578
|
|
|
312
579
|
const query = params.toString();
|
|
313
580
|
return request('GET', `/skills${query ? '?' + query : ''}`);
|
|
314
581
|
},
|
|
315
582
|
|
|
583
|
+
/**
|
|
584
|
+
* Get skill content (requires auth, tier-gated)
|
|
585
|
+
* @param {string} skillId - Skill ID (e.g., 'api/route-handler')
|
|
586
|
+
* @returns {Promise<{id, name, content, tier}>}
|
|
587
|
+
*/
|
|
588
|
+
async getSkillContent(skillId) {
|
|
589
|
+
requireAuth();
|
|
590
|
+
// Split skillId into parts for the API path
|
|
591
|
+
return request('GET', `/skills/${skillId}`);
|
|
592
|
+
},
|
|
593
|
+
|
|
594
|
+
/**
|
|
595
|
+
* @deprecated Use getSkillContent instead
|
|
596
|
+
*/
|
|
316
597
|
async getSkill(category, name) {
|
|
317
598
|
requireAuth();
|
|
318
599
|
return request('GET', `/skills/${encodeURIComponent(category)}/${encodeURIComponent(name)}`);
|
|
319
600
|
},
|
|
320
601
|
|
|
321
|
-
async
|
|
602
|
+
async searchSkills(query, options = {}) {
|
|
603
|
+
requireAuth();
|
|
604
|
+
const params = new URLSearchParams();
|
|
605
|
+
params.append('search', query);
|
|
606
|
+
if (options.category) params.append('category', options.category);
|
|
607
|
+
return request('GET', `/skills?${params.toString()}`);
|
|
608
|
+
},
|
|
609
|
+
|
|
610
|
+
// Templates (thin client - served from API)
|
|
611
|
+
/**
|
|
612
|
+
* List available templates in a category
|
|
613
|
+
* @param {string} category - Template category (business, legal, fundraising, etc.)
|
|
614
|
+
* @returns {Promise<{templates: Array}>}
|
|
615
|
+
*/
|
|
616
|
+
async listTemplates(category) {
|
|
322
617
|
requireAuth();
|
|
323
|
-
return request('GET', `/
|
|
618
|
+
return request('GET', `/templates/${encodeURIComponent(category)}`);
|
|
324
619
|
},
|
|
325
620
|
|
|
326
|
-
|
|
621
|
+
/**
|
|
622
|
+
* Get template content
|
|
623
|
+
* @param {string} category - Template category
|
|
624
|
+
* @param {string} name - Template name/file
|
|
625
|
+
* @returns {Promise<{name, content, variables}>}
|
|
626
|
+
*/
|
|
627
|
+
async getTemplate(category, name) {
|
|
327
628
|
requireAuth();
|
|
328
|
-
return request('
|
|
329
|
-
query,
|
|
330
|
-
categories: options.categories,
|
|
331
|
-
tags: options.tags,
|
|
332
|
-
includeExternal: options.includeExternal
|
|
333
|
-
});
|
|
629
|
+
return request('GET', `/templates/${encodeURIComponent(category)}/${encodeURIComponent(name)}`);
|
|
334
630
|
},
|
|
335
631
|
|
|
336
632
|
// Orchestrator
|
|
@@ -432,6 +728,222 @@ const api = {
|
|
|
432
728
|
async getInvoices() {
|
|
433
729
|
requireAuth();
|
|
434
730
|
return request('GET', '/billing/invoices');
|
|
731
|
+
},
|
|
732
|
+
|
|
733
|
+
// Entitlements (v1)
|
|
734
|
+
async resolveEntitlements() {
|
|
735
|
+
requireAuth();
|
|
736
|
+
return request('GET', '/entitlements/resolve', null, { noCache: false });
|
|
737
|
+
},
|
|
738
|
+
|
|
739
|
+
// Usage Tracking (v1)
|
|
740
|
+
async trackUsage(type, metadata = {}) {
|
|
741
|
+
requireAuth();
|
|
742
|
+
return request('POST', '/track', { type, metadata });
|
|
743
|
+
},
|
|
744
|
+
|
|
745
|
+
// Telemetry Batch Upload (v1)
|
|
746
|
+
async uploadTelemetryBatch(events, batchInfo = {}) {
|
|
747
|
+
requireAuth();
|
|
748
|
+
const batchId = batchInfo.id || require('crypto').randomUUID();
|
|
749
|
+
return request('POST', '/events/batch', {
|
|
750
|
+
source: 'bootspring',
|
|
751
|
+
batch: {
|
|
752
|
+
index: batchInfo.index || 0,
|
|
753
|
+
total: batchInfo.total || 1,
|
|
754
|
+
id: batchId
|
|
755
|
+
},
|
|
756
|
+
events
|
|
757
|
+
}, {
|
|
758
|
+
headers: {
|
|
759
|
+
'X-Bootspring-Batch-Id': batchId
|
|
760
|
+
}
|
|
761
|
+
});
|
|
762
|
+
},
|
|
763
|
+
|
|
764
|
+
// Projects (from /api/auth/device/projects)
|
|
765
|
+
async listProjects() {
|
|
766
|
+
requireAuth();
|
|
767
|
+
// Projects endpoint is under auth, not v1
|
|
768
|
+
const url = new URL('/api/auth/device/projects', API_BASE);
|
|
769
|
+
const isHttps = url.protocol === 'https:';
|
|
770
|
+
const httpModule = isHttps ? https : http;
|
|
771
|
+
const apiKey = auth.getApiKey();
|
|
772
|
+
const token = auth.getToken();
|
|
773
|
+
|
|
774
|
+
return new Promise((resolve, reject) => {
|
|
775
|
+
const headers = {
|
|
776
|
+
'Content-Type': 'application/json',
|
|
777
|
+
'User-Agent': `bootspring-cli/${require('../package.json').version}`,
|
|
778
|
+
};
|
|
779
|
+
if (apiKey) {
|
|
780
|
+
headers['X-API-Key'] = apiKey;
|
|
781
|
+
} else if (token) {
|
|
782
|
+
headers['Authorization'] = `Bearer ${token}`;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
const req = httpModule.request(url, {
|
|
786
|
+
method: 'GET',
|
|
787
|
+
headers,
|
|
788
|
+
timeout: 10000
|
|
789
|
+
}, (res) => {
|
|
790
|
+
let body = '';
|
|
791
|
+
res.on('data', chunk => body += chunk);
|
|
792
|
+
res.on('end', () => {
|
|
793
|
+
try {
|
|
794
|
+
const json = JSON.parse(body);
|
|
795
|
+
if (res.statusCode >= 400) {
|
|
796
|
+
const error = new Error(json.message || json.error || 'Failed to list projects');
|
|
797
|
+
error.status = res.statusCode;
|
|
798
|
+
reject(error);
|
|
799
|
+
} else {
|
|
800
|
+
resolve(json);
|
|
801
|
+
}
|
|
802
|
+
} catch {
|
|
803
|
+
reject(new Error('Invalid response from API'));
|
|
804
|
+
}
|
|
805
|
+
});
|
|
806
|
+
});
|
|
807
|
+
req.on('error', reject);
|
|
808
|
+
req.on('timeout', () => {
|
|
809
|
+
req.destroy();
|
|
810
|
+
reject(new Error('Request timeout'));
|
|
811
|
+
});
|
|
812
|
+
req.end();
|
|
813
|
+
});
|
|
814
|
+
},
|
|
815
|
+
|
|
816
|
+
// Project Members Management
|
|
817
|
+
async getProjectMembers(projectId) {
|
|
818
|
+
requireAuth();
|
|
819
|
+
return directRequest('GET', `/projects/${encodeURIComponent(projectId)}/members`);
|
|
820
|
+
},
|
|
821
|
+
|
|
822
|
+
async addProjectMember(projectId, email, role = 'member') {
|
|
823
|
+
requireAuth();
|
|
824
|
+
return directRequest('POST', `/projects/${encodeURIComponent(projectId)}/members`, {
|
|
825
|
+
email,
|
|
826
|
+
role
|
|
827
|
+
});
|
|
828
|
+
},
|
|
829
|
+
|
|
830
|
+
async updateProjectMember(projectId, userId, role) {
|
|
831
|
+
requireAuth();
|
|
832
|
+
return directRequest('PATCH', `/projects/${encodeURIComponent(projectId)}/members/${encodeURIComponent(userId)}`, {
|
|
833
|
+
role
|
|
834
|
+
});
|
|
835
|
+
},
|
|
836
|
+
|
|
837
|
+
async removeProjectMember(projectId, userId) {
|
|
838
|
+
requireAuth();
|
|
839
|
+
return directRequest('DELETE', `/projects/${encodeURIComponent(projectId)}/members/${encodeURIComponent(userId)}`);
|
|
840
|
+
},
|
|
841
|
+
|
|
842
|
+
async transferProjectOwnership(projectId, newOwnerId) {
|
|
843
|
+
requireAuth();
|
|
844
|
+
return directRequest('POST', `/projects/${encodeURIComponent(projectId)}/transfer`, {
|
|
845
|
+
newOwnerId
|
|
846
|
+
});
|
|
847
|
+
},
|
|
848
|
+
|
|
849
|
+
async findSimilarProjects(name, repoUrl) {
|
|
850
|
+
requireAuth();
|
|
851
|
+
const params = new URLSearchParams();
|
|
852
|
+
if (name) params.set('name', name);
|
|
853
|
+
if (repoUrl) params.set('repo', repoUrl);
|
|
854
|
+
return directRequest('GET', `/projects/similar?${params.toString()}`);
|
|
855
|
+
},
|
|
856
|
+
|
|
857
|
+
// Preseed Documents
|
|
858
|
+
/**
|
|
859
|
+
* List preseed documents for a project
|
|
860
|
+
* @param {string} projectId - Project ID
|
|
861
|
+
* @returns {Promise<{documents: Array, count: number, storage: object}>}
|
|
862
|
+
*/
|
|
863
|
+
async listPreseedDocuments(projectId) {
|
|
864
|
+
requireAuth();
|
|
865
|
+
return directRequest('GET', `/projects/${encodeURIComponent(projectId)}/preseed/documents`);
|
|
866
|
+
},
|
|
867
|
+
|
|
868
|
+
/**
|
|
869
|
+
* Get a single preseed document content
|
|
870
|
+
* @param {string} projectId - Project ID
|
|
871
|
+
* @param {string} documentName - Document name (e.g., 'VISION', 'PRD')
|
|
872
|
+
* @returns {Promise<{name: string, content: string, format: string}>}
|
|
873
|
+
*/
|
|
874
|
+
async getPreseedDocument(projectId, documentName) {
|
|
875
|
+
requireAuth();
|
|
876
|
+
return directRequest('GET', `/projects/${encodeURIComponent(projectId)}/preseed/documents?name=${encodeURIComponent(documentName)}`);
|
|
877
|
+
},
|
|
878
|
+
|
|
879
|
+
/**
|
|
880
|
+
* Download preseed documents as ZIP
|
|
881
|
+
* @param {string} projectId - Project ID
|
|
882
|
+
* @returns {Promise<Buffer>} ZIP file buffer
|
|
883
|
+
*/
|
|
884
|
+
async downloadPreseedZip(projectId) {
|
|
885
|
+
requireAuth();
|
|
886
|
+
const apiKey = auth.getApiKey();
|
|
887
|
+
const token = auth.getToken();
|
|
888
|
+
const url = new URL(`/api/projects/${encodeURIComponent(projectId)}/preseed/documents?format=zip`, API_BASE);
|
|
889
|
+
const isHttps = url.protocol === 'https:';
|
|
890
|
+
const httpModule = isHttps ? https : http;
|
|
891
|
+
|
|
892
|
+
return new Promise((resolve, reject) => {
|
|
893
|
+
const headers = {
|
|
894
|
+
'User-Agent': `bootspring-cli/${require('../package.json').version}`,
|
|
895
|
+
};
|
|
896
|
+
if (apiKey) {
|
|
897
|
+
headers['X-API-Key'] = apiKey;
|
|
898
|
+
} else if (token) {
|
|
899
|
+
headers['Authorization'] = `Bearer ${token}`;
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
const req = httpModule.request(url, {
|
|
903
|
+
method: 'GET',
|
|
904
|
+
headers,
|
|
905
|
+
timeout: 60000
|
|
906
|
+
}, (res) => {
|
|
907
|
+
if (res.statusCode >= 400) {
|
|
908
|
+
let body = '';
|
|
909
|
+
res.on('data', chunk => body += chunk);
|
|
910
|
+
res.on('end', () => {
|
|
911
|
+
try {
|
|
912
|
+
const json = JSON.parse(body);
|
|
913
|
+
const error = new Error(json.message || json.error || 'Failed to download preseed documents');
|
|
914
|
+
error.status = res.statusCode;
|
|
915
|
+
reject(error);
|
|
916
|
+
} catch {
|
|
917
|
+
reject(new Error(`HTTP ${res.statusCode}: ${body}`));
|
|
918
|
+
}
|
|
919
|
+
});
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
const chunks = [];
|
|
924
|
+
res.on('data', chunk => chunks.push(chunk));
|
|
925
|
+
res.on('end', () => {
|
|
926
|
+
resolve(Buffer.concat(chunks));
|
|
927
|
+
});
|
|
928
|
+
});
|
|
929
|
+
|
|
930
|
+
req.on('error', reject);
|
|
931
|
+
req.on('timeout', () => {
|
|
932
|
+
req.destroy();
|
|
933
|
+
reject(new Error('Request timeout'));
|
|
934
|
+
});
|
|
935
|
+
req.end();
|
|
936
|
+
});
|
|
937
|
+
},
|
|
938
|
+
|
|
939
|
+
/**
|
|
940
|
+
* Get preseed wizard data for a project
|
|
941
|
+
* @param {string} projectId - Project ID
|
|
942
|
+
* @returns {Promise<{wizardData: object, progress: object}>}
|
|
943
|
+
*/
|
|
944
|
+
async getPreseedWizard(projectId) {
|
|
945
|
+
requireAuth();
|
|
946
|
+
return directRequest('GET', `/projects/${encodeURIComponent(projectId)}/preseed/wizard`);
|
|
435
947
|
}
|
|
436
948
|
};
|
|
437
949
|
|
|
@@ -439,6 +951,7 @@ module.exports = {
|
|
|
439
951
|
API_BASE,
|
|
440
952
|
API_VERSION,
|
|
441
953
|
request,
|
|
954
|
+
directRequest,
|
|
442
955
|
clearCache,
|
|
443
956
|
healthCheck,
|
|
444
957
|
requireAuth,
|