@girardmedia/bootspring 2.0.21 → 2.0.23
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/bin/bootspring.js +5 -0
- package/cli/org.js +474 -0
- package/cli/preseed/index.js +16 -0
- package/cli/preseed/interactive.js +143 -0
- package/cli/preseed/templates.js +227 -0
- package/cli/preseed.js +9 -301
- package/cli/seed/builders/ai-context-builder.js +85 -0
- package/cli/seed/builders/index.js +13 -0
- package/cli/seed/builders/seed-builder.js +272 -0
- package/cli/seed/extractors/content-extractors.js +383 -0
- package/cli/seed/extractors/index.js +47 -0
- package/cli/seed/extractors/metadata-extractors.js +167 -0
- package/cli/seed/extractors/section-extractor.js +54 -0
- package/cli/seed/extractors/stack-extractors.js +228 -0
- package/cli/seed/index.js +18 -0
- package/cli/seed/utils/folder-structure.js +84 -0
- package/cli/seed/utils/index.js +11 -0
- package/cli/seed.js +23 -1074
- package/core/api-client.js +77 -0
- package/core/entitlements.js +36 -0
- package/core/organizations.js +223 -0
- package/core/policies.js +51 -6
- package/core/policy-matrix.js +303 -0
- package/core/project-context.js +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.js +3220 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/context-McpJQa_2.d.ts +5710 -0
- package/dist/core/index.d.ts +635 -0
- package/dist/core/index.js +2593 -0
- package/dist/core/index.js.map +1 -0
- package/dist/index-QqbeEiDm.d.ts +857 -0
- package/dist/index-UiYCgwiH.d.ts +174 -0
- package/dist/index.d.ts +453 -0
- package/dist/index.js +44228 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/index.d.ts +1 -0
- package/dist/mcp/index.js +41173 -0
- package/dist/mcp/index.js.map +1 -0
- package/generators/index.ts +82 -0
- package/intelligence/orchestrator/config/failure-signatures.js +48 -0
- package/intelligence/orchestrator/config/index.js +23 -0
- package/intelligence/orchestrator/config/pack-lifecycle.js +262 -0
- package/intelligence/orchestrator/config/phases.js +111 -0
- package/intelligence/orchestrator/config/remediation.js +150 -0
- package/intelligence/orchestrator/config/workflows.js +168 -0
- package/intelligence/orchestrator/core/index.js +16 -0
- package/intelligence/orchestrator/core/state-manager.js +88 -0
- package/intelligence/orchestrator/core/telemetry.js +24 -0
- package/intelligence/orchestrator/index.js +17 -0
- package/intelligence/orchestrator.js +17 -512
- package/mcp/contracts/mcp-contract.v1.json +1 -1
- package/package.json +16 -3
- package/src/cli/agent.ts +703 -0
- package/src/cli/analyze.ts +640 -0
- package/src/cli/audit.ts +707 -0
- package/src/cli/auth.ts +930 -0
- package/src/cli/billing.ts +364 -0
- package/src/cli/build.ts +1089 -0
- package/src/cli/business.ts +508 -0
- package/src/cli/checkpoint-utils.ts +236 -0
- package/src/cli/checkpoint.ts +757 -0
- package/src/cli/cloud-sync.ts +534 -0
- package/src/cli/content.ts +273 -0
- package/src/cli/context.ts +667 -0
- package/src/cli/dashboard.ts +133 -0
- package/src/cli/deploy.ts +704 -0
- package/src/cli/doctor.ts +480 -0
- package/src/cli/fundraise.ts +494 -0
- package/src/cli/generate.ts +346 -0
- package/src/cli/github-cmd.ts +566 -0
- package/src/cli/health.ts +599 -0
- package/src/cli/index.ts +113 -0
- package/src/cli/init.ts +838 -0
- package/src/cli/legal.ts +495 -0
- package/src/cli/log.ts +316 -0
- package/src/cli/loop.ts +1660 -0
- package/src/cli/manager.ts +878 -0
- package/src/cli/mcp.ts +275 -0
- package/src/cli/memory.ts +346 -0
- package/src/cli/metrics.ts +590 -0
- package/src/cli/monitor.ts +960 -0
- package/src/cli/mvp.ts +662 -0
- package/src/cli/onboard.ts +663 -0
- package/src/cli/orchestrator.ts +622 -0
- package/src/cli/plugin.ts +483 -0
- package/src/cli/prd.ts +671 -0
- package/src/cli/preseed-start.ts +1633 -0
- package/src/cli/preseed.ts +2434 -0
- package/src/cli/project.ts +526 -0
- package/src/cli/quality.ts +885 -0
- package/src/cli/security.ts +1079 -0
- package/src/cli/seed.ts +1224 -0
- package/src/cli/skill.ts +537 -0
- package/src/cli/suggest.ts +1225 -0
- package/src/cli/switch.ts +518 -0
- package/src/cli/task.ts +780 -0
- package/src/cli/telemetry.ts +172 -0
- package/src/cli/todo.ts +627 -0
- package/src/cli/types.ts +15 -0
- package/src/cli/update.ts +334 -0
- package/src/cli/visualize.ts +609 -0
- package/src/cli/watch.ts +895 -0
- package/src/cli/workspace.ts +709 -0
- package/src/core/action-recorder.ts +673 -0
- package/src/core/analyze-workflow.ts +1453 -0
- package/src/core/api-client.ts +1120 -0
- package/src/core/audit-workflow.ts +1681 -0
- package/src/core/auth.ts +471 -0
- package/src/core/build-orchestrator.ts +509 -0
- package/src/core/build-state.ts +621 -0
- package/src/core/checkpoint-engine.ts +482 -0
- package/src/core/config.ts +1285 -0
- package/src/core/context-loader.ts +694 -0
- package/src/core/context.ts +410 -0
- package/src/core/deploy-workflow.ts +1085 -0
- package/src/core/entitlements.ts +322 -0
- package/src/core/github-sync.ts +720 -0
- package/src/core/index.ts +981 -0
- package/src/core/ingest.ts +1186 -0
- package/src/core/metrics-engine.ts +886 -0
- package/src/core/mvp.ts +847 -0
- package/src/core/onboard-workflow.ts +1293 -0
- package/src/core/policies.ts +81 -0
- package/src/core/preseed-workflow.ts +1163 -0
- package/src/core/preseed.ts +1826 -0
- package/src/core/project-context.ts +380 -0
- package/src/core/project-state.ts +699 -0
- package/src/core/r2-sync.ts +691 -0
- package/src/core/scaffold.ts +1715 -0
- package/src/core/session.ts +286 -0
- package/src/core/task-extractor.ts +799 -0
- package/src/core/telemetry.ts +371 -0
- package/src/core/tier-enforcement.ts +737 -0
- package/src/core/utils.ts +437 -0
- package/src/index.ts +29 -0
- package/src/intelligence/agent-collab.ts +2376 -0
- package/src/intelligence/auto-suggest.ts +713 -0
- package/src/intelligence/content-gen.ts +1351 -0
- package/src/intelligence/cross-project.ts +1692 -0
- package/src/intelligence/git-memory.ts +529 -0
- package/src/intelligence/index.ts +318 -0
- package/src/intelligence/orchestrator.ts +534 -0
- package/src/intelligence/prd.ts +466 -0
- package/src/intelligence/recommendations.ts +982 -0
- package/src/intelligence/workflow-composer.ts +1472 -0
- package/src/mcp/capabilities.ts +233 -0
- package/src/mcp/index.ts +37 -0
- package/src/mcp/registry.ts +1268 -0
- package/src/mcp/response-formatter.ts +797 -0
- package/src/mcp/server.ts +240 -0
- package/src/types/agent.ts +69 -0
- package/src/types/config.ts +86 -0
- package/src/types/context.ts +77 -0
- package/src/types/index.ts +53 -0
- package/src/types/mcp.ts +91 -0
- package/src/types/skills.ts +47 -0
- package/src/types/workflow.ts +155 -0
- package/generators/index.js +0 -18
|
@@ -0,0 +1,518 @@
|
|
|
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
|
+
* @module cli/switch
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import * as https from 'https';
|
|
19
|
+
import * as http from 'http';
|
|
20
|
+
import type { IncomingMessage, ClientRequest } from 'http';
|
|
21
|
+
|
|
22
|
+
// Import JS modules with type interfaces
|
|
23
|
+
interface AuthModule {
|
|
24
|
+
getApiKey(): string | null;
|
|
25
|
+
getToken(): string | null;
|
|
26
|
+
getDeviceId(): string;
|
|
27
|
+
isAuthenticated(): boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface Project {
|
|
31
|
+
id: string;
|
|
32
|
+
name: string;
|
|
33
|
+
slug?: string;
|
|
34
|
+
lastUsed?: string;
|
|
35
|
+
source?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface SessionState {
|
|
39
|
+
project: Project | null;
|
|
40
|
+
source: string;
|
|
41
|
+
hasLocalConfig: boolean;
|
|
42
|
+
localConfigPath?: string;
|
|
43
|
+
hasSession: boolean;
|
|
44
|
+
lastUpdated?: string;
|
|
45
|
+
recentProjects: Project[];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface LocalConfig {
|
|
49
|
+
_path: string;
|
|
50
|
+
_dir: string;
|
|
51
|
+
projectId?: string;
|
|
52
|
+
projectName?: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
interface SessionModule {
|
|
56
|
+
getEffectiveProject(): Project | null;
|
|
57
|
+
setCurrentProject(project: Project | null): void;
|
|
58
|
+
addRecentProject(project: Project): void;
|
|
59
|
+
getRecentProjects(): Project[];
|
|
60
|
+
getSessionState(): SessionState;
|
|
61
|
+
findLocalConfig(dir?: string): LocalConfig | null;
|
|
62
|
+
createLocalConfig(dir: string, project: Project): string;
|
|
63
|
+
SESSION_FILE: string;
|
|
64
|
+
LOCAL_CONFIG_NAME: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
interface ParsedArgs {
|
|
68
|
+
_: string[];
|
|
69
|
+
init?: boolean;
|
|
70
|
+
help?: boolean;
|
|
71
|
+
h?: boolean;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
75
|
+
const auth = require('../../core/auth') as AuthModule;
|
|
76
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
77
|
+
const session = require('../../core/session') as SessionModule;
|
|
78
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
79
|
+
const utils = require('../../core/utils') as typeof import('../core/utils');
|
|
80
|
+
|
|
81
|
+
const API_BASE = process.env.BOOTSPRING_API_URL || 'https://www.bootspring.com';
|
|
82
|
+
|
|
83
|
+
interface ApiResponse {
|
|
84
|
+
projects?: Project[];
|
|
85
|
+
error?: string;
|
|
86
|
+
message?: string;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Make a direct API request (without v1 prefix)
|
|
91
|
+
*/
|
|
92
|
+
async function directRequest(method: string, path: string): Promise<ApiResponse> {
|
|
93
|
+
const apiKey = auth.getApiKey();
|
|
94
|
+
const token = auth.getToken();
|
|
95
|
+
const deviceId = auth.getDeviceId();
|
|
96
|
+
const url = new URL(`/api${path}`, API_BASE);
|
|
97
|
+
const isHttps = url.protocol === 'https:';
|
|
98
|
+
const httpModule = isHttps ? https : http;
|
|
99
|
+
|
|
100
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
101
|
+
const pkg = require('../../package.json') as { version: string };
|
|
102
|
+
|
|
103
|
+
const headers: Record<string, string> = {
|
|
104
|
+
'Content-Type': 'application/json',
|
|
105
|
+
'User-Agent': `bootspring-cli/${pkg.version}`,
|
|
106
|
+
'X-Device-Id': deviceId
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
if (apiKey) {
|
|
110
|
+
headers['X-API-Key'] = apiKey;
|
|
111
|
+
} else if (token) {
|
|
112
|
+
headers['Authorization'] = `Bearer ${token}`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return new Promise((resolve, reject) => {
|
|
116
|
+
const req: ClientRequest = httpModule.request(url, {
|
|
117
|
+
method,
|
|
118
|
+
headers,
|
|
119
|
+
timeout: 30000
|
|
120
|
+
}, (res: IncomingMessage) => {
|
|
121
|
+
let body = '';
|
|
122
|
+
res.on('data', (chunk: Buffer | string) => body += chunk);
|
|
123
|
+
res.on('end', () => {
|
|
124
|
+
try {
|
|
125
|
+
const json = JSON.parse(body) as ApiResponse;
|
|
126
|
+
if (res.statusCode && res.statusCode >= 400) {
|
|
127
|
+
const error = new Error(json.error || json.message || 'API Error') as Error & { status?: number };
|
|
128
|
+
error.status = res.statusCode;
|
|
129
|
+
reject(error);
|
|
130
|
+
} else {
|
|
131
|
+
resolve(json);
|
|
132
|
+
}
|
|
133
|
+
} catch {
|
|
134
|
+
if (res.statusCode && res.statusCode >= 400) {
|
|
135
|
+
reject(new Error(`API Error: ${res.statusCode}`));
|
|
136
|
+
} else {
|
|
137
|
+
resolve({} as ApiResponse);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
req.on('error', reject);
|
|
144
|
+
req.on('timeout', () => {
|
|
145
|
+
req.destroy();
|
|
146
|
+
reject(new Error('Request timeout'));
|
|
147
|
+
});
|
|
148
|
+
req.end();
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ANSI colors
|
|
153
|
+
const colors = {
|
|
154
|
+
reset: '\x1b[0m',
|
|
155
|
+
bold: '\x1b[1m',
|
|
156
|
+
dim: '\x1b[2m',
|
|
157
|
+
green: '\x1b[32m',
|
|
158
|
+
yellow: '\x1b[33m',
|
|
159
|
+
red: '\x1b[31m',
|
|
160
|
+
cyan: '\x1b[36m'
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Run switch command
|
|
165
|
+
*/
|
|
166
|
+
export async function run(args: string[]): Promise<void> {
|
|
167
|
+
const parsedArgs = utils.parseArgs(args) as ParsedArgs;
|
|
168
|
+
const subcommand = parsedArgs._[0];
|
|
169
|
+
|
|
170
|
+
// Handle --init flag anywhere
|
|
171
|
+
if (parsedArgs.init) {
|
|
172
|
+
if (!auth.isAuthenticated()) {
|
|
173
|
+
console.log(`${colors.yellow}Not logged in${colors.reset}`);
|
|
174
|
+
console.log(`${colors.dim}Run 'bootspring auth login' first${colors.reset}`);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
await initLocalConfig();
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Handle --help flag
|
|
182
|
+
if (parsedArgs.help || parsedArgs.h || subcommand === 'help') {
|
|
183
|
+
showHelp();
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Handle subcommands that don't require auth (local data only)
|
|
188
|
+
if (subcommand === 'status') {
|
|
189
|
+
showStatus();
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (subcommand === 'clear') {
|
|
194
|
+
clearContext();
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (subcommand === 'recent') {
|
|
199
|
+
showRecent();
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Commands that require authentication
|
|
204
|
+
if (!auth.isAuthenticated()) {
|
|
205
|
+
console.log(`${colors.yellow}Not logged in${colors.reset}`);
|
|
206
|
+
console.log(`${colors.dim}Run 'bootspring auth login' first${colors.reset}`);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (subcommand === 'init') {
|
|
211
|
+
await initLocalConfig();
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// If no subcommand or unknown, treat as project identifier or show projects
|
|
216
|
+
if (!subcommand) {
|
|
217
|
+
await showProjects();
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Switch to specified project
|
|
222
|
+
await switchTo(subcommand);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Show current project and list available projects
|
|
227
|
+
*/
|
|
228
|
+
async function showProjects(): Promise<void> {
|
|
229
|
+
console.log(`\n${colors.bold}Project Context${colors.reset}\n`);
|
|
230
|
+
|
|
231
|
+
// Show current project
|
|
232
|
+
const current = session.getEffectiveProject();
|
|
233
|
+
if (current) {
|
|
234
|
+
console.log(`${colors.cyan}Current:${colors.reset} ${colors.bold}${current.name}${colors.reset}`);
|
|
235
|
+
if (current.slug) {
|
|
236
|
+
console.log(`${colors.dim} ${current.slug}${colors.reset}`);
|
|
237
|
+
}
|
|
238
|
+
const sourceLabel = current.source === 'local' ? '(from .bootspring.json)' : '(from session)';
|
|
239
|
+
console.log(`${colors.dim} ${sourceLabel}${colors.reset}`);
|
|
240
|
+
console.log();
|
|
241
|
+
} else {
|
|
242
|
+
console.log(`${colors.yellow}No project selected${colors.reset}\n`);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Fetch available projects
|
|
246
|
+
console.log(`${colors.dim}Fetching projects...${colors.reset}`);
|
|
247
|
+
|
|
248
|
+
try {
|
|
249
|
+
const response = await directRequest('GET', '/projects');
|
|
250
|
+
const projects = response.projects || [];
|
|
251
|
+
|
|
252
|
+
if (projects.length === 0) {
|
|
253
|
+
console.log(`\n${colors.yellow}No projects found${colors.reset}`);
|
|
254
|
+
console.log(`${colors.dim}Create a project at https://bootspring.com/dashboard/projects${colors.reset}`);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
console.log(`\n${colors.bold}Available Projects${colors.reset}\n`);
|
|
259
|
+
for (const project of projects) {
|
|
260
|
+
const isCurrent = current?.id === project.id;
|
|
261
|
+
const marker = isCurrent ? colors.green + '* ' + colors.reset : ' ';
|
|
262
|
+
console.log(`${marker}${colors.bold}${project.name}${colors.reset} ${colors.dim}(${project.slug})${colors.reset}`);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
console.log(`\n${colors.dim}Usage: bootspring switch <project-name-or-slug>${colors.reset}`);
|
|
266
|
+
} catch (error) {
|
|
267
|
+
console.log(`\n${colors.red}Failed to fetch projects: ${(error as Error).message}${colors.reset}`);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Switch to a specific project
|
|
273
|
+
*/
|
|
274
|
+
async function switchTo(identifier: string): Promise<void> {
|
|
275
|
+
console.log(`${colors.dim}Switching to project...${colors.reset}`);
|
|
276
|
+
|
|
277
|
+
try {
|
|
278
|
+
const response = await directRequest('GET', '/projects');
|
|
279
|
+
const projects = response.projects || [];
|
|
280
|
+
|
|
281
|
+
// Find project by name or slug
|
|
282
|
+
const project = projects.find(
|
|
283
|
+
p => p.name.toLowerCase() === identifier.toLowerCase() ||
|
|
284
|
+
p.slug === identifier.toLowerCase()
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
if (!project) {
|
|
288
|
+
console.log(`\n${colors.red}Project not found: ${identifier}${colors.reset}`);
|
|
289
|
+
console.log(`${colors.dim}Run 'bootspring switch' to see available projects${colors.reset}`);
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Update session
|
|
294
|
+
session.setCurrentProject(project);
|
|
295
|
+
session.addRecentProject(project);
|
|
296
|
+
|
|
297
|
+
console.log(`\n${colors.green}Switched to: ${project.name}${colors.reset}`);
|
|
298
|
+
|
|
299
|
+
// Automatically create/update local config to lock this directory to the project
|
|
300
|
+
const existingConfig = session.findLocalConfig();
|
|
301
|
+
if (existingConfig && existingConfig._dir === process.cwd()) {
|
|
302
|
+
// Update existing config in same directory
|
|
303
|
+
try {
|
|
304
|
+
session.createLocalConfig(process.cwd(), project);
|
|
305
|
+
console.log(`${colors.dim}Updated: ${existingConfig._path}${colors.reset}`);
|
|
306
|
+
} catch {
|
|
307
|
+
// Ignore errors
|
|
308
|
+
}
|
|
309
|
+
} else if (!existingConfig) {
|
|
310
|
+
// Create new local config to lock this directory
|
|
311
|
+
try {
|
|
312
|
+
const configPath = session.createLocalConfig(process.cwd(), project);
|
|
313
|
+
console.log(`${colors.dim}Locked directory to "${project.name}"${colors.reset}`);
|
|
314
|
+
console.log(`${colors.dim}Config: ${configPath}${colors.reset}`);
|
|
315
|
+
} catch {
|
|
316
|
+
// Ignore errors - some directories may not be writable
|
|
317
|
+
}
|
|
318
|
+
} else {
|
|
319
|
+
// Config exists in parent directory
|
|
320
|
+
console.log(`${colors.dim}Note: Parent directory locked to "${existingConfig.projectName}"${colors.reset}`);
|
|
321
|
+
console.log(`${colors.dim}Run 'bootspring switch --init' here to override${colors.reset}`);
|
|
322
|
+
}
|
|
323
|
+
} catch (error) {
|
|
324
|
+
console.log(`\n${colors.red}Failed to switch project: ${(error as Error).message}${colors.reset}`);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Show recent projects and allow quick switching
|
|
330
|
+
*/
|
|
331
|
+
function showRecent(): void {
|
|
332
|
+
const recent = session.getRecentProjects();
|
|
333
|
+
|
|
334
|
+
console.log(`\n${colors.bold}Recent Projects${colors.reset}\n`);
|
|
335
|
+
|
|
336
|
+
if (recent.length === 0) {
|
|
337
|
+
console.log(`${colors.dim}No recent projects${colors.reset}`);
|
|
338
|
+
console.log(`${colors.dim}Use 'bootspring switch <project>' to switch to a project${colors.reset}`);
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const current = session.getEffectiveProject();
|
|
343
|
+
|
|
344
|
+
for (let i = 0; i < recent.length; i++) {
|
|
345
|
+
const project = recent[i];
|
|
346
|
+
if (!project) continue;
|
|
347
|
+
const isCurrent = current?.id === project.id;
|
|
348
|
+
const marker = isCurrent ? colors.green + '* ' + colors.reset : ' ';
|
|
349
|
+
const index = colors.dim + `[${i + 1}]` + colors.reset;
|
|
350
|
+
const lastUsed = project.lastUsed ? formatRelativeTime(project.lastUsed) : '';
|
|
351
|
+
|
|
352
|
+
console.log(`${marker}${index} ${colors.bold}${project.name}${colors.reset} ${colors.dim}(${project.slug})${colors.reset}`);
|
|
353
|
+
if (lastUsed) {
|
|
354
|
+
console.log(` ${colors.dim}${lastUsed}${colors.reset}`);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
console.log(`\n${colors.dim}Usage: bootspring switch <name-or-number>${colors.reset}`);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Initialize local .bootspring.json config
|
|
363
|
+
*/
|
|
364
|
+
async function initLocalConfig(): Promise<void> {
|
|
365
|
+
const current = session.getEffectiveProject();
|
|
366
|
+
|
|
367
|
+
if (!current) {
|
|
368
|
+
console.log(`${colors.yellow}No project selected${colors.reset}`);
|
|
369
|
+
console.log(`${colors.dim}First switch to a project: bootspring switch <project>${colors.reset}`);
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Check if local config already exists
|
|
374
|
+
const existingConfig = session.findLocalConfig(process.cwd());
|
|
375
|
+
if (existingConfig && existingConfig._dir === process.cwd()) {
|
|
376
|
+
console.log(`${colors.yellow}Local config already exists${colors.reset}`);
|
|
377
|
+
console.log(`${colors.dim}Path: ${existingConfig._path}${colors.reset}`);
|
|
378
|
+
console.log(`${colors.dim}Project: ${existingConfig.projectName || existingConfig.projectId}${colors.reset}`);
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Create local config
|
|
383
|
+
try {
|
|
384
|
+
const configPath = session.createLocalConfig(process.cwd(), current);
|
|
385
|
+
console.log(`\n${colors.green}Created local config${colors.reset}`);
|
|
386
|
+
console.log(`${colors.dim}Path: ${configPath}${colors.reset}`);
|
|
387
|
+
console.log(`${colors.dim}Project: ${current.name}${colors.reset}`);
|
|
388
|
+
console.log(`\n${colors.dim}This directory is now linked to project "${current.name}"${colors.reset}`);
|
|
389
|
+
} catch (error) {
|
|
390
|
+
console.log(`${colors.red}Failed to create config: ${(error as Error).message}${colors.reset}`);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Show detailed context status
|
|
396
|
+
*/
|
|
397
|
+
function showStatus(): void {
|
|
398
|
+
const state = session.getSessionState();
|
|
399
|
+
|
|
400
|
+
console.log(`\n${colors.bold}${colors.cyan}⚡ Project Context Status${colors.reset}\n`);
|
|
401
|
+
|
|
402
|
+
// Current project
|
|
403
|
+
console.log(`${colors.bold}Current Project${colors.reset}`);
|
|
404
|
+
if (state.project) {
|
|
405
|
+
console.log(` Name: ${colors.bold}${state.project.name}${colors.reset}`);
|
|
406
|
+
if (state.project.slug) {
|
|
407
|
+
console.log(` Slug: ${state.project.slug}`);
|
|
408
|
+
}
|
|
409
|
+
console.log(` ID: ${colors.dim}${state.project.id}${colors.reset}`);
|
|
410
|
+
console.log(` Source: ${state.source === 'local' ? colors.green + 'local config' : colors.cyan + 'session'}${colors.reset}`);
|
|
411
|
+
} else {
|
|
412
|
+
console.log(` ${colors.yellow}None selected${colors.reset}`);
|
|
413
|
+
}
|
|
414
|
+
console.log();
|
|
415
|
+
|
|
416
|
+
// Local config
|
|
417
|
+
console.log(`${colors.bold}Local Config${colors.reset}`);
|
|
418
|
+
if (state.hasLocalConfig) {
|
|
419
|
+
console.log(` ${colors.green}●${colors.reset} Found: ${state.localConfigPath}`);
|
|
420
|
+
} else {
|
|
421
|
+
console.log(` ${colors.dim}○ Not found in current directory or ancestors${colors.reset}`);
|
|
422
|
+
console.log(` ${colors.dim}Run 'bootspring switch --init' to create one${colors.reset}`);
|
|
423
|
+
}
|
|
424
|
+
console.log();
|
|
425
|
+
|
|
426
|
+
// Session
|
|
427
|
+
console.log(`${colors.bold}Session${colors.reset}`);
|
|
428
|
+
console.log(` Status: ${state.hasSession ? colors.green + 'active' : colors.dim + 'inactive'}${colors.reset}`);
|
|
429
|
+
if (state.lastUpdated) {
|
|
430
|
+
console.log(` Last updated: ${formatRelativeTime(state.lastUpdated)}`);
|
|
431
|
+
}
|
|
432
|
+
console.log(` Recent projects: ${state.recentProjects.length}`);
|
|
433
|
+
console.log();
|
|
434
|
+
|
|
435
|
+
// Paths
|
|
436
|
+
console.log(`${colors.bold}Paths${colors.reset}`);
|
|
437
|
+
console.log(` Session file: ${colors.dim}${session.SESSION_FILE}${colors.reset}`);
|
|
438
|
+
console.log(` Config name: ${colors.dim}${session.LOCAL_CONFIG_NAME}${colors.reset}`);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Clear current project context
|
|
443
|
+
*/
|
|
444
|
+
function clearContext(): void {
|
|
445
|
+
const current = session.getEffectiveProject();
|
|
446
|
+
|
|
447
|
+
if (!current) {
|
|
448
|
+
console.log(`${colors.dim}No project context to clear${colors.reset}`);
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// Clear session project
|
|
453
|
+
session.setCurrentProject(null);
|
|
454
|
+
|
|
455
|
+
console.log(`\n${colors.green}Cleared project context${colors.reset}`);
|
|
456
|
+
console.log(`${colors.dim}Previous project: ${current.name}${colors.reset}`);
|
|
457
|
+
|
|
458
|
+
// Check for local config
|
|
459
|
+
const localConfig = session.findLocalConfig();
|
|
460
|
+
if (localConfig) {
|
|
461
|
+
console.log(`\n${colors.yellow}Note:${colors.reset} Local config still exists at:`);
|
|
462
|
+
console.log(` ${localConfig._path}`);
|
|
463
|
+
console.log(`${colors.dim}Delete it manually if needed${colors.reset}`);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Show help
|
|
469
|
+
*/
|
|
470
|
+
function showHelp(): void {
|
|
471
|
+
console.log(`
|
|
472
|
+
${colors.cyan}${colors.bold}⚡ Bootspring Switch${colors.reset}
|
|
473
|
+
${colors.dim}Switch between project contexts${colors.reset}
|
|
474
|
+
|
|
475
|
+
${colors.bold}Usage:${colors.reset}
|
|
476
|
+
bootspring switch Show current and available projects
|
|
477
|
+
bootspring switch <project> Switch to a project by name or slug
|
|
478
|
+
bootspring switch recent Show recent projects
|
|
479
|
+
bootspring switch init Create .bootspring.json in current directory
|
|
480
|
+
bootspring switch --init Same as above
|
|
481
|
+
bootspring switch clear Clear current project context
|
|
482
|
+
bootspring switch status Show detailed context status
|
|
483
|
+
|
|
484
|
+
${colors.bold}Options:${colors.reset}
|
|
485
|
+
--init Create local .bootspring.json config
|
|
486
|
+
--help, -h Show this help
|
|
487
|
+
|
|
488
|
+
${colors.bold}Examples:${colors.reset}
|
|
489
|
+
bootspring switch my-project Switch to "my-project"
|
|
490
|
+
bootspring switch List all available projects
|
|
491
|
+
bootspring switch recent Show recently used projects
|
|
492
|
+
bootspring switch --init Link current directory to active project
|
|
493
|
+
|
|
494
|
+
${colors.bold}Context Priority:${colors.reset}
|
|
495
|
+
1. Local .bootspring.json (in current directory or ancestors)
|
|
496
|
+
2. Session project (from 'bootspring switch <project>')
|
|
497
|
+
`);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Format relative time
|
|
502
|
+
*/
|
|
503
|
+
function formatRelativeTime(isoDate: string): string {
|
|
504
|
+
const date = new Date(isoDate);
|
|
505
|
+
const now = new Date();
|
|
506
|
+
const diffMs = now.getTime() - date.getTime();
|
|
507
|
+
const diffSecs = Math.floor(diffMs / 1000);
|
|
508
|
+
const diffMins = Math.floor(diffSecs / 60);
|
|
509
|
+
const diffHours = Math.floor(diffMins / 60);
|
|
510
|
+
const diffDays = Math.floor(diffHours / 24);
|
|
511
|
+
|
|
512
|
+
if (diffSecs < 60) return 'just now';
|
|
513
|
+
if (diffMins < 60) return `${diffMins}m ago`;
|
|
514
|
+
if (diffHours < 24) return `${diffHours}h ago`;
|
|
515
|
+
if (diffDays < 7) return `${diffDays}d ago`;
|
|
516
|
+
|
|
517
|
+
return date.toLocaleDateString();
|
|
518
|
+
}
|