@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
package/cli/switch.js ADDED
@@ -0,0 +1,453 @@
1
+ /**
2
+ * Bootspring Switch Command
3
+ *
4
+ * Switch between project contexts.
5
+ *
6
+ * Commands:
7
+ * (default) Show current project and list available
8
+ * <project> Switch to a project by name or slug
9
+ * recent Show and switch to recent projects
10
+ * init Create .bootspring.json in current directory
11
+ * clear Clear current project context
12
+ * status Show detailed context status
13
+ *
14
+ * @package bootspring
15
+ * @command switch
16
+ */
17
+
18
+ const https = require('https');
19
+ const http = require('http');
20
+ const auth = require('../core/auth');
21
+ const session = require('../core/session');
22
+ const utils = require('../core/utils');
23
+
24
+ const API_BASE = process.env.BOOTSPRING_API_URL || 'https://www.bootspring.com';
25
+
26
+ /**
27
+ * Make a direct API request (without v1 prefix)
28
+ */
29
+ async function directRequest(method, path) {
30
+ const apiKey = auth.getApiKey();
31
+ const token = auth.getToken();
32
+ const deviceId = auth.getDeviceId();
33
+ const url = new URL(`/api${path}`, API_BASE);
34
+ const isHttps = url.protocol === 'https:';
35
+ const httpModule = isHttps ? https : http;
36
+
37
+ const headers = {
38
+ 'Content-Type': 'application/json',
39
+ 'User-Agent': `bootspring-cli/${require('../package.json').version}`,
40
+ 'X-Device-Id': deviceId
41
+ };
42
+
43
+ if (apiKey) {
44
+ headers['X-API-Key'] = apiKey;
45
+ } else if (token) {
46
+ headers['Authorization'] = `Bearer ${token}`;
47
+ }
48
+
49
+ return new Promise((resolve, reject) => {
50
+ const req = httpModule.request(url, {
51
+ method,
52
+ headers,
53
+ timeout: 30000
54
+ }, (res) => {
55
+ let body = '';
56
+ res.on('data', chunk => body += chunk);
57
+ res.on('end', () => {
58
+ try {
59
+ const json = JSON.parse(body);
60
+ if (res.statusCode >= 400) {
61
+ const error = new Error(json.error || json.message || 'API Error');
62
+ error.status = res.statusCode;
63
+ reject(error);
64
+ } else {
65
+ resolve(json);
66
+ }
67
+ } catch {
68
+ if (res.statusCode >= 400) {
69
+ reject(new Error(`API Error: ${res.statusCode}`));
70
+ } else {
71
+ resolve(body);
72
+ }
73
+ }
74
+ });
75
+ });
76
+
77
+ req.on('error', reject);
78
+ req.on('timeout', () => {
79
+ req.destroy();
80
+ reject(new Error('Request timeout'));
81
+ });
82
+ req.end();
83
+ });
84
+ }
85
+
86
+ // ANSI colors
87
+ const colors = {
88
+ reset: '\x1b[0m',
89
+ bold: '\x1b[1m',
90
+ dim: '\x1b[2m',
91
+ green: '\x1b[32m',
92
+ yellow: '\x1b[33m',
93
+ red: '\x1b[31m',
94
+ cyan: '\x1b[36m'
95
+ };
96
+
97
+ /**
98
+ * Run switch command
99
+ */
100
+ async function run(args) {
101
+ const parsedArgs = utils.parseArgs(args);
102
+ const subcommand = parsedArgs._[0];
103
+
104
+ // Handle --init flag anywhere
105
+ if (parsedArgs.init) {
106
+ if (!auth.isAuthenticated()) {
107
+ console.log(`${colors.yellow}Not logged in${colors.reset}`);
108
+ console.log(`${colors.dim}Run 'bootspring auth login' first${colors.reset}`);
109
+ return;
110
+ }
111
+ await initLocalConfig();
112
+ return;
113
+ }
114
+
115
+ // Handle --help flag
116
+ if (parsedArgs.help || parsedArgs.h || subcommand === 'help') {
117
+ showHelp();
118
+ return;
119
+ }
120
+
121
+ // Handle subcommands that don't require auth (local data only)
122
+ if (subcommand === 'status') {
123
+ showStatus();
124
+ return;
125
+ }
126
+
127
+ if (subcommand === 'clear') {
128
+ clearContext();
129
+ return;
130
+ }
131
+
132
+ if (subcommand === 'recent') {
133
+ showRecent();
134
+ return;
135
+ }
136
+
137
+ // Commands that require authentication
138
+ if (!auth.isAuthenticated()) {
139
+ console.log(`${colors.yellow}Not logged in${colors.reset}`);
140
+ console.log(`${colors.dim}Run 'bootspring auth login' first${colors.reset}`);
141
+ return;
142
+ }
143
+
144
+ if (subcommand === 'init') {
145
+ await initLocalConfig();
146
+ return;
147
+ }
148
+
149
+ // If no subcommand or unknown, treat as project identifier or show projects
150
+ if (!subcommand) {
151
+ await showProjects();
152
+ return;
153
+ }
154
+
155
+ // Switch to specified project
156
+ await switchTo(subcommand);
157
+ }
158
+
159
+ /**
160
+ * Show current project and list available projects
161
+ */
162
+ async function showProjects() {
163
+ console.log(`\n${colors.bold}Project Context${colors.reset}\n`);
164
+
165
+ // Show current project
166
+ const current = session.getEffectiveProject();
167
+ if (current) {
168
+ console.log(`${colors.cyan}Current:${colors.reset} ${colors.bold}${current.name}${colors.reset}`);
169
+ if (current.slug) {
170
+ console.log(`${colors.dim} ${current.slug}${colors.reset}`);
171
+ }
172
+ const sourceLabel = current.source === 'local' ? '(from .bootspring.json)' : '(from session)';
173
+ console.log(`${colors.dim} ${sourceLabel}${colors.reset}`);
174
+ console.log();
175
+ } else {
176
+ console.log(`${colors.yellow}No project selected${colors.reset}\n`);
177
+ }
178
+
179
+ // Fetch available projects
180
+ console.log(`${colors.dim}Fetching projects...${colors.reset}`);
181
+
182
+ try {
183
+ const response = await directRequest('GET', '/projects');
184
+ const projects = response.projects || [];
185
+
186
+ if (projects.length === 0) {
187
+ console.log(`\n${colors.yellow}No projects found${colors.reset}`);
188
+ console.log(`${colors.dim}Create a project at https://bootspring.com/dashboard/projects${colors.reset}`);
189
+ return;
190
+ }
191
+
192
+ console.log(`\n${colors.bold}Available Projects${colors.reset}\n`);
193
+ for (const project of projects) {
194
+ const isCurrent = current?.id === project.id;
195
+ const marker = isCurrent ? colors.green + '* ' + colors.reset : ' ';
196
+ console.log(`${marker}${colors.bold}${project.name}${colors.reset} ${colors.dim}(${project.slug})${colors.reset}`);
197
+ }
198
+
199
+ console.log(`\n${colors.dim}Usage: bootspring switch <project-name-or-slug>${colors.reset}`);
200
+ } catch (error) {
201
+ console.log(`\n${colors.red}Failed to fetch projects: ${error.message}${colors.reset}`);
202
+ }
203
+ }
204
+
205
+ /**
206
+ * Switch to a specific project
207
+ */
208
+ async function switchTo(identifier) {
209
+ console.log(`${colors.dim}Switching to project...${colors.reset}`);
210
+
211
+ try {
212
+ const response = await directRequest('GET', '/projects');
213
+ const projects = response.projects || [];
214
+
215
+ // Find project by name or slug
216
+ const project = projects.find(
217
+ p => p.name.toLowerCase() === identifier.toLowerCase() ||
218
+ p.slug === identifier.toLowerCase()
219
+ );
220
+
221
+ if (!project) {
222
+ console.log(`\n${colors.red}Project not found: ${identifier}${colors.reset}`);
223
+ console.log(`${colors.dim}Run 'bootspring switch' to see available projects${colors.reset}`);
224
+ return;
225
+ }
226
+
227
+ // Update session
228
+ session.setCurrentProject(project);
229
+ session.addRecentProject(project);
230
+
231
+ console.log(`\n${colors.green}Switched to: ${project.name}${colors.reset}`);
232
+
233
+ // Automatically create/update local config to lock this directory to the project
234
+ const existingConfig = session.findLocalConfig();
235
+ if (existingConfig && existingConfig._dir === process.cwd()) {
236
+ // Update existing config in same directory
237
+ try {
238
+ session.createLocalConfig(process.cwd(), project);
239
+ console.log(`${colors.dim}Updated: ${existingConfig._path}${colors.reset}`);
240
+ } catch {
241
+ // Ignore errors
242
+ }
243
+ } else if (!existingConfig) {
244
+ // Create new local config to lock this directory
245
+ try {
246
+ const configPath = session.createLocalConfig(process.cwd(), project);
247
+ console.log(`${colors.dim}Locked directory to "${project.name}"${colors.reset}`);
248
+ console.log(`${colors.dim}Config: ${configPath}${colors.reset}`);
249
+ } catch {
250
+ // Ignore errors - some directories may not be writable
251
+ }
252
+ } else {
253
+ // Config exists in parent directory
254
+ console.log(`${colors.dim}Note: Parent directory locked to "${existingConfig.projectName}"${colors.reset}`);
255
+ console.log(`${colors.dim}Run 'bootspring switch --init' here to override${colors.reset}`);
256
+ }
257
+ } catch (error) {
258
+ console.log(`\n${colors.red}Failed to switch project: ${error.message}${colors.reset}`);
259
+ }
260
+ }
261
+
262
+ /**
263
+ * Show recent projects and allow quick switching
264
+ */
265
+ function showRecent() {
266
+ const recent = session.getRecentProjects();
267
+
268
+ console.log(`\n${colors.bold}Recent Projects${colors.reset}\n`);
269
+
270
+ if (recent.length === 0) {
271
+ console.log(`${colors.dim}No recent projects${colors.reset}`);
272
+ console.log(`${colors.dim}Use 'bootspring switch <project>' to switch to a project${colors.reset}`);
273
+ return;
274
+ }
275
+
276
+ const current = session.getEffectiveProject();
277
+
278
+ for (let i = 0; i < recent.length; i++) {
279
+ const project = recent[i];
280
+ const isCurrent = current?.id === project.id;
281
+ const marker = isCurrent ? colors.green + '* ' + colors.reset : ' ';
282
+ const index = colors.dim + `[${i + 1}]` + colors.reset;
283
+ const lastUsed = project.lastUsed ? formatRelativeTime(project.lastUsed) : '';
284
+
285
+ console.log(`${marker}${index} ${colors.bold}${project.name}${colors.reset} ${colors.dim}(${project.slug})${colors.reset}`);
286
+ if (lastUsed) {
287
+ console.log(` ${colors.dim}${lastUsed}${colors.reset}`);
288
+ }
289
+ }
290
+
291
+ console.log(`\n${colors.dim}Usage: bootspring switch <name-or-number>${colors.reset}`);
292
+ }
293
+
294
+ /**
295
+ * Initialize local .bootspring.json config
296
+ */
297
+ async function initLocalConfig() {
298
+ const current = session.getEffectiveProject();
299
+
300
+ if (!current) {
301
+ console.log(`${colors.yellow}No project selected${colors.reset}`);
302
+ console.log(`${colors.dim}First switch to a project: bootspring switch <project>${colors.reset}`);
303
+ return;
304
+ }
305
+
306
+ // Check if local config already exists
307
+ const existingConfig = session.findLocalConfig(process.cwd());
308
+ if (existingConfig && existingConfig._dir === process.cwd()) {
309
+ console.log(`${colors.yellow}Local config already exists${colors.reset}`);
310
+ console.log(`${colors.dim}Path: ${existingConfig._path}${colors.reset}`);
311
+ console.log(`${colors.dim}Project: ${existingConfig.projectName || existingConfig.projectId}${colors.reset}`);
312
+ return;
313
+ }
314
+
315
+ // Create local config
316
+ try {
317
+ const configPath = session.createLocalConfig(process.cwd(), current);
318
+ console.log(`\n${colors.green}Created local config${colors.reset}`);
319
+ console.log(`${colors.dim}Path: ${configPath}${colors.reset}`);
320
+ console.log(`${colors.dim}Project: ${current.name}${colors.reset}`);
321
+ console.log(`\n${colors.dim}This directory is now linked to project "${current.name}"${colors.reset}`);
322
+ } catch (error) {
323
+ console.log(`${colors.red}Failed to create config: ${error.message}${colors.reset}`);
324
+ }
325
+ }
326
+
327
+ /**
328
+ * Show detailed context status
329
+ */
330
+ function showStatus() {
331
+ const state = session.getSessionState();
332
+
333
+ console.log(`\n${colors.bold}${colors.cyan}⚡ Project Context Status${colors.reset}\n`);
334
+
335
+ // Current project
336
+ console.log(`${colors.bold}Current Project${colors.reset}`);
337
+ if (state.project) {
338
+ console.log(` Name: ${colors.bold}${state.project.name}${colors.reset}`);
339
+ if (state.project.slug) {
340
+ console.log(` Slug: ${state.project.slug}`);
341
+ }
342
+ console.log(` ID: ${colors.dim}${state.project.id}${colors.reset}`);
343
+ console.log(` Source: ${state.source === 'local' ? colors.green + 'local config' : colors.cyan + 'session'}${colors.reset}`);
344
+ } else {
345
+ console.log(` ${colors.yellow}None selected${colors.reset}`);
346
+ }
347
+ console.log();
348
+
349
+ // Local config
350
+ console.log(`${colors.bold}Local Config${colors.reset}`);
351
+ if (state.hasLocalConfig) {
352
+ console.log(` ${colors.green}●${colors.reset} Found: ${state.localConfigPath}`);
353
+ } else {
354
+ console.log(` ${colors.dim}○ Not found in current directory or ancestors${colors.reset}`);
355
+ console.log(` ${colors.dim}Run 'bootspring switch --init' to create one${colors.reset}`);
356
+ }
357
+ console.log();
358
+
359
+ // Session
360
+ console.log(`${colors.bold}Session${colors.reset}`);
361
+ console.log(` Status: ${state.hasSession ? colors.green + 'active' : colors.dim + 'inactive'}${colors.reset}`);
362
+ if (state.lastUpdated) {
363
+ console.log(` Last updated: ${formatRelativeTime(state.lastUpdated)}`);
364
+ }
365
+ console.log(` Recent projects: ${state.recentProjects.length}`);
366
+ console.log();
367
+
368
+ // Paths
369
+ console.log(`${colors.bold}Paths${colors.reset}`);
370
+ console.log(` Session file: ${colors.dim}${session.SESSION_FILE}${colors.reset}`);
371
+ console.log(` Config name: ${colors.dim}${session.LOCAL_CONFIG_NAME}${colors.reset}`);
372
+ }
373
+
374
+ /**
375
+ * Clear current project context
376
+ */
377
+ function clearContext() {
378
+ const current = session.getEffectiveProject();
379
+
380
+ if (!current) {
381
+ console.log(`${colors.dim}No project context to clear${colors.reset}`);
382
+ return;
383
+ }
384
+
385
+ // Clear session project
386
+ session.setCurrentProject(null);
387
+
388
+ console.log(`\n${colors.green}Cleared project context${colors.reset}`);
389
+ console.log(`${colors.dim}Previous project: ${current.name}${colors.reset}`);
390
+
391
+ // Check for local config
392
+ const localConfig = session.findLocalConfig();
393
+ if (localConfig) {
394
+ console.log(`\n${colors.yellow}Note:${colors.reset} Local config still exists at:`);
395
+ console.log(` ${localConfig._path}`);
396
+ console.log(`${colors.dim}Delete it manually if needed${colors.reset}`);
397
+ }
398
+ }
399
+
400
+ /**
401
+ * Show help
402
+ */
403
+ function showHelp() {
404
+ console.log(`
405
+ ${colors.cyan}${colors.bold}⚡ Bootspring Switch${colors.reset}
406
+ ${colors.dim}Switch between project contexts${colors.reset}
407
+
408
+ ${colors.bold}Usage:${colors.reset}
409
+ bootspring switch Show current and available projects
410
+ bootspring switch <project> Switch to a project by name or slug
411
+ bootspring switch recent Show recent projects
412
+ bootspring switch init Create .bootspring.json in current directory
413
+ bootspring switch --init Same as above
414
+ bootspring switch clear Clear current project context
415
+ bootspring switch status Show detailed context status
416
+
417
+ ${colors.bold}Options:${colors.reset}
418
+ --init Create local .bootspring.json config
419
+ --help, -h Show this help
420
+
421
+ ${colors.bold}Examples:${colors.reset}
422
+ bootspring switch my-project Switch to "my-project"
423
+ bootspring switch List all available projects
424
+ bootspring switch recent Show recently used projects
425
+ bootspring switch --init Link current directory to active project
426
+
427
+ ${colors.bold}Context Priority:${colors.reset}
428
+ 1. Local .bootspring.json (in current directory or ancestors)
429
+ 2. Session project (from 'bootspring switch <project>')
430
+ `);
431
+ }
432
+
433
+ /**
434
+ * Format relative time
435
+ */
436
+ function formatRelativeTime(isoDate) {
437
+ const date = new Date(isoDate);
438
+ const now = new Date();
439
+ const diffMs = now - date;
440
+ const diffSecs = Math.floor(diffMs / 1000);
441
+ const diffMins = Math.floor(diffSecs / 60);
442
+ const diffHours = Math.floor(diffMins / 60);
443
+ const diffDays = Math.floor(diffHours / 24);
444
+
445
+ if (diffSecs < 60) return 'just now';
446
+ if (diffMins < 60) return `${diffMins}m ago`;
447
+ if (diffHours < 24) return `${diffHours}h ago`;
448
+ if (diffDays < 7) return `${diffDays}d ago`;
449
+
450
+ return date.toLocaleDateString();
451
+ }
452
+
453
+ module.exports = { run };