@devobsessed/code-captain 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +214 -0
- package/bin/install.js +1048 -0
- package/claude-code/README.md +276 -0
- package/claude-code/agents/code-captain.md +121 -0
- package/claude-code/agents/spec-generator.md +271 -0
- package/claude-code/agents/story-creator.md +309 -0
- package/claude-code/agents/tech-spec.md +440 -0
- package/claude-code/commands/cc-initialize.md +520 -0
- package/copilot/README.md +210 -0
- package/copilot/chatmodes/Code Captain.chatmode.md +60 -0
- package/copilot/docs/best-practices.md +74 -0
- package/copilot/prompts/create-adr.prompt.md +468 -0
- package/copilot/prompts/create-spec.prompt.md +430 -0
- package/copilot/prompts/edit-spec.prompt.md +396 -0
- package/copilot/prompts/execute-task.prompt.md +144 -0
- package/copilot/prompts/explain-code.prompt.md +292 -0
- package/copilot/prompts/initialize.prompt.md +65 -0
- package/copilot/prompts/new-command.prompt.md +310 -0
- package/copilot/prompts/plan-product.prompt.md +450 -0
- package/copilot/prompts/research.prompt.md +329 -0
- package/copilot/prompts/status.prompt.md +424 -0
- package/copilot/prompts/swab.prompt.md +217 -0
- package/cursor/README.md +224 -0
- package/cursor/cc.md +183 -0
- package/cursor/cc.mdc +69 -0
- package/cursor/commands/create-adr.md +504 -0
- package/cursor/commands/create-spec.md +430 -0
- package/cursor/commands/edit-spec.md +405 -0
- package/cursor/commands/execute-task.md +514 -0
- package/cursor/commands/explain-code.md +289 -0
- package/cursor/commands/initialize.md +397 -0
- package/cursor/commands/new-command.md +312 -0
- package/cursor/commands/plan-product.md +466 -0
- package/cursor/commands/research.md +317 -0
- package/cursor/commands/status.md +413 -0
- package/cursor/commands/swab.md +209 -0
- package/cursor/docs/best-practices.md +74 -0
- package/cursor/integrations/azure-devops/create-azure-work-items.md +403 -0
- package/cursor/integrations/azure-devops/sync-azure-work-items.md +486 -0
- package/cursor/integrations/github/create-github-issues.md +765 -0
- package/cursor/integrations/github/scripts/create-issues-batch.sh +272 -0
- package/cursor/integrations/github/sync-github-issues.md +237 -0
- package/cursor/integrations/github/sync.md +305 -0
- package/manifest.json +381 -0
- package/package.json +58 -0
- package/windsurf/README.md +254 -0
- package/windsurf/rules/cc.md +5 -0
- package/windsurf/workflows/create-adr.md +331 -0
- package/windsurf/workflows/create-spec.md +280 -0
- package/windsurf/workflows/edit-spec.md +273 -0
- package/windsurf/workflows/execute-task.md +276 -0
- package/windsurf/workflows/explain-code.md +292 -0
- package/windsurf/workflows/initialize.md +298 -0
- package/windsurf/workflows/new-command.md +321 -0
- package/windsurf/workflows/status.md +213 -0
package/bin/install.js
ADDED
|
@@ -0,0 +1,1048 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import boxen from 'boxen';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import { spawn } from 'cross-spawn';
|
|
6
|
+
import fs from 'fs-extra';
|
|
7
|
+
import inquirer from 'inquirer';
|
|
8
|
+
import fetch from 'node-fetch';
|
|
9
|
+
import ora from 'ora';
|
|
10
|
+
import path, { dirname } from 'path';
|
|
11
|
+
import { fileURLToPath } from 'url';
|
|
12
|
+
|
|
13
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
14
|
+
const __dirname = dirname(__filename);
|
|
15
|
+
|
|
16
|
+
class CodeCaptainInstaller {
|
|
17
|
+
constructor() {
|
|
18
|
+
this.config = {
|
|
19
|
+
repoUrl: 'https://github.com/devobsessed/code-captain',
|
|
20
|
+
baseUrl: 'https://raw.githubusercontent.com/devobsessed/code-captain/main',
|
|
21
|
+
version: 'main',
|
|
22
|
+
localSource: process.env.CC_LOCAL_SOURCE || null,
|
|
23
|
+
versionFile: '.code-captain/.version',
|
|
24
|
+
manifestFile: '.code-captain/.manifest.json'
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
this.ides = {
|
|
28
|
+
cursor: {
|
|
29
|
+
name: 'Cursor',
|
|
30
|
+
description: 'AI-first code editor with built-in AI agent capabilities',
|
|
31
|
+
details: 'Uses .code-captain/ structure + .cursor/rules/cc.mdc'
|
|
32
|
+
},
|
|
33
|
+
copilot: {
|
|
34
|
+
name: 'VS Code with GitHub Copilot',
|
|
35
|
+
description: 'Visual Studio Code with GitHub Copilot extension',
|
|
36
|
+
details: 'Uses .github/chatmodes/ + .github/prompts/ + .code-captain/docs/'
|
|
37
|
+
},
|
|
38
|
+
windsurf: {
|
|
39
|
+
name: 'Windsurf',
|
|
40
|
+
description: 'Codeium\'s AI-powered development environment',
|
|
41
|
+
details: 'Uses windsurf/rules/ for custom workflows'
|
|
42
|
+
},
|
|
43
|
+
claude: {
|
|
44
|
+
name: 'Claude Code',
|
|
45
|
+
description: 'Direct integration with Claude for development workflows',
|
|
46
|
+
details: 'Uses .code-captain/claude/ structure with agents and commands'
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Display welcome banner
|
|
52
|
+
showWelcome() {
|
|
53
|
+
const banner = boxen(
|
|
54
|
+
chalk.bold.green('Code Captain 2.0') + '\n' +
|
|
55
|
+
chalk.gray('Unified AI Development Agent System') + '\n\n' +
|
|
56
|
+
chalk.blue('š Interactive Installation Wizard'),
|
|
57
|
+
{
|
|
58
|
+
padding: 1,
|
|
59
|
+
margin: 1,
|
|
60
|
+
borderStyle: 'round',
|
|
61
|
+
borderColor: 'green',
|
|
62
|
+
textAlignment: 'center'
|
|
63
|
+
}
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
console.log(banner);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Check system compatibility
|
|
70
|
+
async checkCompatibility() {
|
|
71
|
+
const spinner = ora('Checking system compatibility...').start();
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
// Check Node.js version
|
|
75
|
+
const nodeVersion = process.version;
|
|
76
|
+
const majorVersion = parseInt(nodeVersion.slice(1).split('.')[0]);
|
|
77
|
+
|
|
78
|
+
if (majorVersion < 16) {
|
|
79
|
+
spinner.fail(`Node.js ${nodeVersion} detected. Requires Node.js 16 or higher.`);
|
|
80
|
+
console.log(chalk.yellow('\nš¦ Please update Node.js:'));
|
|
81
|
+
console.log(chalk.blue(' Visit: https://nodejs.org/'));
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Check if we're in a Git repository
|
|
86
|
+
const isGitRepo = await fs.pathExists('.git');
|
|
87
|
+
|
|
88
|
+
// Check for existing Code Captain installations
|
|
89
|
+
const existingInstallations = await this.detectExistingInstallations();
|
|
90
|
+
|
|
91
|
+
spinner.succeed('System compatibility check passed');
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
nodeVersion,
|
|
95
|
+
isGitRepo,
|
|
96
|
+
existingInstallations
|
|
97
|
+
};
|
|
98
|
+
} catch (error) {
|
|
99
|
+
spinner.fail('Compatibility check failed');
|
|
100
|
+
console.error(chalk.red('Error:'), error.message);
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Detect existing Code Captain installations
|
|
106
|
+
async detectExistingInstallations() {
|
|
107
|
+
const installations = [];
|
|
108
|
+
|
|
109
|
+
// Define all possible Code Captain paths
|
|
110
|
+
const pathsToCheck = {
|
|
111
|
+
'Code Captain Core': ['.code-captain/'],
|
|
112
|
+
'Cursor Integration': ['.cursor/rules/cc.mdc', '.cursor/rules/'],
|
|
113
|
+
'Copilot Integration': ['.github/chatmodes/', '.github/prompts/'],
|
|
114
|
+
'Windsurf Integration': ['windsurf/rules/', 'windsurf/workflows/'],
|
|
115
|
+
'Claude Integration': ['.code-captain/claude/', 'claude-code/', '.claude/'],
|
|
116
|
+
'Legacy Structure': ['cursor/', 'copilot/', 'windsurf/']
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
for (const [name, paths] of Object.entries(pathsToCheck)) {
|
|
120
|
+
for (const path of paths) {
|
|
121
|
+
if (await fs.pathExists(path)) {
|
|
122
|
+
installations.push(name);
|
|
123
|
+
break; // Only add each installation type once
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return [...new Set(installations)]; // Remove duplicates
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Calculate SHA256 hash of a local file
|
|
132
|
+
async calculateFileHash(filePath) {
|
|
133
|
+
try {
|
|
134
|
+
const crypto = await import('crypto');
|
|
135
|
+
const content = await fs.readFile(filePath, 'utf8');
|
|
136
|
+
return crypto.default.createHash('sha256').update(content).digest('hex');
|
|
137
|
+
} catch (error) {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Get remote manifest with file versions/hashes
|
|
143
|
+
async getRemoteManifest() {
|
|
144
|
+
try {
|
|
145
|
+
const manifestUrl = `${this.config.baseUrl}/manifest.json`;
|
|
146
|
+
|
|
147
|
+
if (this.config.localSource) {
|
|
148
|
+
const localManifestPath = path.join(this.config.localSource, 'manifest.json');
|
|
149
|
+
if (await fs.pathExists(localManifestPath)) {
|
|
150
|
+
const content = await fs.readFile(localManifestPath, 'utf8');
|
|
151
|
+
return JSON.parse(content);
|
|
152
|
+
}
|
|
153
|
+
} else {
|
|
154
|
+
const response = await fetch(manifestUrl);
|
|
155
|
+
if (response.ok) {
|
|
156
|
+
return await response.json();
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Fallback: generate manifest from current commit
|
|
161
|
+
return await this.generateFallbackManifest();
|
|
162
|
+
} catch (error) {
|
|
163
|
+
console.warn(chalk.yellow('Warning: Could not fetch remote manifest, using fallback'));
|
|
164
|
+
return await this.generateFallbackManifest();
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Generate fallback manifest if remote manifest doesn't exist
|
|
169
|
+
async generateFallbackManifest() {
|
|
170
|
+
const timestamp = new Date().toISOString();
|
|
171
|
+
return {
|
|
172
|
+
version: this.config.version,
|
|
173
|
+
timestamp,
|
|
174
|
+
commit: 'unknown',
|
|
175
|
+
files: {} // Will be populated as files are downloaded
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Get local manifest if it exists
|
|
180
|
+
async getLocalManifest() {
|
|
181
|
+
try {
|
|
182
|
+
if (await fs.pathExists(this.config.manifestFile)) {
|
|
183
|
+
const content = await fs.readFile(this.config.manifestFile, 'utf8');
|
|
184
|
+
return JSON.parse(content);
|
|
185
|
+
}
|
|
186
|
+
} catch (error) {
|
|
187
|
+
console.warn(chalk.yellow('Warning: Could not read local manifest'));
|
|
188
|
+
}
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Compare manifests and detect changes
|
|
193
|
+
async detectChanges(selectedIDE) {
|
|
194
|
+
const spinner = ora('Analyzing file changes...').start();
|
|
195
|
+
|
|
196
|
+
try {
|
|
197
|
+
const [remoteManifest, localManifest] = await Promise.all([
|
|
198
|
+
this.getRemoteManifest(),
|
|
199
|
+
this.getLocalManifest()
|
|
200
|
+
]);
|
|
201
|
+
|
|
202
|
+
if (!localManifest) {
|
|
203
|
+
spinner.succeed('No previous manifest found - treating as fresh installation');
|
|
204
|
+
return {
|
|
205
|
+
isFirstInstall: true,
|
|
206
|
+
changes: [],
|
|
207
|
+
newFiles: [],
|
|
208
|
+
recommendations: ['Full installation recommended (no change tracking available)']
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const files = this.getIDEFiles(selectedIDE);
|
|
213
|
+
const changes = [];
|
|
214
|
+
const newFiles = [];
|
|
215
|
+
let filesAnalyzed = 0;
|
|
216
|
+
|
|
217
|
+
for (const file of files) {
|
|
218
|
+
const remotePath = file.source;
|
|
219
|
+
const localPath = file.target;
|
|
220
|
+
|
|
221
|
+
filesAnalyzed++;
|
|
222
|
+
spinner.text = `Analyzing changes... (${filesAnalyzed}/${files.length})`;
|
|
223
|
+
|
|
224
|
+
// Check if file exists locally
|
|
225
|
+
const localExists = await fs.pathExists(localPath);
|
|
226
|
+
const remoteFileInfo = remoteManifest.files?.[remotePath];
|
|
227
|
+
|
|
228
|
+
if (!localExists) {
|
|
229
|
+
newFiles.push({
|
|
230
|
+
file: remotePath,
|
|
231
|
+
component: file.component,
|
|
232
|
+
reason: 'File does not exist locally'
|
|
233
|
+
});
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Calculate actual hash of local file
|
|
238
|
+
const localFileHash = await this.calculateFileHash(localPath);
|
|
239
|
+
|
|
240
|
+
if (!localFileHash) {
|
|
241
|
+
// Can't read local file - treat as needs update
|
|
242
|
+
changes.push({
|
|
243
|
+
file: remotePath,
|
|
244
|
+
component: file.component,
|
|
245
|
+
reason: 'Unable to read local file'
|
|
246
|
+
});
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Compare with remote hash
|
|
251
|
+
if (remoteFileInfo && remoteFileInfo.hash) {
|
|
252
|
+
const remoteHash = remoteFileInfo.hash.replace('sha256:', '');
|
|
253
|
+
|
|
254
|
+
if (localFileHash !== remoteHash) {
|
|
255
|
+
changes.push({
|
|
256
|
+
file: remotePath,
|
|
257
|
+
component: file.component,
|
|
258
|
+
localVersion: localManifest.files?.[remotePath]?.version || 'unknown',
|
|
259
|
+
remoteVersion: remoteFileInfo.version || 'latest',
|
|
260
|
+
reason: 'File content has changed',
|
|
261
|
+
localHash: localFileHash.substring(0, 8),
|
|
262
|
+
remoteHash: remoteHash.substring(0, 8)
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
} else {
|
|
266
|
+
// No remote file info - treat as new in remote
|
|
267
|
+
newFiles.push({
|
|
268
|
+
file: remotePath,
|
|
269
|
+
component: file.component,
|
|
270
|
+
reason: 'New file in remote repository'
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const recommendations = this.generateUpdateRecommendations(changes, newFiles);
|
|
276
|
+
|
|
277
|
+
spinner.succeed(`Found ${changes.length} updated files, ${newFiles.length} new files`);
|
|
278
|
+
|
|
279
|
+
return {
|
|
280
|
+
isFirstInstall: false,
|
|
281
|
+
localVersion: localManifest.version,
|
|
282
|
+
remoteVersion: remoteManifest.version,
|
|
283
|
+
changes,
|
|
284
|
+
newFiles,
|
|
285
|
+
recommendations
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
} catch (error) {
|
|
289
|
+
spinner.fail('Could not analyze changes');
|
|
290
|
+
return {
|
|
291
|
+
isFirstInstall: false,
|
|
292
|
+
changes: [],
|
|
293
|
+
newFiles: [],
|
|
294
|
+
recommendations: ['Unable to detect changes - consider full update'],
|
|
295
|
+
error: error.message
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Generate smart update recommendations
|
|
301
|
+
generateUpdateRecommendations(changes, newFiles) {
|
|
302
|
+
const recommendations = [];
|
|
303
|
+
const changedComponents = new Set();
|
|
304
|
+
|
|
305
|
+
// Collect components with changes
|
|
306
|
+
[...changes, ...newFiles].forEach(item => {
|
|
307
|
+
if (item.component) {
|
|
308
|
+
changedComponents.add(item.component);
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
if (changedComponents.size === 0) {
|
|
313
|
+
recommendations.push('ā
All files are up to date!');
|
|
314
|
+
} else {
|
|
315
|
+
recommendations.push(`š¦ Recommended updates: ${Array.from(changedComponents).join(', ')}`);
|
|
316
|
+
|
|
317
|
+
// Specific recommendations
|
|
318
|
+
if (changedComponents.has('commands')) {
|
|
319
|
+
recommendations.push('š Commands updated - new features or bug fixes available');
|
|
320
|
+
}
|
|
321
|
+
if (changedComponents.has('rules')) {
|
|
322
|
+
recommendations.push('āļø Rules updated - improved AI agent behavior');
|
|
323
|
+
}
|
|
324
|
+
if (changedComponents.has('docs')) {
|
|
325
|
+
recommendations.push('š Documentation updated - check for new best practices');
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return recommendations;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Save manifest after successful installation
|
|
333
|
+
async saveManifest(remoteManifest, installedFiles) {
|
|
334
|
+
try {
|
|
335
|
+
const manifest = {
|
|
336
|
+
...remoteManifest,
|
|
337
|
+
installedAt: new Date().toISOString(),
|
|
338
|
+
files: {}
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
// Calculate actual hashes of installed files
|
|
342
|
+
for (const file of installedFiles) {
|
|
343
|
+
const localHash = await this.calculateFileHash(file.target);
|
|
344
|
+
|
|
345
|
+
if (localHash) {
|
|
346
|
+
// Use remote file info as base, but update with actual installed hash
|
|
347
|
+
const remoteFileInfo = remoteManifest.files?.[file.source] || {};
|
|
348
|
+
|
|
349
|
+
manifest.files[file.source] = {
|
|
350
|
+
...remoteFileInfo,
|
|
351
|
+
hash: `sha256:${localHash}`,
|
|
352
|
+
installedAt: new Date().toISOString(),
|
|
353
|
+
actualSize: (await fs.stat(file.target).catch(() => ({ size: 0 }))).size
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
await fs.ensureDir(path.dirname(this.config.manifestFile));
|
|
359
|
+
await fs.writeFile(this.config.manifestFile, JSON.stringify(manifest, null, 2));
|
|
360
|
+
|
|
361
|
+
return true;
|
|
362
|
+
} catch (error) {
|
|
363
|
+
console.warn(chalk.yellow(`Warning: Could not save manifest: ${error.message}`));
|
|
364
|
+
return false;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Auto-detect IDE preference
|
|
369
|
+
detectIDE() {
|
|
370
|
+
const detections = [];
|
|
371
|
+
|
|
372
|
+
// Check for Cursor
|
|
373
|
+
if (fs.pathExistsSync('.cursor') || this.commandExists('cursor')) {
|
|
374
|
+
detections.push('cursor');
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Check for VS Code
|
|
378
|
+
if (this.commandExists('code')) {
|
|
379
|
+
detections.push('copilot');
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Check for Windsurf
|
|
383
|
+
if (fs.pathExistsSync('windsurf') || this.commandExists('windsurf')) {
|
|
384
|
+
detections.push('windsurf');
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Check for Claude Code
|
|
388
|
+
if (fs.pathExistsSync('claude-code') || fs.pathExistsSync('.code-captain/claude') || fs.pathExistsSync('.claude')) {
|
|
389
|
+
detections.push('claude');
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return detections;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Check if command exists
|
|
396
|
+
commandExists(command) {
|
|
397
|
+
try {
|
|
398
|
+
const result = spawn.sync(command, ['--version'], {
|
|
399
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
400
|
+
timeout: 5000,
|
|
401
|
+
windowsHide: true
|
|
402
|
+
});
|
|
403
|
+
return result.status === 0;
|
|
404
|
+
} catch {
|
|
405
|
+
return false;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// IDE selection prompt
|
|
410
|
+
async selectIDE() {
|
|
411
|
+
const detected = this.detectIDE();
|
|
412
|
+
|
|
413
|
+
console.log('\n' + chalk.bold.blue('šÆ IDE Selection'));
|
|
414
|
+
console.log(chalk.gray('ā'.repeat(50)));
|
|
415
|
+
|
|
416
|
+
if (detected.length > 0) {
|
|
417
|
+
console.log(chalk.green(`\n⨠Auto-detected: ${detected.map(id => this.ides[id].name).join(', ')}`));
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const choices = Object.entries(this.ides).map(([key, ide]) => ({
|
|
421
|
+
name: ide.name,
|
|
422
|
+
value: key,
|
|
423
|
+
short: ide.name
|
|
424
|
+
}));
|
|
425
|
+
|
|
426
|
+
const { selectedIDE } = await inquirer.prompt([
|
|
427
|
+
{
|
|
428
|
+
type: 'list',
|
|
429
|
+
name: 'selectedIDE',
|
|
430
|
+
message: 'Which IDE/environment are you using?',
|
|
431
|
+
choices: choices,
|
|
432
|
+
pageSize: 6,
|
|
433
|
+
default: detected.length > 0 ? detected[0] : 'cursor'
|
|
434
|
+
}
|
|
435
|
+
]);
|
|
436
|
+
|
|
437
|
+
return selectedIDE;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Select installation components
|
|
441
|
+
async selectInstallationComponents(selectedIDE, existingInstallations) {
|
|
442
|
+
// First, detect what's changed
|
|
443
|
+
const changeInfo = await this.detectChanges(selectedIDE);
|
|
444
|
+
|
|
445
|
+
if (changeInfo.isFirstInstall) {
|
|
446
|
+
// Fresh installation - install everything
|
|
447
|
+
return {
|
|
448
|
+
installAll: true,
|
|
449
|
+
changeInfo
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
console.log('\n' + chalk.bold.blue('š Change Analysis'));
|
|
454
|
+
console.log(chalk.gray('ā'.repeat(50)));
|
|
455
|
+
|
|
456
|
+
// Show version information
|
|
457
|
+
if (changeInfo.localVersion && changeInfo.remoteVersion) {
|
|
458
|
+
console.log(chalk.blue('Current version:'), changeInfo.localVersion);
|
|
459
|
+
console.log(chalk.blue('Available version:'), changeInfo.remoteVersion);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// Show what's changed
|
|
463
|
+
if (changeInfo.changes.length > 0) {
|
|
464
|
+
console.log(chalk.yellow('\nš Updated Files:'));
|
|
465
|
+
changeInfo.changes.forEach(change => {
|
|
466
|
+
console.log(chalk.gray(` ⢠${change.file} (${change.component})`));
|
|
467
|
+
console.log(chalk.gray(` ${change.reason}`));
|
|
468
|
+
if (change.localHash && change.remoteHash) {
|
|
469
|
+
console.log(chalk.gray(` Local: ${change.localHash}... ā Remote: ${change.remoteHash}...`));
|
|
470
|
+
}
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
if (changeInfo.newFiles.length > 0) {
|
|
475
|
+
console.log(chalk.green('\nš New Files:'));
|
|
476
|
+
changeInfo.newFiles.forEach(file => {
|
|
477
|
+
console.log(chalk.gray(` ⢠${file.file} (${file.component})`));
|
|
478
|
+
console.log(chalk.gray(` ${file.reason}`));
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// Show recommendations
|
|
483
|
+
console.log(chalk.bold.cyan('\nš” Recommendations:'));
|
|
484
|
+
changeInfo.recommendations.forEach(rec => {
|
|
485
|
+
console.log(chalk.gray(` ${rec}`));
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
if (changeInfo.changes.length === 0 && changeInfo.newFiles.length === 0) {
|
|
489
|
+
console.log(chalk.green('\n⨠All files are up to date!'));
|
|
490
|
+
|
|
491
|
+
const { forceUpdate } = await inquirer.prompt([
|
|
492
|
+
{
|
|
493
|
+
type: 'confirm',
|
|
494
|
+
name: 'forceUpdate',
|
|
495
|
+
message: 'All files are current. Force reinstall anyway?',
|
|
496
|
+
default: false
|
|
497
|
+
}
|
|
498
|
+
]);
|
|
499
|
+
|
|
500
|
+
if (!forceUpdate) {
|
|
501
|
+
return {
|
|
502
|
+
skipInstall: true,
|
|
503
|
+
changeInfo
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
console.log('\n' + chalk.bold.blue('š§ Component Selection'));
|
|
509
|
+
console.log(chalk.gray('ā'.repeat(50)));
|
|
510
|
+
|
|
511
|
+
const componentChoices = this.getComponentChoices(selectedIDE);
|
|
512
|
+
|
|
513
|
+
// Pre-select components that have changes
|
|
514
|
+
const changedComponents = new Set();
|
|
515
|
+
[...changeInfo.changes, ...changeInfo.newFiles].forEach(item => {
|
|
516
|
+
if (item.component) {
|
|
517
|
+
changedComponents.add(item.component);
|
|
518
|
+
}
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
// Update choices to pre-select changed components
|
|
522
|
+
componentChoices.forEach(choice => {
|
|
523
|
+
if (changedComponents.has(choice.value)) {
|
|
524
|
+
choice.checked = true;
|
|
525
|
+
choice.name += chalk.yellow(' (has updates)');
|
|
526
|
+
}
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
const { components } = await inquirer.prompt([
|
|
530
|
+
{
|
|
531
|
+
type: 'checkbox',
|
|
532
|
+
name: 'components',
|
|
533
|
+
message: 'Select components to install/update:',
|
|
534
|
+
choices: componentChoices,
|
|
535
|
+
pageSize: 10,
|
|
536
|
+
validate: (answer) => {
|
|
537
|
+
if (answer.length === 0) {
|
|
538
|
+
return 'Please select at least one component to install.';
|
|
539
|
+
}
|
|
540
|
+
return true;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
]);
|
|
544
|
+
|
|
545
|
+
const { createBackups } = await inquirer.prompt([
|
|
546
|
+
{
|
|
547
|
+
type: 'confirm',
|
|
548
|
+
name: 'createBackups',
|
|
549
|
+
message: 'Create backups of existing files before overwriting?',
|
|
550
|
+
default: true
|
|
551
|
+
}
|
|
552
|
+
]);
|
|
553
|
+
|
|
554
|
+
return {
|
|
555
|
+
selectedComponents: components,
|
|
556
|
+
createBackups,
|
|
557
|
+
installAll: false,
|
|
558
|
+
changeInfo
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// Get component choices based on IDE
|
|
563
|
+
getComponentChoices(selectedIDE) {
|
|
564
|
+
const baseChoices = [
|
|
565
|
+
{ name: 'Core Commands', value: 'commands', checked: true },
|
|
566
|
+
{ name: 'Documentation & Best Practices', value: 'docs', checked: true }
|
|
567
|
+
];
|
|
568
|
+
|
|
569
|
+
switch (selectedIDE) {
|
|
570
|
+
case 'cursor':
|
|
571
|
+
return [
|
|
572
|
+
...baseChoices,
|
|
573
|
+
{ name: 'Cursor Rules (.cursor/rules/cc.mdc)', value: 'rules', checked: true },
|
|
574
|
+
{ name: 'GitHub Integration', value: 'github', checked: true },
|
|
575
|
+
{ name: 'Azure DevOps Integration', value: 'azure', checked: true }
|
|
576
|
+
];
|
|
577
|
+
|
|
578
|
+
case 'copilot':
|
|
579
|
+
return [
|
|
580
|
+
...baseChoices,
|
|
581
|
+
{ name: 'GitHub Copilot Chatmodes', value: 'chatmodes', checked: true },
|
|
582
|
+
{ name: 'GitHub Copilot Prompts', value: 'prompts', checked: true }
|
|
583
|
+
];
|
|
584
|
+
|
|
585
|
+
case 'windsurf':
|
|
586
|
+
return [
|
|
587
|
+
...baseChoices,
|
|
588
|
+
{ name: 'Windsurf Rules', value: 'rules', checked: true },
|
|
589
|
+
{ name: 'Windsurf Workflows', value: 'workflows', checked: true }
|
|
590
|
+
];
|
|
591
|
+
|
|
592
|
+
case 'claude':
|
|
593
|
+
return [
|
|
594
|
+
...baseChoices,
|
|
595
|
+
{ name: 'Claude Agents', value: 'agents', checked: true },
|
|
596
|
+
{ name: 'Claude Commands', value: 'claude-commands', checked: true }
|
|
597
|
+
];
|
|
598
|
+
|
|
599
|
+
default:
|
|
600
|
+
return baseChoices;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// Confirmation prompt
|
|
605
|
+
async confirmInstallation(selectedIDE, systemInfo, installOptions) {
|
|
606
|
+
console.log('\n' + chalk.bold.yellow('š Installation Summary'));
|
|
607
|
+
console.log(chalk.gray('ā'.repeat(50)));
|
|
608
|
+
|
|
609
|
+
const ide = this.ides[selectedIDE];
|
|
610
|
+
console.log(chalk.blue('Selected IDE:'), chalk.bold(ide.name));
|
|
611
|
+
console.log(chalk.blue('Description:'), ide.description);
|
|
612
|
+
console.log(chalk.blue('Installation:'), ide.details);
|
|
613
|
+
console.log(chalk.blue('Node.js:'), systemInfo.nodeVersion);
|
|
614
|
+
console.log(chalk.blue('Git Repository:'), systemInfo.isGitRepo ? 'Yes' : 'No');
|
|
615
|
+
|
|
616
|
+
if (systemInfo.existingInstallations.length > 0) {
|
|
617
|
+
console.log(chalk.yellow('Existing installations:'), systemInfo.existingInstallations.join(', '));
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
if (!installOptions.installAll) {
|
|
621
|
+
console.log(chalk.blue('Components to install:'), installOptions.selectedComponents.join(', '));
|
|
622
|
+
console.log(chalk.blue('Create backups:'), installOptions.createBackups ? 'Yes' : 'No');
|
|
623
|
+
|
|
624
|
+
// Show change summary
|
|
625
|
+
const { changeInfo } = installOptions;
|
|
626
|
+
if (changeInfo && (changeInfo.changes.length > 0 || changeInfo.newFiles.length > 0)) {
|
|
627
|
+
console.log(chalk.blue('Files to update:'), `${changeInfo.changes.length} changed, ${changeInfo.newFiles.length} new`);
|
|
628
|
+
}
|
|
629
|
+
} else {
|
|
630
|
+
console.log(chalk.blue('Installation type:'), 'Full installation (new setup)');
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
const { confirmed } = await inquirer.prompt([
|
|
634
|
+
{
|
|
635
|
+
type: 'confirm',
|
|
636
|
+
name: 'confirmed',
|
|
637
|
+
message: 'Proceed with installation?',
|
|
638
|
+
default: true
|
|
639
|
+
}
|
|
640
|
+
]);
|
|
641
|
+
|
|
642
|
+
return confirmed;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// Create backup of existing file
|
|
646
|
+
async createBackup(targetPath, shouldBackup = true) {
|
|
647
|
+
if (!shouldBackup) {
|
|
648
|
+
return null;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
if (await fs.pathExists(targetPath)) {
|
|
652
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
653
|
+
const backupPath = `${targetPath}.backup.${timestamp}`;
|
|
654
|
+
|
|
655
|
+
try {
|
|
656
|
+
await fs.copy(targetPath, backupPath);
|
|
657
|
+
return backupPath;
|
|
658
|
+
} catch (error) {
|
|
659
|
+
console.warn(chalk.yellow(`Warning: Could not backup ${targetPath}: ${error.message}`));
|
|
660
|
+
return null;
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
return null;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
// Download file from URL or local source
|
|
667
|
+
async downloadFile(relativePath, targetPath, shouldBackup = true) {
|
|
668
|
+
try {
|
|
669
|
+
let content;
|
|
670
|
+
|
|
671
|
+
if (this.config.localSource) {
|
|
672
|
+
// Local source mode
|
|
673
|
+
const localPath = path.join(this.config.localSource, relativePath);
|
|
674
|
+
content = await fs.readFile(localPath, 'utf8');
|
|
675
|
+
} else {
|
|
676
|
+
// Remote download mode
|
|
677
|
+
const url = `${this.config.baseUrl}/${relativePath}`;
|
|
678
|
+
const response = await fetch(url);
|
|
679
|
+
|
|
680
|
+
if (!response.ok) {
|
|
681
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
content = await response.text();
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// Ensure target directory exists
|
|
688
|
+
await fs.ensureDir(path.dirname(targetPath));
|
|
689
|
+
|
|
690
|
+
// Create backup if file exists
|
|
691
|
+
await this.createBackup(targetPath, shouldBackup);
|
|
692
|
+
|
|
693
|
+
// Write file
|
|
694
|
+
await fs.writeFile(targetPath, content);
|
|
695
|
+
|
|
696
|
+
return true;
|
|
697
|
+
} catch (error) {
|
|
698
|
+
throw new Error(`Failed to download ${relativePath}: ${error.message}`);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// Get IDE-specific file list
|
|
703
|
+
getIDEFiles(selectedIDE, selectedComponents = null) {
|
|
704
|
+
const files = [];
|
|
705
|
+
|
|
706
|
+
// If no components specified, include all files (fresh installation)
|
|
707
|
+
const includeAll = !selectedComponents;
|
|
708
|
+
|
|
709
|
+
switch (selectedIDE) {
|
|
710
|
+
case 'cursor':
|
|
711
|
+
// Cursor rules
|
|
712
|
+
if (includeAll || selectedComponents.includes('rules')) {
|
|
713
|
+
files.push(
|
|
714
|
+
{ source: 'cursor/cc.mdc', target: '.cursor/rules/cc.mdc', component: 'rules' }
|
|
715
|
+
);
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
// Core commands and docs
|
|
719
|
+
if (includeAll || selectedComponents.includes('commands')) {
|
|
720
|
+
files.push(
|
|
721
|
+
{ source: 'cursor/cc.md', target: '.code-captain/cc.md', component: 'commands' }
|
|
722
|
+
);
|
|
723
|
+
|
|
724
|
+
const cursorCommands = [
|
|
725
|
+
'create-adr.md', 'create-spec.md', 'edit-spec.md', 'execute-task.md',
|
|
726
|
+
'explain-code.md', 'initialize.md', 'new-command.md', 'plan-product.md',
|
|
727
|
+
'research.md', 'status.md', 'swab.md'
|
|
728
|
+
];
|
|
729
|
+
|
|
730
|
+
cursorCommands.forEach(cmd => {
|
|
731
|
+
files.push({
|
|
732
|
+
source: `cursor/commands/${cmd}`,
|
|
733
|
+
target: `.code-captain/commands/${cmd}`,
|
|
734
|
+
component: 'commands'
|
|
735
|
+
});
|
|
736
|
+
});
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
// GitHub integration
|
|
740
|
+
if (includeAll || selectedComponents.includes('github')) {
|
|
741
|
+
const githubFiles = [
|
|
742
|
+
'integrations/github/create-github-issues.md',
|
|
743
|
+
'integrations/github/sync-github-issues.md',
|
|
744
|
+
'integrations/github/sync.md'
|
|
745
|
+
];
|
|
746
|
+
|
|
747
|
+
githubFiles.forEach(file => {
|
|
748
|
+
files.push({
|
|
749
|
+
source: `cursor/${file}`,
|
|
750
|
+
target: `.code-captain/${file}`,
|
|
751
|
+
component: 'github'
|
|
752
|
+
});
|
|
753
|
+
});
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
// Azure DevOps integration
|
|
757
|
+
if (includeAll || selectedComponents.includes('azure')) {
|
|
758
|
+
const azureFiles = [
|
|
759
|
+
'integrations/azure-devops/create-azure-work-items.md',
|
|
760
|
+
'integrations/azure-devops/sync-azure-work-items.md'
|
|
761
|
+
];
|
|
762
|
+
|
|
763
|
+
azureFiles.forEach(file => {
|
|
764
|
+
files.push({
|
|
765
|
+
source: `cursor/${file}`,
|
|
766
|
+
target: `.code-captain/${file}`,
|
|
767
|
+
component: 'azure'
|
|
768
|
+
});
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
// Documentation
|
|
773
|
+
if (includeAll || selectedComponents.includes('docs')) {
|
|
774
|
+
files.push({
|
|
775
|
+
source: 'cursor/docs/best-practices.md',
|
|
776
|
+
target: '.code-captain/docs/best-practices.md',
|
|
777
|
+
component: 'docs'
|
|
778
|
+
});
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
break;
|
|
782
|
+
|
|
783
|
+
case 'copilot':
|
|
784
|
+
// Chatmodes
|
|
785
|
+
if (includeAll || selectedComponents.includes('chatmodes')) {
|
|
786
|
+
files.push(
|
|
787
|
+
{ source: 'copilot/chatmodes/Code Captain.chatmode.md', target: '.github/chatmodes/Code Captain.chatmode.md', component: 'chatmodes' }
|
|
788
|
+
);
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
// Prompts
|
|
792
|
+
if (includeAll || selectedComponents.includes('prompts')) {
|
|
793
|
+
const copilotPrompts = [
|
|
794
|
+
'create-adr.prompt.md', 'create-spec.prompt.md', 'edit-spec.prompt.md',
|
|
795
|
+
'execute-task.prompt.md', 'explain-code.prompt.md', 'initialize.prompt.md',
|
|
796
|
+
'new-command.prompt.md', 'plan-product.prompt.md', 'research.prompt.md',
|
|
797
|
+
'status.prompt.md', 'swab.prompt.md'
|
|
798
|
+
];
|
|
799
|
+
|
|
800
|
+
copilotPrompts.forEach(prompt => {
|
|
801
|
+
files.push({
|
|
802
|
+
source: `copilot/prompts/${prompt}`,
|
|
803
|
+
target: `.github/prompts/${prompt}`,
|
|
804
|
+
component: 'prompts'
|
|
805
|
+
});
|
|
806
|
+
});
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
// Documentation
|
|
810
|
+
if (includeAll || selectedComponents.includes('docs')) {
|
|
811
|
+
files.push({
|
|
812
|
+
source: 'copilot/docs/best-practices.md',
|
|
813
|
+
target: '.code-captain/docs/best-practices.md',
|
|
814
|
+
component: 'docs'
|
|
815
|
+
});
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
break;
|
|
819
|
+
|
|
820
|
+
case 'windsurf':
|
|
821
|
+
// Rules
|
|
822
|
+
if (includeAll || selectedComponents.includes('rules')) {
|
|
823
|
+
files.push(
|
|
824
|
+
{ source: 'windsurf/rules/cc.md', target: 'windsurf/rules/cc.md', component: 'rules' }
|
|
825
|
+
);
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
// Workflows
|
|
829
|
+
if (includeAll || selectedComponents.includes('workflows')) {
|
|
830
|
+
const windsurfWorkflows = [
|
|
831
|
+
'create-adr.md', 'create-spec.md', 'edit-spec.md', 'execute-task.md',
|
|
832
|
+
'explain-code.md', 'initialize.md', 'new-command.md', 'status.md'
|
|
833
|
+
];
|
|
834
|
+
|
|
835
|
+
windsurfWorkflows.forEach(workflow => {
|
|
836
|
+
files.push({
|
|
837
|
+
source: `windsurf/workflows/${workflow}`,
|
|
838
|
+
target: `windsurf/workflows/${workflow}`,
|
|
839
|
+
component: 'workflows'
|
|
840
|
+
});
|
|
841
|
+
});
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
break;
|
|
845
|
+
|
|
846
|
+
case 'claude':
|
|
847
|
+
// Claude agents
|
|
848
|
+
if (includeAll || selectedComponents.includes('agents')) {
|
|
849
|
+
const claudeAgents = [
|
|
850
|
+
'code-captain.md', 'spec-generator.md', 'spec-orchestrator.md',
|
|
851
|
+
'story-creator.md', 'tech-spec.md'
|
|
852
|
+
];
|
|
853
|
+
|
|
854
|
+
claudeAgents.forEach(agent => {
|
|
855
|
+
files.push({
|
|
856
|
+
source: `claude-code/agents/${agent}`,
|
|
857
|
+
target: `.code-captain/claude/agents/${agent}`,
|
|
858
|
+
component: 'agents'
|
|
859
|
+
});
|
|
860
|
+
});
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
// Claude commands
|
|
864
|
+
if (includeAll || selectedComponents.includes('claude-commands')) {
|
|
865
|
+
const claudeCommands = [
|
|
866
|
+
'cc-create-spec.md', 'cc-initialize.md'
|
|
867
|
+
];
|
|
868
|
+
|
|
869
|
+
claudeCommands.forEach(command => {
|
|
870
|
+
files.push({
|
|
871
|
+
source: `claude-code/commands/${command}`,
|
|
872
|
+
target: `.code-captain/claude/commands/${command}`,
|
|
873
|
+
component: 'claude-commands'
|
|
874
|
+
});
|
|
875
|
+
});
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
break;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
return files;
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
// Install files for selected IDE
|
|
885
|
+
async installFiles(selectedIDE, installOptions) {
|
|
886
|
+
const selectedComponents = installOptions.installAll ? null : installOptions.selectedComponents;
|
|
887
|
+
const files = this.getIDEFiles(selectedIDE, selectedComponents);
|
|
888
|
+
const spinner = ora(`Installing ${this.ides[selectedIDE].name} integration...`).start();
|
|
889
|
+
|
|
890
|
+
try {
|
|
891
|
+
let completed = 0;
|
|
892
|
+
const backupPaths = [];
|
|
893
|
+
|
|
894
|
+
for (const file of files) {
|
|
895
|
+
const shouldBackup = installOptions.createBackups !== false; // Default to true if not specified
|
|
896
|
+
await this.downloadFile(file.source, file.target, shouldBackup);
|
|
897
|
+
completed++;
|
|
898
|
+
spinner.text = `Installing files... (${completed}/${files.length})`;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
// Save manifest for future change detection
|
|
902
|
+
if (installOptions.changeInfo) {
|
|
903
|
+
const remoteManifest = await this.getRemoteManifest();
|
|
904
|
+
await this.saveManifest(remoteManifest, files);
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
spinner.succeed(`${this.ides[selectedIDE].name} integration installed successfully!`);
|
|
908
|
+
|
|
909
|
+
return {
|
|
910
|
+
totalFiles: files.length,
|
|
911
|
+
targetDir: selectedIDE === 'copilot' ? '.github + .code-captain/docs' :
|
|
912
|
+
selectedIDE === 'windsurf' ? 'windsurf' :
|
|
913
|
+
selectedIDE === 'claude' ? '.code-captain/claude' : '.code-captain (+ .cursor/rules)',
|
|
914
|
+
components: installOptions.installAll ? 'All components' : installOptions.selectedComponents.join(', '),
|
|
915
|
+
changesDetected: installOptions.changeInfo && (installOptions.changeInfo.changes.length > 0 || installOptions.changeInfo.newFiles.length > 0)
|
|
916
|
+
};
|
|
917
|
+
} catch (error) {
|
|
918
|
+
spinner.fail('Installation failed');
|
|
919
|
+
throw error;
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
// Show post-installation instructions
|
|
924
|
+
showInstructions(selectedIDE, installResult) {
|
|
925
|
+
const ide = this.ides[selectedIDE];
|
|
926
|
+
|
|
927
|
+
console.log('\n' + boxen(
|
|
928
|
+
chalk.bold.green('š Installation Complete!') + '\n\n' +
|
|
929
|
+
chalk.blue('IDE:') + ` ${ide.name}\n` +
|
|
930
|
+
chalk.blue('Files installed:') + ` ${installResult.totalFiles}\n` +
|
|
931
|
+
chalk.blue('Location:') + ` ${installResult.targetDir}/\n` +
|
|
932
|
+
chalk.blue('Components:') + ` ${installResult.components}`,
|
|
933
|
+
{
|
|
934
|
+
padding: 1,
|
|
935
|
+
margin: 1,
|
|
936
|
+
borderStyle: 'round',
|
|
937
|
+
borderColor: 'green'
|
|
938
|
+
}
|
|
939
|
+
));
|
|
940
|
+
|
|
941
|
+
console.log(chalk.bold.yellow('\nš Next Steps:'));
|
|
942
|
+
console.log(chalk.gray('ā'.repeat(50)));
|
|
943
|
+
|
|
944
|
+
switch (selectedIDE) {
|
|
945
|
+
case 'cursor':
|
|
946
|
+
console.log(chalk.blue('1.') + ' Restart Cursor to load the new rule from ' + chalk.cyan('.cursor/rules/cc.mdc'));
|
|
947
|
+
console.log(chalk.blue('2.') + ' Use ' + chalk.cyan('cc: initialize') + ' to set up your project');
|
|
948
|
+
console.log(chalk.blue('3.') + ' Try ' + chalk.cyan('cc: plan-product') + ' for product planning');
|
|
949
|
+
console.log(chalk.blue('4.') + ' Use ' + chalk.cyan('cc: create-spec') + ' for feature specifications');
|
|
950
|
+
break;
|
|
951
|
+
|
|
952
|
+
case 'copilot':
|
|
953
|
+
console.log(chalk.blue('1.') + ' Restart VS Code to load chatmodes from ' + chalk.cyan('.github/chatmodes/'));
|
|
954
|
+
console.log(chalk.blue('2.') + ' Open GitHub Copilot Chat in VS Code');
|
|
955
|
+
console.log(chalk.blue('3.') + ' Type ' + chalk.cyan('@Code Captain') + ' to access the chatmode');
|
|
956
|
+
console.log(chalk.blue('4.') + ' Use prompts from ' + chalk.cyan('.github/prompts/') + ' for workflows');
|
|
957
|
+
break;
|
|
958
|
+
|
|
959
|
+
case 'windsurf':
|
|
960
|
+
console.log(chalk.blue('1.') + ' Restart Windsurf to load the new workflows');
|
|
961
|
+
console.log(chalk.blue('2.') + ' Use the AI agent with Code Captain commands');
|
|
962
|
+
console.log(chalk.blue('3.') + ' Try ' + chalk.cyan('cc: initialize') + ' to set up your project');
|
|
963
|
+
break;
|
|
964
|
+
|
|
965
|
+
case 'claude':
|
|
966
|
+
console.log(chalk.blue('1.') + ' Claude agents and commands are installed in ' + chalk.cyan('.code-captain/claude/'));
|
|
967
|
+
console.log(chalk.blue('2.') + ' Reference the agents in ' + chalk.cyan('.code-captain/claude/agents/') + ' for specialized workflows');
|
|
968
|
+
console.log(chalk.blue('3.') + ' Use command templates from ' + chalk.cyan('.code-captain/claude/commands/'));
|
|
969
|
+
console.log(chalk.blue('4.') + ' Import agent contexts directly into Claude conversations');
|
|
970
|
+
break;
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
console.log('\n' + chalk.green('š Ready to start building with Code Captain!'));
|
|
974
|
+
console.log(chalk.gray('Documentation: https://github.com/devobsessed/code-captain'));
|
|
975
|
+
|
|
976
|
+
// Show backup information if backups were created
|
|
977
|
+
if (installResult.totalFiles > 0) {
|
|
978
|
+
console.log('\n' + chalk.yellow('š¾ Backup Information:'));
|
|
979
|
+
console.log(chalk.gray('Existing files were backed up with timestamps (e.g., filename.backup.2024-01-01T12-00-00-000Z)'));
|
|
980
|
+
console.log(chalk.gray('You can safely delete backup files once you\'re satisfied with the installation.'));
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
// Handle installation errors
|
|
985
|
+
handleError(error, selectedIDE) {
|
|
986
|
+
console.error('\n' + chalk.red('ā Installation failed'));
|
|
987
|
+
console.error(chalk.red('Error:'), error.message);
|
|
988
|
+
|
|
989
|
+
console.log('\n' + chalk.yellow('š§ Troubleshooting:'));
|
|
990
|
+
console.log(chalk.blue('1.') + ' Check your internet connection');
|
|
991
|
+
console.log(chalk.blue('2.') + ' Ensure you have write permissions in this directory');
|
|
992
|
+
console.log(chalk.blue('3.') + ' Try running with ' + chalk.cyan('CC_LOCAL_SOURCE=path npx @devobsessed/code-captain'));
|
|
993
|
+
|
|
994
|
+
if (selectedIDE) {
|
|
995
|
+
console.log(chalk.blue('4.') + ` Try a different IDE option`);
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
console.log('\n' + chalk.gray('For help: https://github.com/devobsessed/code-captain/issues'));
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
// Main installation flow
|
|
1002
|
+
async run() {
|
|
1003
|
+
try {
|
|
1004
|
+
// Show welcome
|
|
1005
|
+
this.showWelcome();
|
|
1006
|
+
|
|
1007
|
+
// Check compatibility
|
|
1008
|
+
const systemInfo = await this.checkCompatibility();
|
|
1009
|
+
|
|
1010
|
+
// Select IDE
|
|
1011
|
+
const selectedIDE = await this.selectIDE();
|
|
1012
|
+
|
|
1013
|
+
// Select installation components
|
|
1014
|
+
const installOptions = await this.selectInstallationComponents(selectedIDE, systemInfo.existingInstallations);
|
|
1015
|
+
|
|
1016
|
+
if (installOptions.skipInstall) {
|
|
1017
|
+
console.log(chalk.yellow('\nš Installation cancelled due to no changes detected.'));
|
|
1018
|
+
process.exit(0);
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
// Confirm installation
|
|
1022
|
+
const confirmed = await this.confirmInstallation(selectedIDE, systemInfo, installOptions);
|
|
1023
|
+
|
|
1024
|
+
if (!confirmed) {
|
|
1025
|
+
console.log(chalk.yellow('\nš Installation cancelled'));
|
|
1026
|
+
process.exit(0);
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
// Install files
|
|
1030
|
+
const installResult = await this.installFiles(selectedIDE, installOptions);
|
|
1031
|
+
|
|
1032
|
+
// Show instructions
|
|
1033
|
+
this.showInstructions(selectedIDE, installResult);
|
|
1034
|
+
|
|
1035
|
+
} catch (error) {
|
|
1036
|
+
this.handleError(error);
|
|
1037
|
+
process.exit(1);
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
// Run installer if called directly
|
|
1043
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
1044
|
+
const installer = new CodeCaptainInstaller();
|
|
1045
|
+
installer.run();
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
export default CodeCaptainInstaller;
|