@devobsessed/code-captain 0.0.6 ā 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 +36 -37
- package/bin/install.js +1166 -983
- package/claude-code/agents/code-captain.md +31 -22
- package/copilot/README.md +26 -16
- package/copilot/chatmodes/Code Captain.chatmode.md +41 -25
- package/copilot/prompts/create-adr.prompt.md +6 -4
- package/copilot/prompts/create-spec.prompt.md +62 -45
- package/copilot/prompts/explain-code.prompt.md +7 -23
- package/copilot/prompts/new-command.prompt.md +60 -21
- package/copilot/prompts/research.prompt.md +14 -30
- package/copilot/prompts/status.prompt.md +13 -2
- package/copilot/prompts/swab.prompt.md +1 -0
- package/cursor/README.md +77 -88
- package/cursor/cc.mdc +13 -42
- package/cursor/commands/create-adr.md +7 -13
- package/cursor/commands/create-spec.md +73 -64
- package/cursor/commands/edit-spec.md +2 -15
- package/cursor/commands/execute-task.md +7 -15
- package/cursor/commands/explain-code.md +16 -35
- package/cursor/commands/initialize.md +19 -18
- package/cursor/commands/new-command.md +173 -81
- package/cursor/commands/plan-product.md +7 -13
- package/cursor/commands/research.md +5 -27
- package/cursor/commands/status.md +34 -23
- package/cursor/commands/swab.md +63 -12
- package/manifest.json +110 -229
- package/package.json +13 -4
- package/cursor/cc.md +0 -183
- package/cursor/integrations/azure-devops/create-azure-work-items.md +0 -403
- package/cursor/integrations/azure-devops/sync-azure-work-items.md +0 -486
- package/cursor/integrations/github/create-github-issues.md +0 -765
- package/cursor/integrations/github/scripts/create-issues-batch.sh +0 -272
- package/cursor/integrations/github/sync-github-issues.md +0 -237
- package/cursor/integrations/github/sync.md +0 -305
- 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 -292
- package/windsurf/workflows/initialize.md +0 -298
- package/windsurf/workflows/new-command.md +0 -321
- package/windsurf/workflows/status.md +0 -213
package/bin/install.js
CHANGED
|
@@ -1,1087 +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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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.`
|
|
72
90
|
);
|
|
73
|
-
|
|
74
|
-
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);
|
|
75
113
|
}
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
existingInstallations
|
|
105
|
-
};
|
|
106
|
-
} catch (error) {
|
|
107
|
-
spinner.fail('Compatibility check failed');
|
|
108
|
-
console.error(chalk.red('Error:'), error.message);
|
|
109
|
-
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
|
|
110
142
|
}
|
|
143
|
+
}
|
|
111
144
|
}
|
|
112
145
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
const installations = [];
|
|
116
|
-
|
|
117
|
-
// Define all possible Code Captain paths
|
|
118
|
-
const pathsToCheck = {
|
|
119
|
-
'Code Captain Core': ['.code-captain/'],
|
|
120
|
-
'Cursor Integration': ['.cursor/rules/cc.mdc', '.cursor/rules/'],
|
|
121
|
-
'Copilot Integration': ['.github/chatmodes/', '.github/prompts/'],
|
|
122
|
-
'Windsurf Integration': ['windsurf/rules/', 'windsurf/workflows/'],
|
|
123
|
-
'Claude Integration': ['.code-captain/claude/', 'claude-code/', '.claude/'],
|
|
124
|
-
'Legacy Structure': ['cursor/', 'copilot/', 'windsurf/']
|
|
125
|
-
};
|
|
146
|
+
return [...new Set(installations)]; // Remove duplicates
|
|
147
|
+
}
|
|
126
148
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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";
|
|
134
160
|
}
|
|
135
|
-
|
|
136
|
-
|
|
161
|
+
}
|
|
162
|
+
return "unknown";
|
|
163
|
+
} catch (error) {
|
|
164
|
+
return "unknown";
|
|
137
165
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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;
|
|
176
|
+
}
|
|
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);
|
|
153
187
|
}
|
|
188
|
+
}
|
|
154
189
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
190
|
+
// Get remote manifest with file versions/hashes
|
|
191
|
+
async getRemoteManifest() {
|
|
192
|
+
try {
|
|
193
|
+
const manifestUrl = `${this.config.baseUrl}/manifest.json`;
|
|
194
|
+
|
|
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 };
|
|
204
|
+
}
|
|
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 };
|
|
163
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 };
|
|
164
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
|
+
}
|
|
165
252
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
return JSON.parse(content);
|
|
176
|
-
}
|
|
177
|
-
} else {
|
|
178
|
-
const response = await fetch(manifestUrl);
|
|
179
|
-
if (response.ok) {
|
|
180
|
-
return await response.json();
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// Fallback: generate manifest from current commit
|
|
185
|
-
return await this.generateFallbackManifest();
|
|
186
|
-
} catch (error) {
|
|
187
|
-
console.warn(chalk.yellow('Warning: Could not fetch remote manifest, using fallback'));
|
|
188
|
-
return await this.generateFallbackManifest();
|
|
253
|
+
if (existingFiles.length === 0) {
|
|
254
|
+
if (manifestIsFallback) {
|
|
255
|
+
spinner.succeed(
|
|
256
|
+
"No existing files found - treating as fresh installation (offline mode)"
|
|
257
|
+
);
|
|
258
|
+
} else {
|
|
259
|
+
spinner.succeed(
|
|
260
|
+
"No existing files found - treating as fresh installation"
|
|
261
|
+
);
|
|
189
262
|
}
|
|
190
|
-
}
|
|
191
263
|
|
|
192
|
-
|
|
193
|
-
async generateFallbackManifest() {
|
|
194
|
-
const timestamp = new Date().toISOString();
|
|
264
|
+
const availableVersion = remoteManifest.version;
|
|
195
265
|
return {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
266
|
+
isFirstInstall: true,
|
|
267
|
+
remoteVersion: availableVersion,
|
|
268
|
+
changes: [],
|
|
269
|
+
newFiles: [],
|
|
270
|
+
recommendations: ["Full installation recommended"],
|
|
271
|
+
manifestIsFallback,
|
|
200
272
|
};
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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;
|
|
212
298
|
}
|
|
213
|
-
return null;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// Compare manifests and detect changes
|
|
217
|
-
async detectChanges(selectedIDE) {
|
|
218
|
-
const spinner = ora('Analyzing file changes...').start();
|
|
219
|
-
|
|
220
|
-
try {
|
|
221
|
-
const [remoteManifest, localManifest] = await Promise.all([
|
|
222
|
-
this.getRemoteManifest(),
|
|
223
|
-
this.getLocalManifest()
|
|
224
|
-
]);
|
|
225
|
-
|
|
226
|
-
if (!localManifest) {
|
|
227
|
-
spinner.succeed('No previous manifest found - treating as fresh installation');
|
|
228
|
-
|
|
229
|
-
// Get proper version information for first install
|
|
230
|
-
const currentVersion = this.config.localSource ? await this.getPackageVersion() : 'unknown';
|
|
231
|
-
const availableVersion = remoteManifest.version;
|
|
232
|
-
|
|
233
|
-
return {
|
|
234
|
-
isFirstInstall: true,
|
|
235
|
-
localVersion: currentVersion,
|
|
236
|
-
remoteVersion: availableVersion,
|
|
237
|
-
changes: [],
|
|
238
|
-
newFiles: [],
|
|
239
|
-
recommendations: ['Full installation recommended (no change tracking available)']
|
|
240
|
-
};
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
const files = this.getIDEFiles(selectedIDE);
|
|
244
|
-
const changes = [];
|
|
245
|
-
const newFiles = [];
|
|
246
|
-
let filesAnalyzed = 0;
|
|
247
|
-
|
|
248
|
-
for (const file of files) {
|
|
249
|
-
const remotePath = file.source;
|
|
250
|
-
const localPath = file.target;
|
|
251
|
-
|
|
252
|
-
filesAnalyzed++;
|
|
253
|
-
spinner.text = `Analyzing changes... (${filesAnalyzed}/${files.length})`;
|
|
254
|
-
|
|
255
|
-
// Check if file exists locally
|
|
256
|
-
const localExists = await fs.pathExists(localPath);
|
|
257
|
-
const remoteFileInfo = remoteManifest.files?.[remotePath];
|
|
258
|
-
|
|
259
|
-
if (!localExists) {
|
|
260
|
-
newFiles.push({
|
|
261
|
-
file: remotePath,
|
|
262
|
-
component: file.component,
|
|
263
|
-
reason: 'File does not exist locally'
|
|
264
|
-
});
|
|
265
|
-
continue;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// Calculate actual hash of local file
|
|
269
|
-
const localFileHash = await this.calculateFileHash(localPath);
|
|
270
|
-
|
|
271
|
-
if (!localFileHash) {
|
|
272
|
-
// Can't read local file - treat as needs update
|
|
273
|
-
changes.push({
|
|
274
|
-
file: remotePath,
|
|
275
|
-
component: file.component,
|
|
276
|
-
reason: 'Unable to read local file'
|
|
277
|
-
});
|
|
278
|
-
continue;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// Compare with remote hash
|
|
282
|
-
if (remoteFileInfo && remoteFileInfo.hash) {
|
|
283
|
-
const remoteHash = remoteFileInfo.hash.replace('sha256:', '');
|
|
284
|
-
|
|
285
|
-
if (localFileHash !== remoteHash) {
|
|
286
|
-
changes.push({
|
|
287
|
-
file: remotePath,
|
|
288
|
-
component: file.component,
|
|
289
|
-
localVersion: localManifest.files?.[remotePath]?.version || 'unknown',
|
|
290
|
-
remoteVersion: remoteFileInfo.version || 'latest',
|
|
291
|
-
reason: 'File content has changed',
|
|
292
|
-
localHash: localFileHash.substring(0, 8),
|
|
293
|
-
remoteHash: remoteHash.substring(0, 8)
|
|
294
|
-
});
|
|
295
|
-
}
|
|
296
|
-
} else {
|
|
297
|
-
// No remote file info - treat as new in remote
|
|
298
|
-
newFiles.push({
|
|
299
|
-
file: remotePath,
|
|
300
|
-
component: file.component,
|
|
301
|
-
reason: 'New file in remote repository'
|
|
302
|
-
});
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
const recommendations = this.generateUpdateRecommendations(changes, newFiles);
|
|
307
|
-
|
|
308
|
-
spinner.succeed(`Found ${changes.length} updated files, ${newFiles.length} new files`);
|
|
309
|
-
|
|
310
|
-
// Get proper version information
|
|
311
|
-
const currentVersion = this.config.localSource ? await this.getPackageVersion() : 'unknown';
|
|
312
|
-
const availableVersion = remoteManifest.version;
|
|
313
|
-
|
|
314
|
-
return {
|
|
315
|
-
isFirstInstall: false,
|
|
316
|
-
localVersion: currentVersion,
|
|
317
|
-
remoteVersion: availableVersion,
|
|
318
|
-
changes,
|
|
319
|
-
newFiles,
|
|
320
|
-
recommendations
|
|
321
|
-
};
|
|
322
299
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
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;
|
|
332
311
|
}
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
// Generate smart update recommendations
|
|
336
|
-
generateUpdateRecommendations(changes, newFiles) {
|
|
337
|
-
const recommendations = [];
|
|
338
|
-
const changedComponents = new Set();
|
|
339
312
|
|
|
340
|
-
//
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
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
|
+
}
|
|
349
327
|
} else {
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
//
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
}
|
|
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
|
+
}
|
|
362
341
|
}
|
|
342
|
+
}
|
|
363
343
|
|
|
364
|
-
|
|
365
|
-
|
|
344
|
+
const recommendations = this.generateUpdateRecommendations(
|
|
345
|
+
changes,
|
|
346
|
+
newFiles,
|
|
347
|
+
manifestIsFallback
|
|
348
|
+
);
|
|
366
349
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
} catch (error) {
|
|
398
|
-
console.warn(chalk.yellow(`Warning: Could not save manifest: ${error.message}`));
|
|
399
|
-
return false;
|
|
400
|
-
}
|
|
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
|
+
};
|
|
401
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
|
+
);
|
|
402
421
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
if (fs.pathExistsSync('.cursor') || this.commandExists('cursor')) {
|
|
409
|
-
detections.push('cursor');
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
// Check for VS Code
|
|
413
|
-
if (this.commandExists('code')) {
|
|
414
|
-
detections.push('copilot');
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
// Check for Windsurf
|
|
418
|
-
if (fs.pathExistsSync('windsurf') || this.commandExists('windsurf')) {
|
|
419
|
-
detections.push('windsurf');
|
|
422
|
+
// Specific recommendations
|
|
423
|
+
if (changedComponents.has("commands")) {
|
|
424
|
+
recommendations.push(
|
|
425
|
+
"š Commands updated - new features or bug fixes available"
|
|
426
|
+
);
|
|
420
427
|
}
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
if (fs.pathExistsSync('claude-code') || fs.pathExistsSync('.code-captain/claude') || fs.pathExistsSync('.claude')) {
|
|
424
|
-
detections.push('claude');
|
|
428
|
+
if (changedComponents.has("rules")) {
|
|
429
|
+
recommendations.push("āļø Rules updated - improved AI agent behavior");
|
|
425
430
|
}
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
// Check if command exists
|
|
431
|
-
commandExists(command) {
|
|
432
|
-
try {
|
|
433
|
-
const result = spawn.sync(command, ['--version'], {
|
|
434
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
435
|
-
timeout: 5000,
|
|
436
|
-
windowsHide: true
|
|
437
|
-
});
|
|
438
|
-
return result.status === 0;
|
|
439
|
-
} catch {
|
|
440
|
-
return false;
|
|
431
|
+
if (changedComponents.has("docs")) {
|
|
432
|
+
recommendations.push(
|
|
433
|
+
"š Documentation updated - check for new best practices"
|
|
434
|
+
);
|
|
441
435
|
}
|
|
436
|
+
}
|
|
442
437
|
}
|
|
443
438
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
const detected = this.detectIDE();
|
|
439
|
+
return recommendations;
|
|
440
|
+
}
|
|
447
441
|
|
|
448
|
-
|
|
449
|
-
|
|
442
|
+
// Auto-detect IDE preference
|
|
443
|
+
detectIDE() {
|
|
444
|
+
const detections = [];
|
|
450
445
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
446
|
+
// Check for Cursor
|
|
447
|
+
if (fs.pathExistsSync(".cursor") || this.commandExists("cursor")) {
|
|
448
|
+
detections.push("cursor");
|
|
449
|
+
}
|
|
454
450
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
short: ide.name
|
|
459
|
-
}));
|
|
460
|
-
|
|
461
|
-
const { selectedIDE } = await inquirer.prompt([
|
|
462
|
-
{
|
|
463
|
-
type: 'list',
|
|
464
|
-
name: 'selectedIDE',
|
|
465
|
-
message: 'Which IDE/environment are you using?',
|
|
466
|
-
choices: choices,
|
|
467
|
-
pageSize: 6,
|
|
468
|
-
default: detected.length > 0 ? detected[0] : 'cursor'
|
|
469
|
-
}
|
|
470
|
-
]);
|
|
471
|
-
|
|
472
|
-
return selectedIDE;
|
|
451
|
+
// Check for VS Code
|
|
452
|
+
if (this.commandExists("code")) {
|
|
453
|
+
detections.push("copilot");
|
|
473
454
|
}
|
|
474
455
|
|
|
475
|
-
//
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
installAll: true,
|
|
484
|
-
changeInfo
|
|
485
|
-
};
|
|
486
|
-
}
|
|
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
|
+
}
|
|
487
464
|
|
|
488
|
-
|
|
489
|
-
|
|
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
|
+
}
|
|
490
498
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
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
|
+
}
|
|
496
531
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
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
|
+
}
|
|
508
548
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
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
|
+
}
|
|
516
556
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
});
|
|
522
|
-
|
|
523
|
-
if (
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
name: 'forceUpdate',
|
|
530
|
-
message: 'All files are current. Force reinstall anyway?',
|
|
531
|
-
default: false
|
|
532
|
-
}
|
|
533
|
-
]);
|
|
534
|
-
|
|
535
|
-
if (!forceUpdate) {
|
|
536
|
-
return {
|
|
537
|
-
skipInstall: true,
|
|
538
|
-
changeInfo
|
|
539
|
-
};
|
|
540
|
-
}
|
|
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
|
+
);
|
|
541
569
|
}
|
|
570
|
+
});
|
|
571
|
+
}
|
|
542
572
|
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
[...changeInfo.changes, ...changeInfo.newFiles].forEach(item => {
|
|
551
|
-
if (item.component) {
|
|
552
|
-
changedComponents.add(item.component);
|
|
553
|
-
}
|
|
554
|
-
});
|
|
555
|
-
|
|
556
|
-
// Update choices to pre-select changed components
|
|
557
|
-
componentChoices.forEach(choice => {
|
|
558
|
-
if (changedComponents.has(choice.value)) {
|
|
559
|
-
choice.checked = true;
|
|
560
|
-
choice.name += chalk.yellow(' (has updates)');
|
|
561
|
-
}
|
|
562
|
-
});
|
|
563
|
-
|
|
564
|
-
const { components } = await inquirer.prompt([
|
|
565
|
-
{
|
|
566
|
-
type: 'checkbox',
|
|
567
|
-
name: 'components',
|
|
568
|
-
message: 'Select components to install/update:',
|
|
569
|
-
choices: componentChoices,
|
|
570
|
-
pageSize: 10,
|
|
571
|
-
validate: (answer) => {
|
|
572
|
-
if (answer.length === 0) {
|
|
573
|
-
return 'Please select at least one component to install.';
|
|
574
|
-
}
|
|
575
|
-
return true;
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
]);
|
|
579
|
-
|
|
580
|
-
const { createBackups } = await inquirer.prompt([
|
|
581
|
-
{
|
|
582
|
-
type: 'confirm',
|
|
583
|
-
name: 'createBackups',
|
|
584
|
-
message: 'Create backups of existing files before overwriting?',
|
|
585
|
-
default: true
|
|
586
|
-
}
|
|
587
|
-
]);
|
|
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
|
+
}
|
|
588
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) {
|
|
589
600
|
return {
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
installAll: false,
|
|
593
|
-
changeInfo
|
|
601
|
+
skipInstall: true,
|
|
602
|
+
changeInfo,
|
|
594
603
|
};
|
|
604
|
+
}
|
|
595
605
|
}
|
|
596
606
|
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
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
|
+
},
|
|
602
677
|
];
|
|
603
678
|
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
{ name: 'Azure DevOps Integration', value: 'azure', checked: true }
|
|
611
|
-
];
|
|
612
|
-
|
|
613
|
-
case 'copilot':
|
|
614
|
-
return [
|
|
615
|
-
...baseChoices,
|
|
616
|
-
{ name: 'GitHub Copilot Chatmodes', value: 'chatmodes', checked: true },
|
|
617
|
-
{ name: 'GitHub Copilot Prompts', value: 'prompts', checked: true }
|
|
618
|
-
];
|
|
619
|
-
|
|
620
|
-
case 'windsurf':
|
|
621
|
-
return [
|
|
622
|
-
...baseChoices,
|
|
623
|
-
{ name: 'Windsurf Rules', value: 'rules', checked: true },
|
|
624
|
-
{ name: 'Windsurf Workflows', value: 'workflows', checked: true }
|
|
625
|
-
];
|
|
626
|
-
|
|
627
|
-
case 'claude':
|
|
628
|
-
return [
|
|
629
|
-
...baseChoices,
|
|
630
|
-
{ name: 'Claude Agents', value: 'agents', checked: true },
|
|
631
|
-
{ name: 'Claude Commands', value: 'claude-commands', checked: true }
|
|
632
|
-
];
|
|
633
|
-
|
|
634
|
-
default:
|
|
635
|
-
return baseChoices;
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
// Confirmation prompt
|
|
640
|
-
async confirmInstallation(selectedIDE, systemInfo, installOptions) {
|
|
641
|
-
console.log('\n' + chalk.bold.yellow('š Installation Summary'));
|
|
642
|
-
console.log(chalk.gray('ā'.repeat(50)));
|
|
679
|
+
case "copilot":
|
|
680
|
+
return [
|
|
681
|
+
...baseChoices,
|
|
682
|
+
{ name: "Copilot Chatmodes", value: "chatmodes", checked: true },
|
|
683
|
+
{ name: "Copilot Prompts", value: "prompts", checked: true },
|
|
684
|
+
];
|
|
643
685
|
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
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
|
+
];
|
|
650
692
|
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
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
|
+
}
|
|
654
719
|
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
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
|
+
}
|
|
658
755
|
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
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 [];
|
|
772
|
+
}
|
|
667
773
|
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
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
|
+
});
|
|
676
785
|
|
|
677
|
-
|
|
678
|
-
}
|
|
786
|
+
const backupPaths = [];
|
|
679
787
|
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
return null;
|
|
684
|
-
}
|
|
788
|
+
for (const dir of targetDirs) {
|
|
789
|
+
if (await fs.pathExists(dir)) {
|
|
790
|
+
const backupPath = `${dir}.backup`;
|
|
685
791
|
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
792
|
+
try {
|
|
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
|
+
);
|
|
697
805
|
}
|
|
698
|
-
|
|
806
|
+
}
|
|
699
807
|
}
|
|
700
808
|
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
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
|
+
}
|
|
721
829
|
|
|
722
|
-
|
|
723
|
-
|
|
830
|
+
content = await response.text();
|
|
831
|
+
}
|
|
724
832
|
|
|
725
|
-
|
|
726
|
-
|
|
833
|
+
// Ensure target directory exists
|
|
834
|
+
await fs.ensureDir(path.dirname(targetPath));
|
|
727
835
|
|
|
728
|
-
|
|
729
|
-
|
|
836
|
+
// Write file
|
|
837
|
+
await fs.writeFile(targetPath, content);
|
|
730
838
|
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
}
|
|
839
|
+
return true;
|
|
840
|
+
} catch (error) {
|
|
841
|
+
throw new Error(`Failed to download ${relativePath}: ${error.message}`);
|
|
735
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
|
+
}
|
|
736
862
|
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
'create-adr.md', 'create-spec.md', 'edit-spec.md', 'execute-task.md',
|
|
761
|
-
'explain-code.md', 'initialize.md', 'new-command.md', 'plan-product.md',
|
|
762
|
-
'research.md', 'status.md', 'swab.md'
|
|
763
|
-
];
|
|
764
|
-
|
|
765
|
-
cursorCommands.forEach(cmd => {
|
|
766
|
-
files.push({
|
|
767
|
-
source: `cursor/commands/${cmd}`,
|
|
768
|
-
target: `.code-captain/commands/${cmd}`,
|
|
769
|
-
component: 'commands'
|
|
770
|
-
});
|
|
771
|
-
});
|
|
772
|
-
}
|
|
773
|
-
|
|
774
|
-
// GitHub integration
|
|
775
|
-
if (includeAll || selectedComponents.includes('github')) {
|
|
776
|
-
const githubFiles = [
|
|
777
|
-
'integrations/github/create-github-issues.md',
|
|
778
|
-
'integrations/github/sync-github-issues.md',
|
|
779
|
-
'integrations/github/sync.md'
|
|
780
|
-
];
|
|
781
|
-
|
|
782
|
-
githubFiles.forEach(file => {
|
|
783
|
-
files.push({
|
|
784
|
-
source: `cursor/${file}`,
|
|
785
|
-
target: `.code-captain/${file}`,
|
|
786
|
-
component: 'github'
|
|
787
|
-
});
|
|
788
|
-
});
|
|
789
|
-
}
|
|
790
|
-
|
|
791
|
-
// Azure DevOps integration
|
|
792
|
-
if (includeAll || selectedComponents.includes('azure')) {
|
|
793
|
-
const azureFiles = [
|
|
794
|
-
'integrations/azure-devops/create-azure-work-items.md',
|
|
795
|
-
'integrations/azure-devops/sync-azure-work-items.md'
|
|
796
|
-
];
|
|
797
|
-
|
|
798
|
-
azureFiles.forEach(file => {
|
|
799
|
-
files.push({
|
|
800
|
-
source: `cursor/${file}`,
|
|
801
|
-
target: `.code-captain/${file}`,
|
|
802
|
-
component: 'azure'
|
|
803
|
-
});
|
|
804
|
-
});
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
// Documentation
|
|
808
|
-
if (includeAll || selectedComponents.includes('docs')) {
|
|
809
|
-
files.push({
|
|
810
|
-
source: 'cursor/docs/best-practices.md',
|
|
811
|
-
target: '.code-captain/docs/best-practices.md',
|
|
812
|
-
component: 'docs'
|
|
813
|
-
});
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
break;
|
|
817
|
-
|
|
818
|
-
case 'copilot':
|
|
819
|
-
// Chatmodes
|
|
820
|
-
if (includeAll || selectedComponents.includes('chatmodes')) {
|
|
821
|
-
files.push(
|
|
822
|
-
{ source: 'copilot/chatmodes/Code Captain.chatmode.md', target: '.github/chatmodes/Code Captain.chatmode.md', component: 'chatmodes' }
|
|
823
|
-
);
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
// Prompts
|
|
827
|
-
if (includeAll || selectedComponents.includes('prompts')) {
|
|
828
|
-
const copilotPrompts = [
|
|
829
|
-
'create-adr.prompt.md', 'create-spec.prompt.md', 'edit-spec.prompt.md',
|
|
830
|
-
'execute-task.prompt.md', 'explain-code.prompt.md', 'initialize.prompt.md',
|
|
831
|
-
'new-command.prompt.md', 'plan-product.prompt.md', 'research.prompt.md',
|
|
832
|
-
'status.prompt.md', 'swab.prompt.md'
|
|
833
|
-
];
|
|
834
|
-
|
|
835
|
-
copilotPrompts.forEach(prompt => {
|
|
836
|
-
files.push({
|
|
837
|
-
source: `copilot/prompts/${prompt}`,
|
|
838
|
-
target: `.github/prompts/${prompt}`,
|
|
839
|
-
component: 'prompts'
|
|
840
|
-
});
|
|
841
|
-
});
|
|
842
|
-
}
|
|
843
|
-
|
|
844
|
-
// Documentation
|
|
845
|
-
if (includeAll || selectedComponents.includes('docs')) {
|
|
846
|
-
files.push({
|
|
847
|
-
source: 'copilot/docs/best-practices.md',
|
|
848
|
-
target: '.code-captain/docs/best-practices.md',
|
|
849
|
-
component: 'docs'
|
|
850
|
-
});
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
break;
|
|
854
|
-
|
|
855
|
-
case 'windsurf':
|
|
856
|
-
// Rules
|
|
857
|
-
if (includeAll || selectedComponents.includes('rules')) {
|
|
858
|
-
files.push(
|
|
859
|
-
{ source: 'windsurf/rules/cc.md', target: 'windsurf/rules/cc.md', component: 'rules' }
|
|
860
|
-
);
|
|
861
|
-
}
|
|
862
|
-
|
|
863
|
-
// Workflows
|
|
864
|
-
if (includeAll || selectedComponents.includes('workflows')) {
|
|
865
|
-
const windsurfWorkflows = [
|
|
866
|
-
'create-adr.md', 'create-spec.md', 'edit-spec.md', 'execute-task.md',
|
|
867
|
-
'explain-code.md', 'initialize.md', 'new-command.md', 'status.md'
|
|
868
|
-
];
|
|
869
|
-
|
|
870
|
-
windsurfWorkflows.forEach(workflow => {
|
|
871
|
-
files.push({
|
|
872
|
-
source: `windsurf/workflows/${workflow}`,
|
|
873
|
-
target: `windsurf/workflows/${workflow}`,
|
|
874
|
-
component: 'workflows'
|
|
875
|
-
});
|
|
876
|
-
});
|
|
877
|
-
}
|
|
878
|
-
|
|
879
|
-
break;
|
|
880
|
-
|
|
881
|
-
case 'claude':
|
|
882
|
-
// Claude agents
|
|
883
|
-
if (includeAll || selectedComponents.includes('agents')) {
|
|
884
|
-
const claudeAgents = [
|
|
885
|
-
'code-captain.md', 'spec-generator.md', 'spec-orchestrator.md',
|
|
886
|
-
'story-creator.md', 'tech-spec.md'
|
|
887
|
-
];
|
|
888
|
-
|
|
889
|
-
claudeAgents.forEach(agent => {
|
|
890
|
-
files.push({
|
|
891
|
-
source: `claude-code/agents/${agent}`,
|
|
892
|
-
target: `.code-captain/claude/agents/${agent}`,
|
|
893
|
-
component: 'agents'
|
|
894
|
-
});
|
|
895
|
-
});
|
|
896
|
-
}
|
|
897
|
-
|
|
898
|
-
// Claude commands
|
|
899
|
-
if (includeAll || selectedComponents.includes('claude-commands')) {
|
|
900
|
-
const claudeCommands = [
|
|
901
|
-
'cc-create-spec.md', 'cc-initialize.md'
|
|
902
|
-
];
|
|
903
|
-
|
|
904
|
-
claudeCommands.forEach(command => {
|
|
905
|
-
files.push({
|
|
906
|
-
source: `claude-code/commands/${command}`,
|
|
907
|
-
target: `.code-captain/claude/commands/${command}`,
|
|
908
|
-
component: 'claude-commands'
|
|
909
|
-
});
|
|
910
|
-
});
|
|
911
|
-
}
|
|
912
|
-
|
|
913
|
-
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
|
+
});
|
|
914
886
|
}
|
|
915
887
|
|
|
916
|
-
|
|
917
|
-
|
|
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
|
+
}
|
|
918
896
|
|
|
919
|
-
|
|
920
|
-
async installFiles(selectedIDE, installOptions) {
|
|
921
|
-
const selectedComponents = installOptions.installAll ? null : installOptions.selectedComponents;
|
|
922
|
-
const files = this.getIDEFiles(selectedIDE, selectedComponents);
|
|
923
|
-
const spinner = ora(`Installing ${this.ides[selectedIDE].name} integration...`).start();
|
|
897
|
+
break;
|
|
924
898
|
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
spinner.text = `Installing files... (${completed}/${files.length})`;
|
|
934
|
-
}
|
|
935
|
-
|
|
936
|
-
// Save manifest for future change detection
|
|
937
|
-
if (installOptions.changeInfo) {
|
|
938
|
-
const remoteManifest = await this.getRemoteManifest();
|
|
939
|
-
await this.saveManifest(remoteManifest, files);
|
|
940
|
-
}
|
|
941
|
-
|
|
942
|
-
spinner.succeed(`${this.ides[selectedIDE].name} integration installed successfully!`);
|
|
943
|
-
|
|
944
|
-
return {
|
|
945
|
-
totalFiles: files.length,
|
|
946
|
-
targetDir: selectedIDE === 'copilot' ? '.github + .code-captain/docs' :
|
|
947
|
-
selectedIDE === 'windsurf' ? 'windsurf' :
|
|
948
|
-
selectedIDE === 'claude' ? '.code-captain/claude' : '.code-captain (+ .cursor/rules)',
|
|
949
|
-
components: installOptions.installAll ? 'All components' : installOptions.selectedComponents.join(', '),
|
|
950
|
-
changesDetected: installOptions.changeInfo && (installOptions.changeInfo.changes.length > 0 || installOptions.changeInfo.newFiles.length > 0)
|
|
951
|
-
};
|
|
952
|
-
} catch (error) {
|
|
953
|
-
spinner.fail('Installation failed');
|
|
954
|
-
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
|
+
});
|
|
955
907
|
}
|
|
956
|
-
}
|
|
957
908
|
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
console.log(chalk.blue('1.') + ' Restart Cursor to load the new rule from ' + chalk.cyan('.cursor/rules/cc.mdc'));
|
|
982
|
-
console.log(chalk.blue('2.') + ' Use ' + chalk.cyan('cc: initialize') + ' to set up your project');
|
|
983
|
-
console.log(chalk.blue('3.') + ' Try ' + chalk.cyan('cc: plan-product') + ' for product planning');
|
|
984
|
-
console.log(chalk.blue('4.') + ' Use ' + chalk.cyan('cc: create-spec') + ' for feature specifications');
|
|
985
|
-
break;
|
|
986
|
-
|
|
987
|
-
case 'copilot':
|
|
988
|
-
console.log(chalk.blue('1.') + ' Restart VS Code to load chatmodes from ' + chalk.cyan('.github/chatmodes/'));
|
|
989
|
-
console.log(chalk.blue('2.') + ' Open GitHub Copilot Chat in VS Code');
|
|
990
|
-
console.log(chalk.blue('3.') + ' Type ' + chalk.cyan('@Code Captain') + ' to access the chatmode');
|
|
991
|
-
console.log(chalk.blue('4.') + ' Use prompts from ' + chalk.cyan('.github/prompts/') + ' for workflows');
|
|
992
|
-
break;
|
|
993
|
-
|
|
994
|
-
case 'windsurf':
|
|
995
|
-
console.log(chalk.blue('1.') + ' Restart Windsurf to load the new workflows');
|
|
996
|
-
console.log(chalk.blue('2.') + ' Use the AI agent with Code Captain commands');
|
|
997
|
-
console.log(chalk.blue('3.') + ' Try ' + chalk.cyan('cc: initialize') + ' to set up your project');
|
|
998
|
-
break;
|
|
999
|
-
|
|
1000
|
-
case 'claude':
|
|
1001
|
-
console.log(chalk.blue('1.') + ' Claude agents and commands are installed in ' + chalk.cyan('.code-captain/claude/'));
|
|
1002
|
-
console.log(chalk.blue('2.') + ' Reference the agents in ' + chalk.cyan('.code-captain/claude/agents/') + ' for specialized workflows');
|
|
1003
|
-
console.log(chalk.blue('3.') + ' Use command templates from ' + chalk.cyan('.code-captain/claude/commands/'));
|
|
1004
|
-
console.log(chalk.blue('4.') + ' Import agent contexts directly into Claude conversations');
|
|
1005
|
-
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
|
+
});
|
|
1006
932
|
}
|
|
1007
933
|
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
console.log(chalk.gray('You can safely delete backup files once you\'re satisfied with the installation.'));
|
|
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
|
+
});
|
|
1016
941
|
}
|
|
1017
|
-
}
|
|
1018
942
|
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
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",
|
|
960
|
+
});
|
|
961
|
+
});
|
|
962
|
+
}
|
|
1023
963
|
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
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"];
|
|
1028
967
|
|
|
1029
|
-
|
|
1030
|
-
|
|
968
|
+
claudeCommands.forEach((command) => {
|
|
969
|
+
files.push({
|
|
970
|
+
source: `claude-code/commands/${command}`,
|
|
971
|
+
target: `.claude/commands/${command}`,
|
|
972
|
+
component: "claude-commands",
|
|
973
|
+
});
|
|
974
|
+
});
|
|
1031
975
|
}
|
|
1032
976
|
|
|
1033
|
-
|
|
977
|
+
break;
|
|
1034
978
|
}
|
|
1035
979
|
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
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;
|
|
1050
1100
|
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
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;
|
|
1055
1121
|
|
|
1056
|
-
|
|
1057
|
-
|
|
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
|
+
}
|
|
1058
1145
|
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
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
|
+
}
|
|
1063
1187
|
|
|
1064
|
-
|
|
1065
|
-
|
|
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
|
+
}
|
|
1066
1249
|
|
|
1067
|
-
|
|
1068
|
-
|
|
1250
|
+
// Handle subcommands
|
|
1251
|
+
const args = process.argv.slice(2);
|
|
1069
1252
|
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
}
|
|
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);
|
|
1075
1257
|
}
|
|
1076
1258
|
|
|
1077
1259
|
// Run installer if called directly
|
|
1078
|
-
const isMainModule =
|
|
1079
|
-
|
|
1080
|
-
|
|
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;
|
|
1081
1264
|
|
|
1082
1265
|
if (isMainModule) {
|
|
1083
|
-
|
|
1084
|
-
|
|
1266
|
+
const installer = new CodeCaptainInstaller();
|
|
1267
|
+
installer.run();
|
|
1085
1268
|
}
|
|
1086
1269
|
|
|
1087
|
-
export default CodeCaptainInstaller;
|
|
1270
|
+
export default CodeCaptainInstaller;
|