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