@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.
Files changed (253) hide show
  1. package/README.md +107 -14
  2. package/bin/bootspring.js +166 -27
  3. package/cli/agent.js +189 -17
  4. package/cli/analyze.js +499 -0
  5. package/cli/audit.js +557 -0
  6. package/cli/auth.js +495 -38
  7. package/cli/billing.js +302 -0
  8. package/cli/build.js +695 -0
  9. package/cli/business.js +109 -26
  10. package/cli/checkpoint-utils.js +168 -0
  11. package/cli/checkpoint.js +639 -0
  12. package/cli/cloud-sync.js +447 -0
  13. package/cli/content.js +198 -0
  14. package/cli/context.js +1 -1
  15. package/cli/deploy.js +543 -0
  16. package/cli/fundraise.js +112 -50
  17. package/cli/github-cmd.js +435 -0
  18. package/cli/health.js +477 -0
  19. package/cli/init.js +84 -13
  20. package/cli/legal.js +107 -95
  21. package/cli/log.js +2 -2
  22. package/cli/loop.js +976 -73
  23. package/cli/manager.js +711 -0
  24. package/cli/metrics.js +480 -0
  25. package/cli/monitor.js +812 -0
  26. package/cli/onboard.js +521 -0
  27. package/cli/orchestrator.js +12 -24
  28. package/cli/prd.js +594 -0
  29. package/cli/preseed-start.js +1483 -0
  30. package/cli/preseed.js +2302 -0
  31. package/cli/project.js +436 -0
  32. package/cli/quality.js +233 -0
  33. package/cli/security.js +913 -0
  34. package/cli/seed.js +1441 -5
  35. package/cli/skill.js +273 -211
  36. package/cli/suggest.js +989 -0
  37. package/cli/switch.js +453 -0
  38. package/cli/visualize.js +527 -0
  39. package/cli/watch.js +769 -0
  40. package/cli/workspace.js +607 -0
  41. package/core/analyze-workflow.js +1134 -0
  42. package/core/api-client.js +535 -22
  43. package/core/audit-workflow.js +1350 -0
  44. package/core/build-orchestrator.js +480 -0
  45. package/core/build-state.js +577 -0
  46. package/core/checkpoint-engine.js +408 -0
  47. package/core/config.js +1109 -26
  48. package/core/context-loader.js +21 -1
  49. package/core/deploy-workflow.js +836 -0
  50. package/core/entitlements.js +93 -22
  51. package/core/github-sync.js +610 -0
  52. package/core/index.js +8 -1
  53. package/core/ingest.js +1111 -0
  54. package/core/metrics-engine.js +768 -0
  55. package/core/onboard-workflow.js +1007 -0
  56. package/core/preseed-workflow.js +934 -0
  57. package/core/preseed.js +1617 -0
  58. package/core/project-context.js +325 -0
  59. package/core/project-state.js +694 -0
  60. package/core/r2-sync.js +583 -0
  61. package/core/scaffold.js +525 -7
  62. package/core/session.js +258 -0
  63. package/core/task-extractor.js +758 -0
  64. package/core/telemetry.js +28 -6
  65. package/core/tier-enforcement.js +737 -0
  66. package/core/utils.js +38 -14
  67. package/generators/questionnaire.js +15 -12
  68. package/generators/sections/ai.js +7 -7
  69. package/generators/sections/content.js +300 -0
  70. package/generators/sections/index.js +3 -0
  71. package/generators/sections/plugins.js +7 -6
  72. package/generators/templates/build-planning.template.js +596 -0
  73. package/generators/templates/content.template.js +819 -0
  74. package/generators/templates/index.js +2 -1
  75. package/hooks/git-autopilot.js +1250 -0
  76. package/hooks/index.js +9 -0
  77. package/intelligence/agent-collab.js +2057 -0
  78. package/intelligence/auto-suggest.js +634 -0
  79. package/intelligence/content-gen.js +1589 -0
  80. package/intelligence/cross-project.js +1647 -0
  81. package/intelligence/index.js +184 -0
  82. package/intelligence/learning/insights.json +517 -7
  83. package/intelligence/learning/pattern-learner.js +1008 -14
  84. package/intelligence/memory/decision-tracker.js +1431 -31
  85. package/intelligence/memory/decisions.jsonl +0 -0
  86. package/intelligence/orchestrator.js +2896 -1
  87. package/intelligence/prd.js +92 -1
  88. package/intelligence/recommendation-weights.json +14 -2
  89. package/intelligence/recommendations.js +463 -9
  90. package/intelligence/workflow-composer.js +1451 -0
  91. package/marketplace/index.d.ts +324 -0
  92. package/marketplace/index.js +1921 -0
  93. package/mcp/contracts/mcp-contract.v1.json +342 -4
  94. package/mcp/registry.js +680 -3
  95. package/mcp/response-formatter.js +23 -0
  96. package/mcp/tools/assist-tool.js +78 -4
  97. package/mcp/tools/autopilot-tool.js +408 -0
  98. package/mcp/tools/content-tool.js +571 -0
  99. package/mcp/tools/dashboard-tool.js +251 -5
  100. package/mcp/tools/mvp-tool.js +344 -0
  101. package/mcp/tools/plugin-tool.js +23 -1
  102. package/mcp/tools/prd-tool.js +579 -0
  103. package/mcp/tools/seed-tool.js +447 -0
  104. package/mcp/tools/skill-tool.js +43 -14
  105. package/mcp/tools/suggest-tool.js +147 -0
  106. package/package.json +15 -6
  107. package/agents/README.md +0 -93
  108. package/agents/ai-integration-expert/context.md +0 -386
  109. package/agents/api-expert/context.md +0 -416
  110. package/agents/architecture-expert/context.md +0 -454
  111. package/agents/auth-expert/context.md +0 -399
  112. package/agents/backend-expert/context.md +0 -483
  113. package/agents/business-strategy-expert/context.md +0 -180
  114. package/agents/code-review-expert/context.md +0 -365
  115. package/agents/competitive-analysis-expert/context.md +0 -239
  116. package/agents/data-modeling-expert/context.md +0 -352
  117. package/agents/database-expert/context.md +0 -250
  118. package/agents/devops-expert/context.md +0 -446
  119. package/agents/email-expert/context.md +0 -379
  120. package/agents/financial-expert/context.md +0 -213
  121. package/agents/frontend-expert/context.md +0 -364
  122. package/agents/fundraising-expert/context.md +0 -257
  123. package/agents/growth-expert/context.md +0 -249
  124. package/agents/index.js +0 -140
  125. package/agents/investor-relations-expert/context.md +0 -266
  126. package/agents/legal-expert/context.md +0 -284
  127. package/agents/marketing-expert/context.md +0 -236
  128. package/agents/monitoring-expert/context.md +0 -362
  129. package/agents/operations-expert/context.md +0 -279
  130. package/agents/partnerships-expert/context.md +0 -286
  131. package/agents/payment-expert/context.md +0 -340
  132. package/agents/performance-expert/context.md +0 -377
  133. package/agents/private-equity-expert/context.md +0 -246
  134. package/agents/railway-expert/context.md +0 -284
  135. package/agents/research-expert/context.md +0 -245
  136. package/agents/sales-expert/context.md +0 -241
  137. package/agents/security-expert/context.md +0 -343
  138. package/agents/testing-expert/context.md +0 -414
  139. package/agents/ui-ux-expert/context.md +0 -448
  140. package/agents/vercel-expert/context.md +0 -426
  141. package/skills/index.js +0 -787
  142. package/skills/patterns/README.md +0 -163
  143. package/skills/patterns/ai/agents.md +0 -281
  144. package/skills/patterns/ai/claude.md +0 -138
  145. package/skills/patterns/ai/embeddings.md +0 -150
  146. package/skills/patterns/ai/rag.md +0 -266
  147. package/skills/patterns/ai/streaming.md +0 -170
  148. package/skills/patterns/ai/structured-output.md +0 -162
  149. package/skills/patterns/ai/tools.md +0 -154
  150. package/skills/patterns/analytics/tracking.md +0 -220
  151. package/skills/patterns/api/errors.md +0 -296
  152. package/skills/patterns/api/graphql.md +0 -440
  153. package/skills/patterns/api/middleware.md +0 -279
  154. package/skills/patterns/api/openapi.md +0 -285
  155. package/skills/patterns/api/rate-limiting.md +0 -231
  156. package/skills/patterns/api/route-handler.md +0 -217
  157. package/skills/patterns/api/server-action.md +0 -249
  158. package/skills/patterns/api/versioning.md +0 -443
  159. package/skills/patterns/api/webhooks.md +0 -247
  160. package/skills/patterns/auth/clerk.md +0 -132
  161. package/skills/patterns/auth/mfa.md +0 -313
  162. package/skills/patterns/auth/nextauth.md +0 -140
  163. package/skills/patterns/auth/oauth.md +0 -237
  164. package/skills/patterns/auth/rbac.md +0 -152
  165. package/skills/patterns/auth/session-management.md +0 -367
  166. package/skills/patterns/auth/session.md +0 -120
  167. package/skills/patterns/database/audit.md +0 -177
  168. package/skills/patterns/database/migrations.md +0 -177
  169. package/skills/patterns/database/pagination.md +0 -230
  170. package/skills/patterns/database/pooling.md +0 -357
  171. package/skills/patterns/database/prisma.md +0 -180
  172. package/skills/patterns/database/relations.md +0 -187
  173. package/skills/patterns/database/seeding.md +0 -246
  174. package/skills/patterns/database/soft-delete.md +0 -153
  175. package/skills/patterns/database/transactions.md +0 -162
  176. package/skills/patterns/deployment/ci-cd.md +0 -231
  177. package/skills/patterns/deployment/docker.md +0 -188
  178. package/skills/patterns/deployment/monitoring.md +0 -387
  179. package/skills/patterns/deployment/vercel.md +0 -160
  180. package/skills/patterns/email/resend.md +0 -143
  181. package/skills/patterns/email/templates.md +0 -245
  182. package/skills/patterns/email/transactional.md +0 -503
  183. package/skills/patterns/email/verification.md +0 -176
  184. package/skills/patterns/files/download.md +0 -243
  185. package/skills/patterns/files/upload.md +0 -239
  186. package/skills/patterns/i18n/nextintl.md +0 -188
  187. package/skills/patterns/logging/structured.md +0 -292
  188. package/skills/patterns/notifications/email-queue.md +0 -248
  189. package/skills/patterns/notifications/push.md +0 -279
  190. package/skills/patterns/payments/checkout.md +0 -303
  191. package/skills/patterns/payments/invoices.md +0 -287
  192. package/skills/patterns/payments/portal.md +0 -245
  193. package/skills/patterns/payments/stripe.md +0 -272
  194. package/skills/patterns/payments/subscriptions.md +0 -300
  195. package/skills/patterns/payments/usage.md +0 -279
  196. package/skills/patterns/performance/caching.md +0 -276
  197. package/skills/patterns/performance/code-splitting.md +0 -233
  198. package/skills/patterns/performance/edge.md +0 -254
  199. package/skills/patterns/performance/isr.md +0 -266
  200. package/skills/patterns/performance/lazy-loading.md +0 -281
  201. package/skills/patterns/realtime/sse.md +0 -327
  202. package/skills/patterns/realtime/websockets.md +0 -336
  203. package/skills/patterns/search/filtering.md +0 -329
  204. package/skills/patterns/search/fulltext.md +0 -260
  205. package/skills/patterns/security/audit-logging.md +0 -444
  206. package/skills/patterns/security/csrf.md +0 -234
  207. package/skills/patterns/security/headers.md +0 -252
  208. package/skills/patterns/security/sanitization.md +0 -258
  209. package/skills/patterns/security/secrets.md +0 -261
  210. package/skills/patterns/security/validation.md +0 -268
  211. package/skills/patterns/security/xss.md +0 -229
  212. package/skills/patterns/seo/metadata.md +0 -252
  213. package/skills/patterns/state/context.md +0 -349
  214. package/skills/patterns/state/react-query.md +0 -313
  215. package/skills/patterns/state/url-state.md +0 -482
  216. package/skills/patterns/state/zustand.md +0 -262
  217. package/skills/patterns/testing/api.md +0 -259
  218. package/skills/patterns/testing/component.md +0 -233
  219. package/skills/patterns/testing/coverage.md +0 -207
  220. package/skills/patterns/testing/fixtures.md +0 -225
  221. package/skills/patterns/testing/integration.md +0 -436
  222. package/skills/patterns/testing/mocking.md +0 -177
  223. package/skills/patterns/testing/playwright.md +0 -162
  224. package/skills/patterns/testing/snapshot.md +0 -175
  225. package/skills/patterns/testing/vitest.md +0 -307
  226. package/skills/patterns/ui/accordions.md +0 -395
  227. package/skills/patterns/ui/cards.md +0 -299
  228. package/skills/patterns/ui/dropdowns.md +0 -476
  229. package/skills/patterns/ui/empty-states.md +0 -320
  230. package/skills/patterns/ui/forms.md +0 -405
  231. package/skills/patterns/ui/inputs.md +0 -319
  232. package/skills/patterns/ui/layouts.md +0 -282
  233. package/skills/patterns/ui/loading.md +0 -291
  234. package/skills/patterns/ui/modals.md +0 -338
  235. package/skills/patterns/ui/navigation.md +0 -374
  236. package/skills/patterns/ui/tables.md +0 -407
  237. package/skills/patterns/ui/toasts.md +0 -300
  238. package/skills/patterns/ui/tooltips.md +0 -396
  239. package/skills/patterns/utils/dates.md +0 -435
  240. package/skills/patterns/utils/errors.md +0 -451
  241. package/skills/patterns/utils/formatting.md +0 -345
  242. package/skills/patterns/utils/validation.md +0 -434
  243. package/templates/bootspring.config.js +0 -83
  244. package/templates/business/business-model-canvas.md +0 -246
  245. package/templates/business/business-plan.md +0 -266
  246. package/templates/business/competitive-analysis.md +0 -312
  247. package/templates/fundraising/data-room-checklist.md +0 -300
  248. package/templates/fundraising/investor-research.md +0 -243
  249. package/templates/fundraising/pitch-deck-outline.md +0 -253
  250. package/templates/legal/gdpr-checklist.md +0 -339
  251. package/templates/legal/privacy-policy.md +0 -285
  252. package/templates/legal/terms-of-service.md +0 -222
  253. package/templates/mcp.json +0 -9
@@ -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 /auth/me with the key
192
- const url = new URL(`/api/${API_VERSION}/auth/me`, API_BASE);
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: 'GET',
378
+ method: 'POST',
199
379
  headers: {
200
380
  'Content-Type': 'application/json',
201
381
  'User-Agent': `bootspring-cli/${require('../package.json').version}`,
202
- 'X-API-Key': apiKey
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.message || json.error || 'Invalid API key');
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 || json.code;
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, json.user);
219
- resolve(json);
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 getExternalSkill(id) {
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', `/skills/external/${encodeURIComponent(id)}`);
618
+ return request('GET', `/templates/${encodeURIComponent(category)}`);
324
619
  },
325
620
 
326
- async searchSkills(query, options = {}) {
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('POST', '/skills/search', {
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,