@zeyue0329/xiaoma-cli 1.0.37 → 1.0.38
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/.idea/workspace.xml +27 -26
- package/JAVA-BACKEND-COMMANDS-REFERENCE.md +62 -52
- package/JAVA-BACKEND-ITERATION-GUIDE.md +125 -18
- package/README.md +1 -1
- package/common/utils/bmad-doc-template.md +5 -5
- package/dist/agents/analyst.txt +35 -5
- package/dist/agents/architect.txt +217 -31
- package/dist/agents/automation-orchestrator.txt +4 -4
- package/dist/agents/dev.txt +3 -3
- package/dist/agents/full-requirement-orchestrator.txt +11 -11
- package/dist/agents/qa.txt +102 -102
- package/dist/agents/sm.txt +6 -6
- package/dist/agents/ux-expert.txt +6 -1
- package/dist/agents/workflow-executor.txt +879 -0
- package/dist/agents/xiaoma-master.txt +258 -37
- package/dist/teams/team-all.txt +1223 -445
- package/dist/teams/team-fullstack-with-database.txt +384 -446
- package/dist/teams/team-fullstack.txt +258 -37
- package/dist/teams/team-ide-minimal.txt +111 -111
- package/dist/teams/team-no-ui.txt +252 -36
- package/docs/architecture-sharding-modification.md +623 -0
- package/docs/automated-requirements-analysis-outputs.md +896 -0
- package/package.json +1 -1
- package/tools/builders/web-builder.js +292 -142
- package/tools/bump-all-versions.js +50 -32
- package/tools/cli.js +52 -47
- package/tools/flattener/aggregate.js +30 -12
- package/tools/flattener/binary.js +46 -43
- package/tools/flattener/discovery.js +23 -15
- package/tools/flattener/files.js +6 -6
- package/tools/flattener/ignoreRules.js +122 -121
- package/tools/flattener/main.js +249 -144
- package/tools/flattener/projectRoot.js +74 -69
- package/tools/flattener/prompts.js +12 -10
- package/tools/flattener/stats.helpers.js +90 -61
- package/tools/flattener/stats.js +1 -1
- package/tools/flattener/test-matrix.js +225 -170
- package/tools/flattener/xml.js +31 -23
- package/tools/installer/bin/xiaoma.js +199 -153
- package/tools/installer/lib/config-loader.js +76 -47
- package/tools/installer/lib/file-manager.js +101 -44
- package/tools/installer/lib/ide-base-setup.js +49 -39
- package/tools/installer/lib/ide-setup.js +694 -380
- package/tools/installer/lib/installer.js +802 -469
- package/tools/installer/lib/memory-profiler.js +22 -12
- package/tools/installer/lib/module-manager.js +16 -14
- package/tools/installer/lib/resource-locator.js +61 -35
- package/tools/lib/dependency-resolver.js +34 -23
- package/tools/lib/yaml-utils.js +7 -2
- package/tools/preview-release-notes.js +33 -25
- package/tools/shared/bannerArt.js +3 -3
- package/tools/sync-installer-version.js +16 -7
- package/tools/upgraders/v3-to-v4-upgrader.js +244 -163
- package/tools/version-bump.js +24 -18
- package/tools/xiaoma-npx-wrapper.js +15 -10
- package/tools/yaml-format.js +60 -36
- package/xiaoma-core/agent-teams/team-fullstack-with-database.yaml +0 -1
- package/xiaoma-core/agents/automated-fix-validator.yaml +2 -1
- package/xiaoma-core/agents/automated-quality-validator.yaml +10 -5
- package/xiaoma-core/agents/automation-orchestrator.md +4 -4
- package/xiaoma-core/agents/dev.md +4 -4
- package/xiaoma-core/agents/enhanced-workflow-orchestrator.yaml +2 -1
- package/xiaoma-core/agents/full-requirement-orchestrator.md +11 -11
- package/xiaoma-core/agents/global-requirements-auditor.yaml +11 -3
- package/xiaoma-core/agents/intelligent-template-adapter.yaml +19 -5
- package/xiaoma-core/agents/master-execution-engine.yaml +19 -5
- package/xiaoma-core/agents/workflow-executor.md +8 -4
- package/xiaoma-core/agents/xiaoma-master.md +1 -1
- package/xiaoma-core/data/test-levels-framework.md +12 -12
- package/xiaoma-core/tasks/analyze-existing-database.md +1 -1
- package/xiaoma-core/tasks/apply-qa-fixes.md +3 -3
- package/xiaoma-core/tasks/batch-story-generation.md +22 -22
- package/xiaoma-core/tasks/create-enhanced-story-with-database.md +6 -6
- package/xiaoma-core/tasks/nfr-assess.md +6 -6
- package/xiaoma-core/tasks/project-integration-testing.md +42 -42
- package/xiaoma-core/tasks/qa-gate.md +23 -23
- package/xiaoma-core/tasks/review-story.md +18 -18
- package/xiaoma-core/tasks/risk-profile.md +25 -25
- package/xiaoma-core/tasks/serial-development-orchestration.md +51 -51
- package/xiaoma-core/tasks/test-design.md +9 -9
- package/xiaoma-core/tasks/trace-requirements.md +21 -21
- package/xiaoma-core/templates/competitor-analysis-tmpl.yaml +35 -5
- package/xiaoma-core/templates/front-end-architecture-tmpl.yaml +77 -11
- package/xiaoma-core/templates/front-end-spec-tmpl.yaml +6 -1
- package/xiaoma-core/templates/fullstack-architecture-tmpl.yaml +140 -20
- package/xiaoma-core/templates/global-qa-monitoring-tmpl.yaml +2 -1
- package/xiaoma-core/templates/requirements-coverage-audit.yaml +2 -1
- package/xiaoma-core/workflows/automated-requirements-analysis.yaml +4 -4
- package/dist/agents/database-architect.txt +0 -322
|
@@ -1,40 +1,47 @@
|
|
|
1
|
-
const path = require(
|
|
2
|
-
const fs = require(
|
|
3
|
-
const chalk = require(
|
|
4
|
-
const ora = require(
|
|
5
|
-
const inquirer = require(
|
|
6
|
-
const fileManager = require(
|
|
7
|
-
const configLoader = require(
|
|
8
|
-
const ideSetup = require(
|
|
9
|
-
const { extractYamlFromAgent } = require(
|
|
10
|
-
const resourceLocator = require(
|
|
1
|
+
const path = require("node:path");
|
|
2
|
+
const fs = require("fs-extra");
|
|
3
|
+
const chalk = require("chalk");
|
|
4
|
+
const ora = require("ora");
|
|
5
|
+
const inquirer = require("inquirer");
|
|
6
|
+
const fileManager = require("./file-manager");
|
|
7
|
+
const configLoader = require("./config-loader");
|
|
8
|
+
const ideSetup = require("./ide-setup");
|
|
9
|
+
const { extractYamlFromAgent } = require("../../lib/yaml-utils");
|
|
10
|
+
const resourceLocator = require("./resource-locator");
|
|
11
11
|
|
|
12
12
|
class Installer {
|
|
13
13
|
async getCoreVersion() {
|
|
14
14
|
try {
|
|
15
15
|
// Always use package.json version
|
|
16
|
-
const packagePath = path.join(
|
|
16
|
+
const packagePath = path.join(
|
|
17
|
+
__dirname,
|
|
18
|
+
"..",
|
|
19
|
+
"..",
|
|
20
|
+
"..",
|
|
21
|
+
"package.json",
|
|
22
|
+
);
|
|
17
23
|
const packageJson = require(packagePath);
|
|
18
24
|
return packageJson.version;
|
|
19
25
|
} catch {
|
|
20
26
|
console.warn("Could not read version from package.json, using 'unknown'");
|
|
21
|
-
return
|
|
27
|
+
return "unknown";
|
|
22
28
|
}
|
|
23
29
|
}
|
|
24
30
|
|
|
25
31
|
async install(config) {
|
|
26
|
-
const spinner = ora(
|
|
32
|
+
const spinner = ora("Analyzing installation directory...").start();
|
|
27
33
|
|
|
28
34
|
try {
|
|
29
35
|
// Store the original CWD where npx was executed
|
|
30
|
-
const originalCwd =
|
|
36
|
+
const originalCwd =
|
|
37
|
+
process.env.INIT_CWD || process.env.PWD || process.cwd();
|
|
31
38
|
|
|
32
39
|
// Resolve installation directory relative to where the user ran the command
|
|
33
40
|
let installDir = path.isAbsolute(config.directory)
|
|
34
41
|
? config.directory
|
|
35
42
|
: path.resolve(originalCwd, config.directory);
|
|
36
43
|
|
|
37
|
-
if (path.basename(installDir) ===
|
|
44
|
+
if (path.basename(installDir) === ".xiaoma-core") {
|
|
38
45
|
// If user points directly to .xiaoma-core, treat its parent as the project root
|
|
39
46
|
installDir = path.dirname(installDir);
|
|
40
47
|
}
|
|
@@ -51,42 +58,42 @@ class Installer {
|
|
|
51
58
|
|
|
52
59
|
const { action } = await inquirer.prompt([
|
|
53
60
|
{
|
|
54
|
-
type:
|
|
55
|
-
name:
|
|
56
|
-
message:
|
|
61
|
+
type: "list",
|
|
62
|
+
name: "action",
|
|
63
|
+
message: "What would you like to do?",
|
|
57
64
|
choices: [
|
|
58
65
|
{
|
|
59
|
-
name:
|
|
60
|
-
value:
|
|
66
|
+
name: "Create the directory and continue",
|
|
67
|
+
value: "create",
|
|
61
68
|
},
|
|
62
69
|
{
|
|
63
|
-
name:
|
|
64
|
-
value:
|
|
70
|
+
name: "Choose a different directory",
|
|
71
|
+
value: "change",
|
|
65
72
|
},
|
|
66
73
|
{
|
|
67
|
-
name:
|
|
68
|
-
value:
|
|
74
|
+
name: "Cancel installation",
|
|
75
|
+
value: "cancel",
|
|
69
76
|
},
|
|
70
77
|
],
|
|
71
78
|
},
|
|
72
79
|
]);
|
|
73
80
|
|
|
74
81
|
switch (action) {
|
|
75
|
-
case
|
|
76
|
-
console.log(
|
|
82
|
+
case "cancel": {
|
|
83
|
+
console.log("Installation cancelled.");
|
|
77
84
|
process.exit(0);
|
|
78
85
|
|
|
79
86
|
break;
|
|
80
87
|
}
|
|
81
|
-
case
|
|
88
|
+
case "change": {
|
|
82
89
|
const { newDirectory } = await inquirer.prompt([
|
|
83
90
|
{
|
|
84
|
-
type:
|
|
85
|
-
name:
|
|
86
|
-
message:
|
|
91
|
+
type: "input",
|
|
92
|
+
name: "newDirectory",
|
|
93
|
+
message: "Enter the new directory path:",
|
|
87
94
|
validate: (input) => {
|
|
88
95
|
if (!input.trim()) {
|
|
89
|
-
return
|
|
96
|
+
return "Please enter a valid directory path";
|
|
90
97
|
}
|
|
91
98
|
return true;
|
|
92
99
|
},
|
|
@@ -96,13 +103,15 @@ class Installer {
|
|
|
96
103
|
config.directory = newDirectory;
|
|
97
104
|
return await this.install(config); // Recursive call with new directory
|
|
98
105
|
}
|
|
99
|
-
case
|
|
106
|
+
case "create": {
|
|
100
107
|
try {
|
|
101
108
|
await fileManager.ensureDirectory(installDir);
|
|
102
109
|
console.log(`✓ Created directory: ${installDir}`);
|
|
103
110
|
} catch (error) {
|
|
104
111
|
console.error(`Failed to create directory: ${error.message}`);
|
|
105
|
-
console.error(
|
|
112
|
+
console.error(
|
|
113
|
+
"You may need to check permissions or use a different path.",
|
|
114
|
+
);
|
|
106
115
|
process.exit(1);
|
|
107
116
|
}
|
|
108
117
|
|
|
@@ -111,17 +120,22 @@ class Installer {
|
|
|
111
120
|
// No default
|
|
112
121
|
}
|
|
113
122
|
|
|
114
|
-
spinner.start(
|
|
123
|
+
spinner.start("Analyzing installation directory...");
|
|
115
124
|
}
|
|
116
125
|
|
|
117
126
|
// If this is an update request from early detection, handle it directly
|
|
118
|
-
if (config.installType ===
|
|
127
|
+
if (config.installType === "update") {
|
|
119
128
|
const state = await this.detectInstallationState(installDir);
|
|
120
|
-
if (state.type ===
|
|
121
|
-
return await this.performUpdate(
|
|
129
|
+
if (state.type === "v4_existing") {
|
|
130
|
+
return await this.performUpdate(
|
|
131
|
+
config,
|
|
132
|
+
installDir,
|
|
133
|
+
state.manifest,
|
|
134
|
+
spinner,
|
|
135
|
+
);
|
|
122
136
|
} else {
|
|
123
|
-
spinner.fail(
|
|
124
|
-
throw new Error(
|
|
137
|
+
spinner.fail("No existing v4 installation found to update");
|
|
138
|
+
throw new Error("No existing v4 installation found");
|
|
125
139
|
}
|
|
126
140
|
}
|
|
127
141
|
|
|
@@ -130,28 +144,43 @@ class Installer {
|
|
|
130
144
|
|
|
131
145
|
// Handle different states
|
|
132
146
|
switch (state.type) {
|
|
133
|
-
case
|
|
147
|
+
case "clean": {
|
|
134
148
|
return await this.performFreshInstall(config, installDir, spinner);
|
|
135
149
|
}
|
|
136
150
|
|
|
137
|
-
case
|
|
138
|
-
return await this.handleExistingV4Installation(
|
|
151
|
+
case "v4_existing": {
|
|
152
|
+
return await this.handleExistingV4Installation(
|
|
153
|
+
config,
|
|
154
|
+
installDir,
|
|
155
|
+
state,
|
|
156
|
+
spinner,
|
|
157
|
+
);
|
|
139
158
|
}
|
|
140
159
|
|
|
141
|
-
case
|
|
142
|
-
return await this.handleV3Installation(
|
|
160
|
+
case "v3_existing": {
|
|
161
|
+
return await this.handleV3Installation(
|
|
162
|
+
config,
|
|
163
|
+
installDir,
|
|
164
|
+
state,
|
|
165
|
+
spinner,
|
|
166
|
+
);
|
|
143
167
|
}
|
|
144
168
|
|
|
145
|
-
case
|
|
146
|
-
return await this.handleUnknownInstallation(
|
|
169
|
+
case "unknown_existing": {
|
|
170
|
+
return await this.handleUnknownInstallation(
|
|
171
|
+
config,
|
|
172
|
+
installDir,
|
|
173
|
+
state,
|
|
174
|
+
spinner,
|
|
175
|
+
);
|
|
147
176
|
}
|
|
148
177
|
}
|
|
149
178
|
} catch (error) {
|
|
150
179
|
// Check if modules were initialized
|
|
151
180
|
if (spinner) {
|
|
152
|
-
spinner.fail(
|
|
181
|
+
spinner.fail("Installation failed");
|
|
153
182
|
} else {
|
|
154
|
-
console.error(
|
|
183
|
+
console.error("Installation failed:", error.message);
|
|
155
184
|
}
|
|
156
185
|
throw error;
|
|
157
186
|
}
|
|
@@ -159,7 +188,7 @@ class Installer {
|
|
|
159
188
|
|
|
160
189
|
async detectInstallationState(installDir) {
|
|
161
190
|
const state = {
|
|
162
|
-
type:
|
|
191
|
+
type: "clean",
|
|
163
192
|
hasV4Manifest: false,
|
|
164
193
|
hasV3Structure: false,
|
|
165
194
|
hasBmadCore: false,
|
|
@@ -174,11 +203,11 @@ class Installer {
|
|
|
174
203
|
}
|
|
175
204
|
|
|
176
205
|
// Check for V4 installation (has .xiaoma-core with manifest)
|
|
177
|
-
const bmadCorePath = path.join(installDir,
|
|
178
|
-
const manifestPath = path.join(bmadCorePath,
|
|
206
|
+
const bmadCorePath = path.join(installDir, ".xiaoma-core");
|
|
207
|
+
const manifestPath = path.join(bmadCorePath, "install-manifest.yaml");
|
|
179
208
|
|
|
180
209
|
if (await fileManager.pathExists(manifestPath)) {
|
|
181
|
-
state.type =
|
|
210
|
+
state.type = "v4_existing";
|
|
182
211
|
state.hasV4Manifest = true;
|
|
183
212
|
state.hasBmadCore = true;
|
|
184
213
|
state.manifest = await fileManager.readManifest(installDir);
|
|
@@ -186,25 +215,25 @@ class Installer {
|
|
|
186
215
|
}
|
|
187
216
|
|
|
188
217
|
// Check for V3 installation (has bmad-agent directory)
|
|
189
|
-
const bmadAgentPath = path.join(installDir,
|
|
218
|
+
const bmadAgentPath = path.join(installDir, "bmad-agent");
|
|
190
219
|
if (await fileManager.pathExists(bmadAgentPath)) {
|
|
191
|
-
state.type =
|
|
220
|
+
state.type = "v3_existing";
|
|
192
221
|
state.hasV3Structure = true;
|
|
193
222
|
return state;
|
|
194
223
|
}
|
|
195
224
|
|
|
196
225
|
// Check for .xiaoma-core without manifest (broken V4 or manual copy)
|
|
197
226
|
if (await fileManager.pathExists(bmadCorePath)) {
|
|
198
|
-
state.type =
|
|
227
|
+
state.type = "unknown_existing";
|
|
199
228
|
state.hasBmadCore = true;
|
|
200
229
|
return state;
|
|
201
230
|
}
|
|
202
231
|
|
|
203
232
|
// Check if directory has other files
|
|
204
|
-
const files = await resourceLocator.findFiles(
|
|
233
|
+
const files = await resourceLocator.findFiles("**/*", {
|
|
205
234
|
cwd: installDir,
|
|
206
235
|
nodir: true,
|
|
207
|
-
ignore: [
|
|
236
|
+
ignore: ["**/.git/**", "**/node_modules/**"],
|
|
208
237
|
});
|
|
209
238
|
|
|
210
239
|
if (files.length > 0) {
|
|
@@ -221,41 +250,41 @@ class Installer {
|
|
|
221
250
|
}
|
|
222
251
|
|
|
223
252
|
async performFreshInstall(config, installDir, spinner, options = {}) {
|
|
224
|
-
spinner.text =
|
|
253
|
+
spinner.text = "Installing XiaoMa-Cli...";
|
|
225
254
|
|
|
226
255
|
let files = [];
|
|
227
256
|
|
|
228
257
|
switch (config.installType) {
|
|
229
|
-
case
|
|
258
|
+
case "full": {
|
|
230
259
|
// Full installation - copy entire .xiaoma-core folder as a subdirectory
|
|
231
|
-
spinner.text =
|
|
260
|
+
spinner.text = "Copying complete .xiaoma-core folder...";
|
|
232
261
|
const sourceDir = resourceLocator.getBmadCorePath();
|
|
233
|
-
const bmadCoreDestDir = path.join(installDir,
|
|
262
|
+
const bmadCoreDestDir = path.join(installDir, ".xiaoma-core");
|
|
234
263
|
await fileManager.copyDirectoryWithRootReplacement(
|
|
235
264
|
sourceDir,
|
|
236
265
|
bmadCoreDestDir,
|
|
237
|
-
|
|
266
|
+
".xiaoma-core",
|
|
238
267
|
);
|
|
239
268
|
|
|
240
269
|
// Copy common/ items to .xiaoma-core
|
|
241
|
-
spinner.text =
|
|
242
|
-
await this.copyCommonItems(installDir,
|
|
270
|
+
spinner.text = "Copying common utilities...";
|
|
271
|
+
await this.copyCommonItems(installDir, ".xiaoma-core", spinner);
|
|
243
272
|
|
|
244
273
|
// Copy documentation files from docs/ to .xiaoma-core
|
|
245
|
-
spinner.text =
|
|
246
|
-
await this.copyDocsItems(installDir,
|
|
274
|
+
spinner.text = "Copying documentation files...";
|
|
275
|
+
await this.copyDocsItems(installDir, ".xiaoma-core", spinner);
|
|
247
276
|
|
|
248
277
|
// Get list of all files for manifest
|
|
249
|
-
const foundFiles = await resourceLocator.findFiles(
|
|
278
|
+
const foundFiles = await resourceLocator.findFiles("**/*", {
|
|
250
279
|
cwd: bmadCoreDestDir,
|
|
251
280
|
nodir: true,
|
|
252
|
-
ignore: [
|
|
281
|
+
ignore: ["**/.git/**", "**/node_modules/**"],
|
|
253
282
|
});
|
|
254
|
-
files = foundFiles.map((file) => path.join(
|
|
283
|
+
files = foundFiles.map((file) => path.join(".xiaoma-core", file));
|
|
255
284
|
|
|
256
285
|
break;
|
|
257
286
|
}
|
|
258
|
-
case
|
|
287
|
+
case "single-agent": {
|
|
259
288
|
// Single agent installation
|
|
260
289
|
spinner.text = `Installing ${config.agent} agent...`;
|
|
261
290
|
|
|
@@ -263,44 +292,54 @@ class Installer {
|
|
|
263
292
|
const agentPath = configLoader.getAgentPath(config.agent);
|
|
264
293
|
const destinationAgentPath = path.join(
|
|
265
294
|
installDir,
|
|
266
|
-
|
|
267
|
-
|
|
295
|
+
".xiaoma-core",
|
|
296
|
+
"agents",
|
|
268
297
|
`${config.agent}.md`,
|
|
269
298
|
);
|
|
270
299
|
await fileManager.copyFileWithRootReplacement(
|
|
271
300
|
agentPath,
|
|
272
301
|
destinationAgentPath,
|
|
273
|
-
|
|
302
|
+
".xiaoma-core",
|
|
274
303
|
);
|
|
275
304
|
files.push(`.xiaoma-core/agents/${config.agent}.md`);
|
|
276
305
|
|
|
277
306
|
// Copy dependencies
|
|
278
|
-
const { all: dependencies } =
|
|
307
|
+
const { all: dependencies } =
|
|
308
|
+
await resourceLocator.getAgentDependencies(config.agent);
|
|
279
309
|
const sourceBase = resourceLocator.getBmadCorePath();
|
|
280
310
|
|
|
281
311
|
for (const dep of dependencies) {
|
|
282
312
|
spinner.text = `Copying dependency: ${dep}`;
|
|
283
313
|
|
|
284
|
-
if (dep.includes(
|
|
314
|
+
if (dep.includes("*")) {
|
|
285
315
|
// Handle glob patterns with {root} replacement
|
|
286
316
|
const copiedFiles = await fileManager.copyGlobPattern(
|
|
287
|
-
dep.replace(
|
|
317
|
+
dep.replace(".xiaoma-core/", ""),
|
|
288
318
|
sourceBase,
|
|
289
|
-
path.join(installDir,
|
|
290
|
-
|
|
319
|
+
path.join(installDir, ".xiaoma-core"),
|
|
320
|
+
".xiaoma-core",
|
|
291
321
|
);
|
|
292
322
|
files.push(...copiedFiles.map((f) => `.xiaoma-core/${f}`));
|
|
293
323
|
} else {
|
|
294
324
|
// Handle single files with {root} replacement if needed
|
|
295
|
-
const sourcePath = path.join(
|
|
325
|
+
const sourcePath = path.join(
|
|
326
|
+
sourceBase,
|
|
327
|
+
dep.replace(".xiaoma-core/", ""),
|
|
328
|
+
);
|
|
296
329
|
const destinationPath = path.join(installDir, dep);
|
|
297
330
|
|
|
298
331
|
const needsRootReplacement =
|
|
299
|
-
dep.endsWith(
|
|
332
|
+
dep.endsWith(".md") ||
|
|
333
|
+
dep.endsWith(".yaml") ||
|
|
334
|
+
dep.endsWith(".yml");
|
|
300
335
|
let success = false;
|
|
301
336
|
|
|
302
337
|
success = await (needsRootReplacement
|
|
303
|
-
? fileManager.copyFileWithRootReplacement(
|
|
338
|
+
? fileManager.copyFileWithRootReplacement(
|
|
339
|
+
sourcePath,
|
|
340
|
+
destinationPath,
|
|
341
|
+
".xiaoma-core",
|
|
342
|
+
)
|
|
304
343
|
: fileManager.copyFile(sourcePath, destinationPath));
|
|
305
344
|
|
|
306
345
|
if (success) {
|
|
@@ -310,49 +349,68 @@ class Installer {
|
|
|
310
349
|
}
|
|
311
350
|
|
|
312
351
|
// Copy common/ items to .xiaoma-core
|
|
313
|
-
spinner.text =
|
|
314
|
-
const commonFiles = await this.copyCommonItems(
|
|
352
|
+
spinner.text = "Copying common utilities...";
|
|
353
|
+
const commonFiles = await this.copyCommonItems(
|
|
354
|
+
installDir,
|
|
355
|
+
".xiaoma-core",
|
|
356
|
+
spinner,
|
|
357
|
+
);
|
|
315
358
|
files.push(...commonFiles);
|
|
316
359
|
|
|
317
360
|
// Copy documentation files from docs/ to .xiaoma-core
|
|
318
|
-
spinner.text =
|
|
319
|
-
const documentFiles = await this.copyDocsItems(
|
|
361
|
+
spinner.text = "Copying documentation files...";
|
|
362
|
+
const documentFiles = await this.copyDocsItems(
|
|
363
|
+
installDir,
|
|
364
|
+
".xiaoma-core",
|
|
365
|
+
spinner,
|
|
366
|
+
);
|
|
320
367
|
files.push(...documentFiles);
|
|
321
368
|
|
|
322
369
|
break;
|
|
323
370
|
}
|
|
324
|
-
case
|
|
371
|
+
case "team": {
|
|
325
372
|
// Team installation
|
|
326
373
|
spinner.text = `Installing ${config.team} team...`;
|
|
327
374
|
|
|
328
375
|
// Get team dependencies
|
|
329
|
-
const teamDependencies = await configLoader.getTeamDependencies(
|
|
376
|
+
const teamDependencies = await configLoader.getTeamDependencies(
|
|
377
|
+
config.team,
|
|
378
|
+
);
|
|
330
379
|
const sourceBase = resourceLocator.getBmadCorePath();
|
|
331
380
|
|
|
332
381
|
// Install all team dependencies
|
|
333
382
|
for (const dep of teamDependencies) {
|
|
334
383
|
spinner.text = `Copying team dependency: ${dep}`;
|
|
335
384
|
|
|
336
|
-
if (dep.includes(
|
|
385
|
+
if (dep.includes("*")) {
|
|
337
386
|
// Handle glob patterns with {root} replacement
|
|
338
387
|
const copiedFiles = await fileManager.copyGlobPattern(
|
|
339
|
-
dep.replace(
|
|
388
|
+
dep.replace(".xiaoma-core/", ""),
|
|
340
389
|
sourceBase,
|
|
341
|
-
path.join(installDir,
|
|
342
|
-
|
|
390
|
+
path.join(installDir, ".xiaoma-core"),
|
|
391
|
+
".xiaoma-core",
|
|
343
392
|
);
|
|
344
393
|
files.push(...copiedFiles.map((f) => `.xiaoma-core/${f}`));
|
|
345
394
|
} else {
|
|
346
395
|
// Handle single files with {root} replacement if needed
|
|
347
|
-
const sourcePath = path.join(
|
|
396
|
+
const sourcePath = path.join(
|
|
397
|
+
sourceBase,
|
|
398
|
+
dep.replace(".xiaoma-core/", ""),
|
|
399
|
+
);
|
|
348
400
|
const destinationPath = path.join(installDir, dep);
|
|
349
401
|
|
|
350
402
|
const needsRootReplacement =
|
|
351
|
-
dep.endsWith(
|
|
403
|
+
dep.endsWith(".md") ||
|
|
404
|
+
dep.endsWith(".yaml") ||
|
|
405
|
+
dep.endsWith(".yml");
|
|
352
406
|
let success = false;
|
|
353
407
|
|
|
354
408
|
success = await (needsRootReplacement
|
|
355
|
-
? fileManager.copyFileWithRootReplacement(
|
|
409
|
+
? fileManager.copyFileWithRootReplacement(
|
|
410
|
+
sourcePath,
|
|
411
|
+
destinationPath,
|
|
412
|
+
".xiaoma-core",
|
|
413
|
+
)
|
|
356
414
|
: fileManager.copyFile(sourcePath, destinationPath));
|
|
357
415
|
|
|
358
416
|
if (success) {
|
|
@@ -362,21 +420,29 @@ class Installer {
|
|
|
362
420
|
}
|
|
363
421
|
|
|
364
422
|
// Copy common/ items to .xiaoma-core
|
|
365
|
-
spinner.text =
|
|
366
|
-
const commonFiles = await this.copyCommonItems(
|
|
423
|
+
spinner.text = "Copying common utilities...";
|
|
424
|
+
const commonFiles = await this.copyCommonItems(
|
|
425
|
+
installDir,
|
|
426
|
+
".xiaoma-core",
|
|
427
|
+
spinner,
|
|
428
|
+
);
|
|
367
429
|
files.push(...commonFiles);
|
|
368
430
|
|
|
369
431
|
// Copy documentation files from docs/ to .xiaoma-core
|
|
370
|
-
spinner.text =
|
|
371
|
-
const documentFiles = await this.copyDocsItems(
|
|
432
|
+
spinner.text = "Copying documentation files...";
|
|
433
|
+
const documentFiles = await this.copyDocsItems(
|
|
434
|
+
installDir,
|
|
435
|
+
".xiaoma-core",
|
|
436
|
+
spinner,
|
|
437
|
+
);
|
|
372
438
|
files.push(...documentFiles);
|
|
373
439
|
|
|
374
440
|
break;
|
|
375
441
|
}
|
|
376
|
-
case
|
|
442
|
+
case "expansion-only": {
|
|
377
443
|
// Expansion-only installation - DO NOT create .xiaoma-core
|
|
378
444
|
// Only install expansion packs
|
|
379
|
-
spinner.text =
|
|
445
|
+
spinner.text = "Installing expansion packs only...";
|
|
380
446
|
|
|
381
447
|
break;
|
|
382
448
|
}
|
|
@@ -394,9 +460,10 @@ class Installer {
|
|
|
394
460
|
|
|
395
461
|
// Install web bundles if requested
|
|
396
462
|
if (config.includeWebBundles && config.webBundlesDirectory) {
|
|
397
|
-
spinner.text =
|
|
463
|
+
spinner.text = "Installing web bundles...";
|
|
398
464
|
// Resolve web bundles directory using the same logic as the main installation directory
|
|
399
|
-
const originalCwd =
|
|
465
|
+
const originalCwd =
|
|
466
|
+
process.env.INIT_CWD || process.env.PWD || process.cwd();
|
|
400
467
|
let resolvedWebBundlesDir = path.isAbsolute(config.webBundlesDirectory)
|
|
401
468
|
? config.webBundlesDirectory
|
|
402
469
|
: path.resolve(originalCwd, config.webBundlesDirectory);
|
|
@@ -409,31 +476,38 @@ class Installer {
|
|
|
409
476
|
for (const ide of ides) {
|
|
410
477
|
spinner.text = `Setting up ${ide} integration...`;
|
|
411
478
|
let preConfiguredSettings = null;
|
|
412
|
-
if (ide ===
|
|
479
|
+
if (ide === "github-copilot") {
|
|
413
480
|
preConfiguredSettings = config.githubCopilotConfig;
|
|
414
|
-
} else if (ide ===
|
|
481
|
+
} else if (ide === "auggie-cli") {
|
|
415
482
|
preConfiguredSettings = config.augmentCodeConfig;
|
|
416
483
|
}
|
|
417
|
-
await ideSetup.setup(
|
|
484
|
+
await ideSetup.setup(
|
|
485
|
+
ide,
|
|
486
|
+
installDir,
|
|
487
|
+
config.agent,
|
|
488
|
+
spinner,
|
|
489
|
+
preConfiguredSettings,
|
|
490
|
+
);
|
|
418
491
|
}
|
|
419
492
|
}
|
|
420
493
|
|
|
421
494
|
// Modify core-config.yaml if sharding preferences were provided
|
|
422
495
|
if (
|
|
423
|
-
config.installType !==
|
|
424
|
-
(config.prdSharded !== undefined ||
|
|
496
|
+
config.installType !== "expansion-only" &&
|
|
497
|
+
(config.prdSharded !== undefined ||
|
|
498
|
+
config.architectureSharded !== undefined)
|
|
425
499
|
) {
|
|
426
|
-
spinner.text =
|
|
500
|
+
spinner.text = "Configuring document sharding settings...";
|
|
427
501
|
await fileManager.modifyCoreConfig(installDir, config);
|
|
428
502
|
}
|
|
429
503
|
|
|
430
504
|
// Create manifest (skip for expansion-only installations)
|
|
431
|
-
if (config.installType !==
|
|
432
|
-
spinner.text =
|
|
505
|
+
if (config.installType !== "expansion-only") {
|
|
506
|
+
spinner.text = "Creating installation manifest...";
|
|
433
507
|
await fileManager.createManifest(installDir, config, files);
|
|
434
508
|
}
|
|
435
509
|
|
|
436
|
-
spinner.succeed(
|
|
510
|
+
spinner.succeed("Installation complete!");
|
|
437
511
|
this.showSuccessMessage(config, installDir, options);
|
|
438
512
|
}
|
|
439
513
|
|
|
@@ -444,15 +518,20 @@ class Installer {
|
|
|
444
518
|
const newVersion = await this.getCoreVersion();
|
|
445
519
|
const versionCompare = this.compareVersions(currentVersion, newVersion);
|
|
446
520
|
|
|
447
|
-
console.log(chalk.yellow(
|
|
521
|
+
console.log(chalk.yellow("\n🔍 Found existing XiaoMa v4 installation"));
|
|
448
522
|
console.log(` Directory: ${installDir}`);
|
|
449
523
|
console.log(` Current version: ${currentVersion}`);
|
|
450
524
|
console.log(` Available version: ${newVersion}`);
|
|
451
|
-
console.log(
|
|
525
|
+
console.log(
|
|
526
|
+
` Installed: ${new Date(state.manifest.installed_at).toLocaleDateString()}`,
|
|
527
|
+
);
|
|
452
528
|
|
|
453
529
|
// Check file integrity
|
|
454
|
-
spinner.start(
|
|
455
|
-
const integrity = await fileManager.checkFileIntegrity(
|
|
530
|
+
spinner.start("Checking installation integrity...");
|
|
531
|
+
const integrity = await fileManager.checkFileIntegrity(
|
|
532
|
+
installDir,
|
|
533
|
+
state.manifest,
|
|
534
|
+
);
|
|
456
535
|
spinner.stop();
|
|
457
536
|
|
|
458
537
|
const hasMissingFiles = integrity.missing.length > 0;
|
|
@@ -460,27 +539,33 @@ class Installer {
|
|
|
460
539
|
const hasIntegrityIssues = hasMissingFiles || hasModifiedFiles;
|
|
461
540
|
|
|
462
541
|
if (hasIntegrityIssues) {
|
|
463
|
-
console.log(chalk.red(
|
|
542
|
+
console.log(chalk.red("\n⚠️ Installation issues detected:"));
|
|
464
543
|
if (hasMissingFiles) {
|
|
465
544
|
console.log(chalk.red(` Missing files: ${integrity.missing.length}`));
|
|
466
545
|
if (integrity.missing.length <= 5) {
|
|
467
|
-
for (const file of integrity.missing)
|
|
546
|
+
for (const file of integrity.missing)
|
|
547
|
+
console.log(chalk.dim(` - ${file}`));
|
|
468
548
|
}
|
|
469
549
|
}
|
|
470
550
|
if (hasModifiedFiles) {
|
|
471
|
-
console.log(
|
|
551
|
+
console.log(
|
|
552
|
+
chalk.yellow(` Modified files: ${integrity.modified.length}`),
|
|
553
|
+
);
|
|
472
554
|
if (integrity.modified.length <= 5) {
|
|
473
|
-
for (const file of integrity.modified)
|
|
555
|
+
for (const file of integrity.modified)
|
|
556
|
+
console.log(chalk.dim(` - ${file}`));
|
|
474
557
|
}
|
|
475
558
|
}
|
|
476
559
|
}
|
|
477
560
|
|
|
478
561
|
// Show existing expansion packs
|
|
479
562
|
if (Object.keys(state.expansionPacks).length > 0) {
|
|
480
|
-
console.log(chalk.cyan(
|
|
563
|
+
console.log(chalk.cyan("\n📦 Installed expansion packs:"));
|
|
481
564
|
for (const [packId, packInfo] of Object.entries(state.expansionPacks)) {
|
|
482
565
|
if (packInfo.hasManifest && packInfo.manifest) {
|
|
483
|
-
console.log(
|
|
566
|
+
console.log(
|
|
567
|
+
` - ${packId} (v${packInfo.manifest.version || "unknown"})`,
|
|
568
|
+
);
|
|
484
569
|
} else {
|
|
485
570
|
console.log(` - ${packId} (no manifest)`);
|
|
486
571
|
}
|
|
@@ -490,72 +575,86 @@ class Installer {
|
|
|
490
575
|
let choices = [];
|
|
491
576
|
|
|
492
577
|
if (versionCompare < 0) {
|
|
493
|
-
console.log(chalk.cyan(
|
|
578
|
+
console.log(chalk.cyan("\n⬆️ Upgrade available for XiaoMa-Cli core"));
|
|
494
579
|
choices.push({
|
|
495
580
|
name: `Upgrade XiaoMa-Cli core (v${currentVersion} → v${newVersion})`,
|
|
496
|
-
value:
|
|
581
|
+
value: "upgrade",
|
|
497
582
|
});
|
|
498
583
|
} else if (versionCompare === 0) {
|
|
499
584
|
if (hasIntegrityIssues) {
|
|
500
585
|
// Offer repair option when files are missing or modified
|
|
501
586
|
choices.push({
|
|
502
|
-
name:
|
|
503
|
-
value:
|
|
587
|
+
name: "Repair installation (restore missing/modified files)",
|
|
588
|
+
value: "repair",
|
|
504
589
|
});
|
|
505
590
|
}
|
|
506
|
-
console.log(chalk.yellow(
|
|
591
|
+
console.log(chalk.yellow("\n⚠️ Same version already installed"));
|
|
507
592
|
choices.push({
|
|
508
593
|
name: `Force reinstall XiaoMa-Cli core (v${currentVersion} - reinstall)`,
|
|
509
|
-
value:
|
|
594
|
+
value: "reinstall",
|
|
510
595
|
});
|
|
511
596
|
} else {
|
|
512
|
-
console.log(
|
|
597
|
+
console.log(
|
|
598
|
+
chalk.yellow("\n⬇️ Installed version is newer than available"),
|
|
599
|
+
);
|
|
513
600
|
choices.push({
|
|
514
601
|
name: `Downgrade XiaoMa-Cli core (v${currentVersion} → v${newVersion})`,
|
|
515
|
-
value:
|
|
602
|
+
value: "reinstall",
|
|
516
603
|
});
|
|
517
604
|
}
|
|
518
605
|
|
|
519
606
|
choices.push(
|
|
520
|
-
{ name:
|
|
521
|
-
{ name:
|
|
607
|
+
{ name: "Add/update expansion packs only", value: "expansions" },
|
|
608
|
+
{ name: "Cancel", value: "cancel" },
|
|
522
609
|
);
|
|
523
610
|
|
|
524
611
|
const { action } = await inquirer.prompt([
|
|
525
612
|
{
|
|
526
|
-
type:
|
|
527
|
-
name:
|
|
528
|
-
message:
|
|
613
|
+
type: "list",
|
|
614
|
+
name: "action",
|
|
615
|
+
message: "What would you like to do?",
|
|
529
616
|
choices: choices,
|
|
530
617
|
},
|
|
531
618
|
]);
|
|
532
619
|
|
|
533
620
|
switch (action) {
|
|
534
|
-
case
|
|
535
|
-
return await this.performUpdate(
|
|
621
|
+
case "upgrade": {
|
|
622
|
+
return await this.performUpdate(
|
|
623
|
+
config,
|
|
624
|
+
installDir,
|
|
625
|
+
state.manifest,
|
|
626
|
+
spinner,
|
|
627
|
+
);
|
|
536
628
|
}
|
|
537
|
-
case
|
|
629
|
+
case "repair": {
|
|
538
630
|
// For repair, restore missing/modified files while backing up modified ones
|
|
539
|
-
return await this.performRepair(
|
|
631
|
+
return await this.performRepair(
|
|
632
|
+
config,
|
|
633
|
+
installDir,
|
|
634
|
+
state.manifest,
|
|
635
|
+
integrity,
|
|
636
|
+
spinner,
|
|
637
|
+
);
|
|
540
638
|
}
|
|
541
|
-
case
|
|
639
|
+
case "reinstall": {
|
|
542
640
|
// For reinstall, don't check for modifications - just overwrite
|
|
543
641
|
return await this.performReinstall(config, installDir, spinner);
|
|
544
642
|
}
|
|
545
|
-
case
|
|
643
|
+
case "expansions": {
|
|
546
644
|
// Ask which expansion packs to install
|
|
547
|
-
const availableExpansionPacks =
|
|
645
|
+
const availableExpansionPacks =
|
|
646
|
+
await resourceLocator.getExpansionPacks();
|
|
548
647
|
|
|
549
648
|
if (availableExpansionPacks.length === 0) {
|
|
550
|
-
console.log(chalk.yellow(
|
|
649
|
+
console.log(chalk.yellow("No expansion packs available."));
|
|
551
650
|
return;
|
|
552
651
|
}
|
|
553
652
|
|
|
554
653
|
const { selectedPacks } = await inquirer.prompt([
|
|
555
654
|
{
|
|
556
|
-
type:
|
|
557
|
-
name:
|
|
558
|
-
message:
|
|
655
|
+
type: "checkbox",
|
|
656
|
+
name: "selectedPacks",
|
|
657
|
+
message: "Select expansion packs to install/update:",
|
|
559
658
|
choices: availableExpansionPacks.map((pack) => ({
|
|
560
659
|
name: `${pack.name} (v${pack.version}) .${pack.id}`,
|
|
561
660
|
value: pack.id,
|
|
@@ -565,28 +664,28 @@ class Installer {
|
|
|
565
664
|
]);
|
|
566
665
|
|
|
567
666
|
if (selectedPacks.length === 0) {
|
|
568
|
-
console.log(chalk.yellow(
|
|
667
|
+
console.log(chalk.yellow("No expansion packs selected."));
|
|
569
668
|
return;
|
|
570
669
|
}
|
|
571
670
|
|
|
572
|
-
spinner.start(
|
|
671
|
+
spinner.start("Installing expansion packs...");
|
|
573
672
|
const expansionFiles = await this.installExpansionPacks(
|
|
574
673
|
installDir,
|
|
575
674
|
selectedPacks,
|
|
576
675
|
spinner,
|
|
577
676
|
{ ides: config.ides || [] },
|
|
578
677
|
);
|
|
579
|
-
spinner.succeed(
|
|
678
|
+
spinner.succeed("Expansion packs installed successfully!");
|
|
580
679
|
|
|
581
|
-
console.log(chalk.green(
|
|
680
|
+
console.log(chalk.green("\n✓ Installation complete!"));
|
|
582
681
|
console.log(chalk.green(`✓ Expansion packs installed/updated:`));
|
|
583
682
|
for (const packId of selectedPacks) {
|
|
584
683
|
console.log(chalk.green(` - ${packId} → .${packId}/`));
|
|
585
684
|
}
|
|
586
685
|
return;
|
|
587
686
|
}
|
|
588
|
-
case
|
|
589
|
-
console.log(
|
|
687
|
+
case "cancel": {
|
|
688
|
+
console.log("Installation cancelled.");
|
|
590
689
|
return;
|
|
591
690
|
}
|
|
592
691
|
}
|
|
@@ -595,37 +694,39 @@ class Installer {
|
|
|
595
694
|
async handleV3Installation(config, installDir, state, spinner) {
|
|
596
695
|
spinner.stop();
|
|
597
696
|
|
|
598
|
-
console.log(
|
|
697
|
+
console.log(
|
|
698
|
+
chalk.yellow("\n🔍 Found XiaoMa v3 installation (bmad-agent/ directory)"),
|
|
699
|
+
);
|
|
599
700
|
console.log(` Directory: ${installDir}`);
|
|
600
701
|
|
|
601
702
|
const { action } = await inquirer.prompt([
|
|
602
703
|
{
|
|
603
|
-
type:
|
|
604
|
-
name:
|
|
605
|
-
message:
|
|
704
|
+
type: "list",
|
|
705
|
+
name: "action",
|
|
706
|
+
message: "What would you like to do?",
|
|
606
707
|
choices: [
|
|
607
|
-
{ name:
|
|
608
|
-
{ name:
|
|
609
|
-
{ name:
|
|
708
|
+
{ name: "Upgrade from v3 to v4 (recommended)", value: "upgrade" },
|
|
709
|
+
{ name: "Install v4 alongside v3", value: "alongside" },
|
|
710
|
+
{ name: "Cancel", value: "cancel" },
|
|
610
711
|
],
|
|
611
712
|
},
|
|
612
713
|
]);
|
|
613
714
|
|
|
614
715
|
switch (action) {
|
|
615
|
-
case
|
|
616
|
-
console.log(chalk.cyan(
|
|
617
|
-
const V3ToV4Upgrader = require(
|
|
716
|
+
case "upgrade": {
|
|
717
|
+
console.log(chalk.cyan("\n📦 Starting v3 to v4 upgrade process..."));
|
|
718
|
+
const V3ToV4Upgrader = require("../../upgraders/v3-to-v4-upgrader");
|
|
618
719
|
const upgrader = new V3ToV4Upgrader();
|
|
619
720
|
return await upgrader.upgrade({
|
|
620
721
|
projectPath: installDir,
|
|
621
722
|
ides: config.ides || [], // Pass IDE selections from initial config
|
|
622
723
|
});
|
|
623
724
|
}
|
|
624
|
-
case
|
|
725
|
+
case "alongside": {
|
|
625
726
|
return await this.performFreshInstall(config, installDir, spinner);
|
|
626
727
|
}
|
|
627
|
-
case
|
|
628
|
-
console.log(
|
|
728
|
+
case "cancel": {
|
|
729
|
+
console.log("Installation cancelled.");
|
|
629
730
|
return;
|
|
630
731
|
}
|
|
631
732
|
}
|
|
@@ -634,54 +735,54 @@ class Installer {
|
|
|
634
735
|
async handleUnknownInstallation(config, installDir, state, spinner) {
|
|
635
736
|
spinner.stop();
|
|
636
737
|
|
|
637
|
-
console.log(chalk.yellow(
|
|
738
|
+
console.log(chalk.yellow("\n⚠️ Directory contains existing files"));
|
|
638
739
|
console.log(` Directory: ${installDir}`);
|
|
639
740
|
|
|
640
741
|
if (state.hasBmadCore) {
|
|
641
|
-
console.log(
|
|
742
|
+
console.log(" Found: .xiaoma-core directory (but no manifest)");
|
|
642
743
|
}
|
|
643
744
|
if (state.hasOtherFiles) {
|
|
644
|
-
console.log(
|
|
745
|
+
console.log(" Found: Other files in directory");
|
|
645
746
|
}
|
|
646
747
|
|
|
647
748
|
const { action } = await inquirer.prompt([
|
|
648
749
|
{
|
|
649
|
-
type:
|
|
650
|
-
name:
|
|
651
|
-
message:
|
|
750
|
+
type: "list",
|
|
751
|
+
name: "action",
|
|
752
|
+
message: "What would you like to do?",
|
|
652
753
|
choices: [
|
|
653
|
-
{ name:
|
|
654
|
-
{ name:
|
|
655
|
-
{ name:
|
|
754
|
+
{ name: "Install anyway (may overwrite files)", value: "force" },
|
|
755
|
+
{ name: "Choose different directory", value: "different" },
|
|
756
|
+
{ name: "Cancel", value: "cancel" },
|
|
656
757
|
],
|
|
657
758
|
},
|
|
658
759
|
]);
|
|
659
760
|
|
|
660
761
|
switch (action) {
|
|
661
|
-
case
|
|
762
|
+
case "force": {
|
|
662
763
|
return await this.performFreshInstall(config, installDir, spinner);
|
|
663
764
|
}
|
|
664
|
-
case
|
|
765
|
+
case "different": {
|
|
665
766
|
const { newDir } = await inquirer.prompt([
|
|
666
767
|
{
|
|
667
|
-
type:
|
|
668
|
-
name:
|
|
669
|
-
message:
|
|
670
|
-
default: path.join(path.dirname(installDir),
|
|
768
|
+
type: "input",
|
|
769
|
+
name: "newDir",
|
|
770
|
+
message: "Enter new installation directory:",
|
|
771
|
+
default: path.join(path.dirname(installDir), "bmad-project"),
|
|
671
772
|
},
|
|
672
773
|
]);
|
|
673
774
|
config.directory = newDir;
|
|
674
775
|
return await this.install(config);
|
|
675
776
|
}
|
|
676
|
-
case
|
|
677
|
-
console.log(
|
|
777
|
+
case "cancel": {
|
|
778
|
+
console.log("Installation cancelled.");
|
|
678
779
|
return;
|
|
679
780
|
}
|
|
680
781
|
}
|
|
681
782
|
}
|
|
682
783
|
|
|
683
784
|
async performUpdate(newConfig, installDir, manifest, spinner) {
|
|
684
|
-
spinner.start(
|
|
785
|
+
spinner.start("Checking for updates...");
|
|
685
786
|
|
|
686
787
|
try {
|
|
687
788
|
// Get current and new versions
|
|
@@ -692,47 +793,53 @@ class Installer {
|
|
|
692
793
|
// Only check for modified files if it's an actual version upgrade
|
|
693
794
|
let modifiedFiles = [];
|
|
694
795
|
if (versionCompare !== 0) {
|
|
695
|
-
spinner.text =
|
|
696
|
-
modifiedFiles = await fileManager.checkModifiedFiles(
|
|
796
|
+
spinner.text = "Checking for modified files...";
|
|
797
|
+
modifiedFiles = await fileManager.checkModifiedFiles(
|
|
798
|
+
installDir,
|
|
799
|
+
manifest,
|
|
800
|
+
);
|
|
697
801
|
}
|
|
698
802
|
|
|
699
803
|
if (modifiedFiles.length > 0) {
|
|
700
|
-
spinner.warn(
|
|
701
|
-
console.log(chalk.yellow(
|
|
804
|
+
spinner.warn("Found modified files");
|
|
805
|
+
console.log(chalk.yellow("\nThe following files have been modified:"));
|
|
702
806
|
for (const file of modifiedFiles) {
|
|
703
807
|
console.log(` - ${file}`);
|
|
704
808
|
}
|
|
705
809
|
|
|
706
810
|
const { action } = await inquirer.prompt([
|
|
707
811
|
{
|
|
708
|
-
type:
|
|
709
|
-
name:
|
|
710
|
-
message:
|
|
812
|
+
type: "list",
|
|
813
|
+
name: "action",
|
|
814
|
+
message: "How would you like to proceed?",
|
|
711
815
|
choices: [
|
|
712
|
-
{ name:
|
|
713
|
-
{ name:
|
|
714
|
-
{ name:
|
|
816
|
+
{ name: "Backup and overwrite modified files", value: "backup" },
|
|
817
|
+
{ name: "Skip modified files", value: "skip" },
|
|
818
|
+
{ name: "Cancel update", value: "cancel" },
|
|
715
819
|
],
|
|
716
820
|
},
|
|
717
821
|
]);
|
|
718
822
|
|
|
719
|
-
if (action ===
|
|
720
|
-
console.log(
|
|
823
|
+
if (action === "cancel") {
|
|
824
|
+
console.log("Update cancelled.");
|
|
721
825
|
return;
|
|
722
826
|
}
|
|
723
827
|
|
|
724
|
-
if (action ===
|
|
725
|
-
spinner.start(
|
|
828
|
+
if (action === "backup") {
|
|
829
|
+
spinner.start("Backing up modified files...");
|
|
726
830
|
for (const file of modifiedFiles) {
|
|
727
831
|
const filePath = path.join(installDir, file);
|
|
728
832
|
const backupPath = await fileManager.backupFile(filePath);
|
|
729
|
-
console.log(
|
|
833
|
+
console.log(
|
|
834
|
+
chalk.dim(` Backed up: ${file} → ${path.basename(backupPath)}`),
|
|
835
|
+
);
|
|
730
836
|
}
|
|
731
837
|
}
|
|
732
838
|
}
|
|
733
839
|
|
|
734
840
|
// Perform update by re-running installation
|
|
735
|
-
spinner.text =
|
|
841
|
+
spinner.text =
|
|
842
|
+
versionCompare === 0 ? "Reinstalling files..." : "Updating files...";
|
|
736
843
|
const config = {
|
|
737
844
|
installType: manifest.install_type,
|
|
738
845
|
agent: manifest.agent,
|
|
@@ -740,56 +847,62 @@ class Installer {
|
|
|
740
847
|
ides: newConfig?.ides || manifest.ides_setup || [],
|
|
741
848
|
};
|
|
742
849
|
|
|
743
|
-
await this.performFreshInstall(config, installDir, spinner, {
|
|
850
|
+
await this.performFreshInstall(config, installDir, spinner, {
|
|
851
|
+
isUpdate: true,
|
|
852
|
+
});
|
|
744
853
|
|
|
745
854
|
// Clean up .yml files that now have .yaml counterparts
|
|
746
|
-
spinner.text =
|
|
855
|
+
spinner.text = "Cleaning up legacy .yml files...";
|
|
747
856
|
await this.cleanupLegacyYmlFiles(installDir, spinner);
|
|
748
857
|
} catch (error) {
|
|
749
|
-
spinner.fail(
|
|
858
|
+
spinner.fail("Update failed");
|
|
750
859
|
throw error;
|
|
751
860
|
}
|
|
752
861
|
}
|
|
753
862
|
|
|
754
863
|
async performRepair(config, installDir, manifest, integrity, spinner) {
|
|
755
|
-
spinner.start(
|
|
864
|
+
spinner.start("Preparing to repair installation...");
|
|
756
865
|
|
|
757
866
|
try {
|
|
758
867
|
// Back up modified files
|
|
759
868
|
if (integrity.modified.length > 0) {
|
|
760
|
-
spinner.text =
|
|
869
|
+
spinner.text = "Backing up modified files...";
|
|
761
870
|
for (const file of integrity.modified) {
|
|
762
871
|
const filePath = path.join(installDir, file);
|
|
763
872
|
if (await fileManager.pathExists(filePath)) {
|
|
764
873
|
const backupPath = await fileManager.backupFile(filePath);
|
|
765
|
-
console.log(
|
|
874
|
+
console.log(
|
|
875
|
+
chalk.dim(` Backed up: ${file} → ${path.basename(backupPath)}`),
|
|
876
|
+
);
|
|
766
877
|
}
|
|
767
878
|
}
|
|
768
879
|
}
|
|
769
880
|
|
|
770
881
|
// Restore missing and modified files
|
|
771
|
-
spinner.text =
|
|
882
|
+
spinner.text = "Restoring files...";
|
|
772
883
|
const sourceBase = resourceLocator.getBmadCorePath();
|
|
773
884
|
const filesToRestore = [...integrity.missing, ...integrity.modified];
|
|
774
885
|
|
|
775
886
|
for (const file of filesToRestore) {
|
|
776
887
|
// Skip the manifest file itself
|
|
777
|
-
if (file.endsWith(
|
|
888
|
+
if (file.endsWith("install-manifest.yaml")) continue;
|
|
778
889
|
|
|
779
|
-
const relativePath = file.replace(
|
|
890
|
+
const relativePath = file.replace(".xiaoma-core/", "");
|
|
780
891
|
const destinationPath = path.join(installDir, file);
|
|
781
892
|
|
|
782
893
|
// Check if this is a common/ file that needs special processing
|
|
783
|
-
const commonBase = path.dirname(
|
|
784
|
-
|
|
894
|
+
const commonBase = path.dirname(
|
|
895
|
+
path.dirname(path.dirname(path.dirname(__filename))),
|
|
896
|
+
);
|
|
897
|
+
const commonSourcePath = path.join(commonBase, "common", relativePath);
|
|
785
898
|
|
|
786
899
|
if (await fileManager.pathExists(commonSourcePath)) {
|
|
787
900
|
// This is a common/ file - needs template processing
|
|
788
|
-
const fs = require(
|
|
789
|
-
const content = await fs.readFile(commonSourcePath,
|
|
790
|
-
const updatedContent = content.replaceAll(
|
|
901
|
+
const fs = require("node:fs").promises;
|
|
902
|
+
const content = await fs.readFile(commonSourcePath, "utf8");
|
|
903
|
+
const updatedContent = content.replaceAll("{root}", ".xiaoma-core");
|
|
791
904
|
await fileManager.ensureDirectory(path.dirname(destinationPath));
|
|
792
|
-
await fs.writeFile(destinationPath, updatedContent,
|
|
905
|
+
await fs.writeFile(destinationPath, updatedContent, "utf8");
|
|
793
906
|
spinner.text = `Restored: ${file}`;
|
|
794
907
|
} else {
|
|
795
908
|
// Regular file from xiaoma-core
|
|
@@ -799,95 +912,120 @@ class Installer {
|
|
|
799
912
|
spinner.text = `Restored: ${file}`;
|
|
800
913
|
|
|
801
914
|
// If this is a .yaml file, check for and remove corresponding .yml file
|
|
802
|
-
if (file.endsWith(
|
|
803
|
-
const ymlFile = file.replace(/\.yaml$/,
|
|
915
|
+
if (file.endsWith(".yaml")) {
|
|
916
|
+
const ymlFile = file.replace(/\.yaml$/, ".yml");
|
|
804
917
|
const ymlPath = path.join(installDir, ymlFile);
|
|
805
918
|
if (await fileManager.pathExists(ymlPath)) {
|
|
806
|
-
const fs = require(
|
|
919
|
+
const fs = require("node:fs").promises;
|
|
807
920
|
await fs.unlink(ymlPath);
|
|
808
|
-
console.log(
|
|
921
|
+
console.log(
|
|
922
|
+
chalk.dim(
|
|
923
|
+
` Removed legacy: ${ymlFile} (replaced by ${file})`,
|
|
924
|
+
),
|
|
925
|
+
);
|
|
809
926
|
}
|
|
810
927
|
}
|
|
811
928
|
} else {
|
|
812
|
-
console.warn(
|
|
929
|
+
console.warn(
|
|
930
|
+
chalk.yellow(` Warning: Source file not found: ${file}`),
|
|
931
|
+
);
|
|
813
932
|
}
|
|
814
933
|
}
|
|
815
934
|
}
|
|
816
935
|
|
|
817
936
|
// Clean up .yml files that now have .yaml counterparts
|
|
818
|
-
spinner.text =
|
|
937
|
+
spinner.text = "Cleaning up legacy .yml files...";
|
|
819
938
|
await this.cleanupLegacyYmlFiles(installDir, spinner);
|
|
820
939
|
|
|
821
|
-
spinner.succeed(
|
|
940
|
+
spinner.succeed("Repair completed successfully!");
|
|
822
941
|
|
|
823
942
|
// Show summary
|
|
824
|
-
console.log(chalk.green(
|
|
943
|
+
console.log(chalk.green("\n✓ Installation repaired!"));
|
|
825
944
|
if (integrity.missing.length > 0) {
|
|
826
|
-
console.log(
|
|
945
|
+
console.log(
|
|
946
|
+
chalk.green(` Restored ${integrity.missing.length} missing files`),
|
|
947
|
+
);
|
|
827
948
|
}
|
|
828
949
|
if (integrity.modified.length > 0) {
|
|
829
950
|
console.log(
|
|
830
|
-
chalk.green(
|
|
951
|
+
chalk.green(
|
|
952
|
+
` Restored ${integrity.modified.length} modified files (backups created)`,
|
|
953
|
+
),
|
|
831
954
|
);
|
|
832
955
|
}
|
|
833
956
|
|
|
834
957
|
// Warning for Cursor custom modes if agents were repaired
|
|
835
958
|
const ides = manifest.ides_setup || [];
|
|
836
|
-
if (ides.includes(
|
|
837
|
-
console.log(
|
|
959
|
+
if (ides.includes("cursor")) {
|
|
960
|
+
console.log(
|
|
961
|
+
chalk.yellow.bold(
|
|
962
|
+
"\n⚠️ IMPORTANT: Cursor Custom Modes Update Required",
|
|
963
|
+
),
|
|
964
|
+
);
|
|
838
965
|
console.log(
|
|
839
966
|
chalk.yellow(
|
|
840
|
-
|
|
967
|
+
"Since agent files have been repaired, you need to update any custom agent modes configured in the Cursor custom agent GUI per the Cursor docs.",
|
|
841
968
|
),
|
|
842
969
|
);
|
|
843
970
|
}
|
|
844
971
|
} catch (error) {
|
|
845
|
-
spinner.fail(
|
|
972
|
+
spinner.fail("Repair failed");
|
|
846
973
|
throw error;
|
|
847
974
|
}
|
|
848
975
|
}
|
|
849
976
|
|
|
850
977
|
async performReinstall(config, installDir, spinner) {
|
|
851
|
-
spinner.start(
|
|
978
|
+
spinner.start("Preparing to reinstall XiaoMa-Cli...");
|
|
852
979
|
|
|
853
980
|
// Remove existing .xiaoma-core
|
|
854
|
-
const bmadCorePath = path.join(installDir,
|
|
981
|
+
const bmadCorePath = path.join(installDir, ".xiaoma-core");
|
|
855
982
|
if (await fileManager.pathExists(bmadCorePath)) {
|
|
856
|
-
spinner.text =
|
|
983
|
+
spinner.text = "Removing existing installation...";
|
|
857
984
|
await fileManager.removeDirectory(bmadCorePath);
|
|
858
985
|
}
|
|
859
986
|
|
|
860
|
-
spinner.text =
|
|
861
|
-
const result = await this.performFreshInstall(config, installDir, spinner, {
|
|
987
|
+
spinner.text = "Installing fresh copy...";
|
|
988
|
+
const result = await this.performFreshInstall(config, installDir, spinner, {
|
|
989
|
+
isUpdate: true,
|
|
990
|
+
});
|
|
862
991
|
|
|
863
992
|
// Clean up .yml files that now have .yaml counterparts
|
|
864
|
-
spinner.text =
|
|
993
|
+
spinner.text = "Cleaning up legacy .yml files...";
|
|
865
994
|
await this.cleanupLegacyYmlFiles(installDir, spinner);
|
|
866
995
|
|
|
867
996
|
return result;
|
|
868
997
|
}
|
|
869
998
|
|
|
870
999
|
showSuccessMessage(config, installDir, options = {}) {
|
|
871
|
-
console.log(chalk.green(
|
|
1000
|
+
console.log(chalk.green("\n✓ XiaoMa-Cli installed successfully!\n"));
|
|
872
1001
|
|
|
873
1002
|
const ides = config.ides || (config.ide ? [config.ide] : []);
|
|
874
1003
|
if (ides.length > 0) {
|
|
875
1004
|
for (const ide of ides) {
|
|
876
1005
|
const ideConfig = configLoader.getIdeConfiguration(ide);
|
|
877
1006
|
if (ideConfig?.instructions) {
|
|
878
|
-
console.log(
|
|
1007
|
+
console.log(
|
|
1008
|
+
chalk.bold(`To use XiaoMa-Cli agents in ${ideConfig.name}:`),
|
|
1009
|
+
);
|
|
879
1010
|
console.log(ideConfig.instructions);
|
|
880
1011
|
}
|
|
881
1012
|
}
|
|
882
1013
|
} else {
|
|
883
|
-
console.log(chalk.yellow(
|
|
884
|
-
console.log(
|
|
1014
|
+
console.log(chalk.yellow("No IDE configuration was set up."));
|
|
1015
|
+
console.log(
|
|
1016
|
+
"You can manually configure your IDE using the agent files in:",
|
|
1017
|
+
installDir,
|
|
1018
|
+
);
|
|
885
1019
|
}
|
|
886
1020
|
|
|
887
1021
|
// Information about installation components
|
|
888
|
-
console.log(chalk.bold(
|
|
889
|
-
if (config.installType !==
|
|
890
|
-
console.log(
|
|
1022
|
+
console.log(chalk.bold("\n🎯 Installation Summary:"));
|
|
1023
|
+
if (config.installType !== "expansion-only") {
|
|
1024
|
+
console.log(
|
|
1025
|
+
chalk.green(
|
|
1026
|
+
"✓ .xiaoma-core framework installed with all agents and workflows",
|
|
1027
|
+
),
|
|
1028
|
+
);
|
|
891
1029
|
}
|
|
892
1030
|
|
|
893
1031
|
if (config.expansionPacks && config.expansionPacks.length > 0) {
|
|
@@ -900,12 +1038,15 @@ class Installer {
|
|
|
900
1038
|
if (config.includeWebBundles && config.webBundlesDirectory) {
|
|
901
1039
|
const bundleInfo = this.getWebBundleInfo(config);
|
|
902
1040
|
// Resolve the web bundles directory for display
|
|
903
|
-
const originalCwd =
|
|
1041
|
+
const originalCwd =
|
|
1042
|
+
process.env.INIT_CWD || process.env.PWD || process.cwd();
|
|
904
1043
|
const resolvedWebBundlesDir = path.isAbsolute(config.webBundlesDirectory)
|
|
905
1044
|
? config.webBundlesDirectory
|
|
906
1045
|
: path.resolve(originalCwd, config.webBundlesDirectory);
|
|
907
1046
|
console.log(
|
|
908
|
-
chalk.green(
|
|
1047
|
+
chalk.green(
|
|
1048
|
+
`✓ Web bundles (${bundleInfo}) installed to: ${resolvedWebBundlesDir}`,
|
|
1049
|
+
),
|
|
909
1050
|
);
|
|
910
1051
|
}
|
|
911
1052
|
|
|
@@ -915,30 +1056,48 @@ class Installer {
|
|
|
915
1056
|
const ideConfig = configLoader.getIdeConfiguration(ide);
|
|
916
1057
|
return ideConfig?.name || ide;
|
|
917
1058
|
})
|
|
918
|
-
.join(
|
|
919
|
-
console.log(
|
|
1059
|
+
.join(", ");
|
|
1060
|
+
console.log(
|
|
1061
|
+
chalk.green(`✓ IDE rules and configurations set up for: ${ideNames}`),
|
|
1062
|
+
);
|
|
920
1063
|
}
|
|
921
1064
|
|
|
922
1065
|
// Information about web bundles
|
|
923
1066
|
if (!config.includeWebBundles) {
|
|
924
|
-
console.log(chalk.bold(
|
|
925
|
-
console.log(
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
console.log(
|
|
1067
|
+
console.log(chalk.bold("\n📦 Web Bundles Available:"));
|
|
1068
|
+
console.log(
|
|
1069
|
+
"Pre-built web bundles are available and can be added later:",
|
|
1070
|
+
);
|
|
1071
|
+
console.log(
|
|
1072
|
+
chalk.cyan(" Run the installer again to add them to your project"),
|
|
1073
|
+
);
|
|
1074
|
+
console.log(
|
|
1075
|
+
"These bundles work independently and can be shared, moved, or used",
|
|
1076
|
+
);
|
|
1077
|
+
console.log("in other projects as standalone files.");
|
|
929
1078
|
}
|
|
930
1079
|
|
|
931
|
-
if (config.installType ===
|
|
932
|
-
console.log(
|
|
933
|
-
|
|
1080
|
+
if (config.installType === "single-agent") {
|
|
1081
|
+
console.log(
|
|
1082
|
+
chalk.dim(
|
|
1083
|
+
"\nNeed other agents? Run: npx xiaoma-cli install --agent=<name>",
|
|
1084
|
+
),
|
|
1085
|
+
);
|
|
1086
|
+
console.log(
|
|
1087
|
+
chalk.dim("Need everything? Run: npx xiaoma-cli install --full"),
|
|
1088
|
+
);
|
|
934
1089
|
}
|
|
935
1090
|
|
|
936
1091
|
// Warning for Cursor custom modes if agents were updated
|
|
937
|
-
if (options.isUpdate && ides.includes(
|
|
938
|
-
console.log(
|
|
1092
|
+
if (options.isUpdate && ides.includes("cursor")) {
|
|
1093
|
+
console.log(
|
|
1094
|
+
chalk.yellow.bold(
|
|
1095
|
+
"\n⚠️ IMPORTANT: Cursor Custom Modes Update Required",
|
|
1096
|
+
),
|
|
1097
|
+
);
|
|
939
1098
|
console.log(
|
|
940
1099
|
chalk.yellow(
|
|
941
|
-
|
|
1100
|
+
"Since agents have been updated, you need to update any custom agent modes configured in the Cursor custom agent GUI per the Cursor docs.",
|
|
942
1101
|
),
|
|
943
1102
|
);
|
|
944
1103
|
}
|
|
@@ -946,12 +1105,12 @@ class Installer {
|
|
|
946
1105
|
// Important notice to read the user guide
|
|
947
1106
|
console.log(
|
|
948
1107
|
chalk.red.bold(
|
|
949
|
-
|
|
1108
|
+
"\n📖 IMPORTANT: Please read the user guide at docs/user-guide.md (also installed at .xiaoma-core/user-guide.md)",
|
|
950
1109
|
),
|
|
951
1110
|
);
|
|
952
1111
|
console.log(
|
|
953
1112
|
chalk.red(
|
|
954
|
-
|
|
1113
|
+
"This guide contains essential information about the XiaoMa-Cli workflow and how to use the agents effectively.",
|
|
955
1114
|
),
|
|
956
1115
|
);
|
|
957
1116
|
}
|
|
@@ -966,68 +1125,81 @@ class Installer {
|
|
|
966
1125
|
const installDir = await this.findInstallation();
|
|
967
1126
|
if (installDir) {
|
|
968
1127
|
const config = {
|
|
969
|
-
installType:
|
|
1128
|
+
installType: "full",
|
|
970
1129
|
directory: path.dirname(installDir),
|
|
971
1130
|
ide: null,
|
|
972
1131
|
};
|
|
973
1132
|
return await this.install(config);
|
|
974
1133
|
}
|
|
975
|
-
console.log(chalk.red(
|
|
1134
|
+
console.log(chalk.red("No XiaoMa installation found."));
|
|
976
1135
|
}
|
|
977
1136
|
|
|
978
1137
|
async listAgents() {
|
|
979
1138
|
const agents = await resourceLocator.getAvailableAgents();
|
|
980
1139
|
|
|
981
|
-
console.log(chalk.bold(
|
|
1140
|
+
console.log(chalk.bold("\nAvailable XiaoMa Agents:\n"));
|
|
982
1141
|
|
|
983
1142
|
for (const agent of agents) {
|
|
984
1143
|
console.log(chalk.cyan(` ${agent.id.padEnd(20)}`), agent.description);
|
|
985
1144
|
}
|
|
986
1145
|
|
|
987
|
-
console.log(
|
|
1146
|
+
console.log(
|
|
1147
|
+
chalk.dim("\nInstall with: npx xiaoma-cli install --agent=<id>\n"),
|
|
1148
|
+
);
|
|
988
1149
|
}
|
|
989
1150
|
|
|
990
1151
|
async listExpansionPacks() {
|
|
991
1152
|
const expansionPacks = await resourceLocator.getExpansionPacks();
|
|
992
1153
|
|
|
993
|
-
console.log(chalk.bold(
|
|
1154
|
+
console.log(chalk.bold("\nAvailable XiaoMa Expansion Packs:\n"));
|
|
994
1155
|
|
|
995
1156
|
if (expansionPacks.length === 0) {
|
|
996
|
-
console.log(chalk.yellow(
|
|
1157
|
+
console.log(chalk.yellow("No expansion packs found."));
|
|
997
1158
|
return;
|
|
998
1159
|
}
|
|
999
1160
|
|
|
1000
1161
|
for (const pack of expansionPacks) {
|
|
1001
|
-
console.log(
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1162
|
+
console.log(
|
|
1163
|
+
chalk.cyan(` ${pack.id.padEnd(20)}`),
|
|
1164
|
+
`${pack.name} v${pack.version}`,
|
|
1165
|
+
);
|
|
1166
|
+
console.log(chalk.dim(` ${" ".repeat(22)}${pack.description}`));
|
|
1167
|
+
if (pack.author && pack.author !== "Unknown") {
|
|
1168
|
+
console.log(chalk.dim(` ${" ".repeat(22)}by ${pack.author}`));
|
|
1005
1169
|
}
|
|
1006
1170
|
console.log();
|
|
1007
1171
|
}
|
|
1008
1172
|
|
|
1009
|
-
console.log(
|
|
1173
|
+
console.log(
|
|
1174
|
+
chalk.dim(
|
|
1175
|
+
"Install with: npx xiaoma-cli install --full --expansion-packs <id>\n",
|
|
1176
|
+
),
|
|
1177
|
+
);
|
|
1010
1178
|
}
|
|
1011
1179
|
|
|
1012
1180
|
async showStatus() {
|
|
1013
1181
|
const installDir = await this.findInstallation();
|
|
1014
1182
|
|
|
1015
1183
|
if (!installDir) {
|
|
1016
|
-
console.log(
|
|
1184
|
+
console.log(
|
|
1185
|
+
chalk.yellow("No XiaoMa installation found in current directory tree"),
|
|
1186
|
+
);
|
|
1017
1187
|
return;
|
|
1018
1188
|
}
|
|
1019
1189
|
|
|
1020
1190
|
const manifest = await fileManager.readManifest(installDir);
|
|
1021
1191
|
|
|
1022
1192
|
if (!manifest) {
|
|
1023
|
-
console.log(chalk.red(
|
|
1193
|
+
console.log(chalk.red("Invalid installation - manifest not found"));
|
|
1024
1194
|
return;
|
|
1025
1195
|
}
|
|
1026
1196
|
|
|
1027
|
-
console.log(chalk.bold(
|
|
1197
|
+
console.log(chalk.bold("\nXiaoMa-Cli Installation Status:\n"));
|
|
1028
1198
|
console.log(` Directory: ${installDir}`);
|
|
1029
1199
|
console.log(` Version: ${manifest.version}`);
|
|
1030
|
-
console.log(
|
|
1200
|
+
console.log(
|
|
1201
|
+
` Installed: ${new Date(manifest.installed_at).toLocaleDateString()}`,
|
|
1202
|
+
);
|
|
1031
1203
|
console.log(` Type: ${manifest.install_type}`);
|
|
1032
1204
|
|
|
1033
1205
|
if (manifest.agent) {
|
|
@@ -1035,18 +1207,21 @@ class Installer {
|
|
|
1035
1207
|
}
|
|
1036
1208
|
|
|
1037
1209
|
if (manifest.ides_setup && manifest.ides_setup.length > 0) {
|
|
1038
|
-
console.log(` IDE Setup: ${manifest.ides_setup.join(
|
|
1210
|
+
console.log(` IDE Setup: ${manifest.ides_setup.join(", ")}`);
|
|
1039
1211
|
}
|
|
1040
1212
|
|
|
1041
1213
|
console.log(` Total Files: ${manifest.files.length}`);
|
|
1042
1214
|
|
|
1043
1215
|
// Check for modifications
|
|
1044
|
-
const modifiedFiles = await fileManager.checkModifiedFiles(
|
|
1216
|
+
const modifiedFiles = await fileManager.checkModifiedFiles(
|
|
1217
|
+
installDir,
|
|
1218
|
+
manifest,
|
|
1219
|
+
);
|
|
1045
1220
|
if (modifiedFiles.length > 0) {
|
|
1046
1221
|
console.log(chalk.yellow(` Modified Files: ${modifiedFiles.length}`));
|
|
1047
1222
|
}
|
|
1048
1223
|
|
|
1049
|
-
console.log(
|
|
1224
|
+
console.log("");
|
|
1050
1225
|
}
|
|
1051
1226
|
|
|
1052
1227
|
async getAvailableAgents() {
|
|
@@ -1082,74 +1257,105 @@ class Installer {
|
|
|
1082
1257
|
|
|
1083
1258
|
// Check if expansion pack already exists
|
|
1084
1259
|
let expansionDotFolder = path.join(installDir, `.${packId}`);
|
|
1085
|
-
const existingManifestPath = path.join(
|
|
1260
|
+
const existingManifestPath = path.join(
|
|
1261
|
+
expansionDotFolder,
|
|
1262
|
+
"install-manifest.yaml",
|
|
1263
|
+
);
|
|
1086
1264
|
|
|
1087
1265
|
if (await fileManager.pathExists(existingManifestPath)) {
|
|
1088
1266
|
spinner.stop();
|
|
1089
|
-
const existingManifest = await fileManager.readExpansionPackManifest(
|
|
1267
|
+
const existingManifest = await fileManager.readExpansionPackManifest(
|
|
1268
|
+
installDir,
|
|
1269
|
+
packId,
|
|
1270
|
+
);
|
|
1090
1271
|
|
|
1091
|
-
console.log(
|
|
1092
|
-
|
|
1272
|
+
console.log(
|
|
1273
|
+
chalk.yellow(`\n🔍 Found existing ${pack.name} installation`),
|
|
1274
|
+
);
|
|
1275
|
+
console.log(
|
|
1276
|
+
` Current version: ${existingManifest.version || "unknown"}`,
|
|
1277
|
+
);
|
|
1093
1278
|
console.log(` New version: ${pack.version}`);
|
|
1094
1279
|
|
|
1095
1280
|
// Check integrity of existing expansion pack
|
|
1096
|
-
const packIntegrity = await fileManager.checkFileIntegrity(
|
|
1281
|
+
const packIntegrity = await fileManager.checkFileIntegrity(
|
|
1282
|
+
installDir,
|
|
1283
|
+
existingManifest,
|
|
1284
|
+
);
|
|
1097
1285
|
const hasPackIntegrityIssues =
|
|
1098
|
-
packIntegrity.missing.length > 0 ||
|
|
1286
|
+
packIntegrity.missing.length > 0 ||
|
|
1287
|
+
packIntegrity.modified.length > 0;
|
|
1099
1288
|
|
|
1100
1289
|
if (hasPackIntegrityIssues) {
|
|
1101
|
-
console.log(chalk.red(
|
|
1290
|
+
console.log(chalk.red(" ⚠️ Installation issues detected:"));
|
|
1102
1291
|
if (packIntegrity.missing.length > 0) {
|
|
1103
|
-
console.log(
|
|
1292
|
+
console.log(
|
|
1293
|
+
chalk.red(
|
|
1294
|
+
` Missing files: ${packIntegrity.missing.length}`,
|
|
1295
|
+
),
|
|
1296
|
+
);
|
|
1104
1297
|
}
|
|
1105
1298
|
if (packIntegrity.modified.length > 0) {
|
|
1106
|
-
console.log(
|
|
1299
|
+
console.log(
|
|
1300
|
+
chalk.yellow(
|
|
1301
|
+
` Modified files: ${packIntegrity.modified.length}`,
|
|
1302
|
+
),
|
|
1303
|
+
);
|
|
1107
1304
|
}
|
|
1108
1305
|
}
|
|
1109
1306
|
|
|
1110
1307
|
const versionCompare = this.compareVersions(
|
|
1111
|
-
existingManifest.version ||
|
|
1308
|
+
existingManifest.version || "0.0.0",
|
|
1112
1309
|
pack.version,
|
|
1113
1310
|
);
|
|
1114
1311
|
|
|
1115
1312
|
if (versionCompare === 0) {
|
|
1116
|
-
console.log(chalk.yellow(
|
|
1313
|
+
console.log(chalk.yellow(" ⚠️ Same version already installed"));
|
|
1117
1314
|
|
|
1118
1315
|
const choices = [];
|
|
1119
1316
|
if (hasPackIntegrityIssues) {
|
|
1120
|
-
choices.push({
|
|
1317
|
+
choices.push({
|
|
1318
|
+
name: "Repair (restore missing/modified files)",
|
|
1319
|
+
value: "repair",
|
|
1320
|
+
});
|
|
1121
1321
|
}
|
|
1122
1322
|
choices.push(
|
|
1123
|
-
{ name:
|
|
1124
|
-
{ name:
|
|
1125
|
-
{ name:
|
|
1323
|
+
{ name: "Force reinstall (overwrite)", value: "overwrite" },
|
|
1324
|
+
{ name: "Skip this expansion pack", value: "skip" },
|
|
1325
|
+
{ name: "Cancel installation", value: "cancel" },
|
|
1126
1326
|
);
|
|
1127
1327
|
|
|
1128
1328
|
const { action } = await inquirer.prompt([
|
|
1129
1329
|
{
|
|
1130
|
-
type:
|
|
1131
|
-
name:
|
|
1330
|
+
type: "list",
|
|
1331
|
+
name: "action",
|
|
1132
1332
|
message: `${pack.name} v${pack.version} is already installed. What would you like to do?`,
|
|
1133
1333
|
choices: choices,
|
|
1134
1334
|
},
|
|
1135
1335
|
]);
|
|
1136
1336
|
|
|
1137
1337
|
switch (action) {
|
|
1138
|
-
case
|
|
1338
|
+
case "skip": {
|
|
1139
1339
|
spinner.start();
|
|
1140
1340
|
continue;
|
|
1141
1341
|
|
|
1142
1342
|
break;
|
|
1143
1343
|
}
|
|
1144
|
-
case
|
|
1145
|
-
console.log(
|
|
1344
|
+
case "cancel": {
|
|
1345
|
+
console.log("Installation cancelled.");
|
|
1146
1346
|
process.exit(0);
|
|
1147
1347
|
|
|
1148
1348
|
break;
|
|
1149
1349
|
}
|
|
1150
|
-
case
|
|
1350
|
+
case "repair": {
|
|
1151
1351
|
// Repair the expansion pack
|
|
1152
|
-
await this.repairExpansionPack(
|
|
1352
|
+
await this.repairExpansionPack(
|
|
1353
|
+
installDir,
|
|
1354
|
+
packId,
|
|
1355
|
+
pack,
|
|
1356
|
+
packIntegrity,
|
|
1357
|
+
spinner,
|
|
1358
|
+
);
|
|
1153
1359
|
continue;
|
|
1154
1360
|
|
|
1155
1361
|
break;
|
|
@@ -1157,12 +1363,12 @@ class Installer {
|
|
|
1157
1363
|
// No default
|
|
1158
1364
|
}
|
|
1159
1365
|
} else if (versionCompare < 0) {
|
|
1160
|
-
console.log(chalk.cyan(
|
|
1366
|
+
console.log(chalk.cyan(" ⬆️ Upgrade available"));
|
|
1161
1367
|
|
|
1162
1368
|
const { proceed } = await inquirer.prompt([
|
|
1163
1369
|
{
|
|
1164
|
-
type:
|
|
1165
|
-
name:
|
|
1370
|
+
type: "confirm",
|
|
1371
|
+
name: "proceed",
|
|
1166
1372
|
message: `Upgrade ${pack.name} from v${existingManifest.version} to v${pack.version}?`,
|
|
1167
1373
|
default: true,
|
|
1168
1374
|
},
|
|
@@ -1173,26 +1379,33 @@ class Installer {
|
|
|
1173
1379
|
continue;
|
|
1174
1380
|
}
|
|
1175
1381
|
} else {
|
|
1176
|
-
console.log(
|
|
1382
|
+
console.log(
|
|
1383
|
+
chalk.yellow(
|
|
1384
|
+
" ⬇️ Installed version is newer than available version",
|
|
1385
|
+
),
|
|
1386
|
+
);
|
|
1177
1387
|
|
|
1178
1388
|
const { action } = await inquirer.prompt([
|
|
1179
1389
|
{
|
|
1180
|
-
type:
|
|
1181
|
-
name:
|
|
1182
|
-
message:
|
|
1390
|
+
type: "list",
|
|
1391
|
+
name: "action",
|
|
1392
|
+
message: "What would you like to do?",
|
|
1183
1393
|
choices: [
|
|
1184
|
-
{ name:
|
|
1185
|
-
{
|
|
1186
|
-
|
|
1394
|
+
{ name: "Keep current version", value: "skip" },
|
|
1395
|
+
{
|
|
1396
|
+
name: "Downgrade to available version",
|
|
1397
|
+
value: "downgrade",
|
|
1398
|
+
},
|
|
1399
|
+
{ name: "Cancel installation", value: "cancel" },
|
|
1187
1400
|
],
|
|
1188
1401
|
},
|
|
1189
1402
|
]);
|
|
1190
1403
|
|
|
1191
|
-
if (action ===
|
|
1404
|
+
if (action === "skip") {
|
|
1192
1405
|
spinner.start();
|
|
1193
1406
|
continue;
|
|
1194
|
-
} else if (action ===
|
|
1195
|
-
console.log(
|
|
1407
|
+
} else if (action === "cancel") {
|
|
1408
|
+
console.log("Installation cancelled.");
|
|
1196
1409
|
process.exit(0);
|
|
1197
1410
|
}
|
|
1198
1411
|
}
|
|
@@ -1210,15 +1423,15 @@ class Installer {
|
|
|
1210
1423
|
|
|
1211
1424
|
// Define the folders to copy from expansion packs
|
|
1212
1425
|
const foldersToSync = [
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1426
|
+
"agents",
|
|
1427
|
+
"agent-teams",
|
|
1428
|
+
"templates",
|
|
1429
|
+
"tasks",
|
|
1430
|
+
"checklists",
|
|
1431
|
+
"workflows",
|
|
1432
|
+
"data",
|
|
1433
|
+
"utils",
|
|
1434
|
+
"schemas",
|
|
1222
1435
|
];
|
|
1223
1436
|
|
|
1224
1437
|
// Copy each folder if it exists
|
|
@@ -1228,7 +1441,7 @@ class Installer {
|
|
|
1228
1441
|
// Check if folder exists in expansion pack
|
|
1229
1442
|
if (await fileManager.pathExists(sourceFolder)) {
|
|
1230
1443
|
// Get all files in this folder
|
|
1231
|
-
const files = await resourceLocator.findFiles(
|
|
1444
|
+
const files = await resourceLocator.findFiles("**/*", {
|
|
1232
1445
|
cwd: sourceFolder,
|
|
1233
1446
|
nodir: true,
|
|
1234
1447
|
});
|
|
@@ -1236,14 +1449,24 @@ class Installer {
|
|
|
1236
1449
|
// Copy each file to the expansion pack's dot folder with {root} replacement
|
|
1237
1450
|
for (const file of files) {
|
|
1238
1451
|
const sourcePath = path.join(sourceFolder, file);
|
|
1239
|
-
const destinationPath = path.join(
|
|
1452
|
+
const destinationPath = path.join(
|
|
1453
|
+
expansionDotFolder,
|
|
1454
|
+
folder,
|
|
1455
|
+
file,
|
|
1456
|
+
);
|
|
1240
1457
|
|
|
1241
1458
|
const needsRootReplacement =
|
|
1242
|
-
file.endsWith(
|
|
1459
|
+
file.endsWith(".md") ||
|
|
1460
|
+
file.endsWith(".yaml") ||
|
|
1461
|
+
file.endsWith(".yml");
|
|
1243
1462
|
let success = false;
|
|
1244
1463
|
|
|
1245
1464
|
success = await (needsRootReplacement
|
|
1246
|
-
? fileManager.copyFileWithRootReplacement(
|
|
1465
|
+
? fileManager.copyFileWithRootReplacement(
|
|
1466
|
+
sourcePath,
|
|
1467
|
+
destinationPath,
|
|
1468
|
+
`.${packId}`,
|
|
1469
|
+
)
|
|
1247
1470
|
: fileManager.copyFile(sourcePath, destinationPath));
|
|
1248
1471
|
|
|
1249
1472
|
if (success) {
|
|
@@ -1254,9 +1477,12 @@ class Installer {
|
|
|
1254
1477
|
}
|
|
1255
1478
|
|
|
1256
1479
|
// Copy config.yaml with {root} replacement
|
|
1257
|
-
const configPath = path.join(expansionPackDir,
|
|
1480
|
+
const configPath = path.join(expansionPackDir, "config.yaml");
|
|
1258
1481
|
if (await fileManager.pathExists(configPath)) {
|
|
1259
|
-
const configDestinationPath = path.join(
|
|
1482
|
+
const configDestinationPath = path.join(
|
|
1483
|
+
expansionDotFolder,
|
|
1484
|
+
"config.yaml",
|
|
1485
|
+
);
|
|
1260
1486
|
if (
|
|
1261
1487
|
await fileManager.copyFileWithRootReplacement(
|
|
1262
1488
|
configPath,
|
|
@@ -1264,14 +1490,17 @@ class Installer {
|
|
|
1264
1490
|
`.${packId}`,
|
|
1265
1491
|
)
|
|
1266
1492
|
) {
|
|
1267
|
-
installedFiles.push(path.join(`.${packId}`,
|
|
1493
|
+
installedFiles.push(path.join(`.${packId}`, "config.yaml"));
|
|
1268
1494
|
}
|
|
1269
1495
|
}
|
|
1270
1496
|
|
|
1271
1497
|
// Copy README if it exists with {root} replacement
|
|
1272
|
-
const readmePath = path.join(expansionPackDir,
|
|
1498
|
+
const readmePath = path.join(expansionPackDir, "README.md");
|
|
1273
1499
|
if (await fileManager.pathExists(readmePath)) {
|
|
1274
|
-
const readmeDestinationPath = path.join(
|
|
1500
|
+
const readmeDestinationPath = path.join(
|
|
1501
|
+
expansionDotFolder,
|
|
1502
|
+
"README.md",
|
|
1503
|
+
);
|
|
1275
1504
|
if (
|
|
1276
1505
|
await fileManager.copyFileWithRootReplacement(
|
|
1277
1506
|
readmePath,
|
|
@@ -1279,7 +1508,7 @@ class Installer {
|
|
|
1279
1508
|
`.${packId}`,
|
|
1280
1509
|
)
|
|
1281
1510
|
) {
|
|
1282
|
-
installedFiles.push(path.join(`.${packId}`,
|
|
1511
|
+
installedFiles.push(path.join(`.${packId}`, "README.md"));
|
|
1283
1512
|
}
|
|
1284
1513
|
}
|
|
1285
1514
|
|
|
@@ -1297,12 +1526,17 @@ class Installer {
|
|
|
1297
1526
|
);
|
|
1298
1527
|
|
|
1299
1528
|
// Check and resolve core agents referenced by teams
|
|
1300
|
-
await this.resolveExpansionPackCoreAgents(
|
|
1529
|
+
await this.resolveExpansionPackCoreAgents(
|
|
1530
|
+
installDir,
|
|
1531
|
+
expansionDotFolder,
|
|
1532
|
+
packId,
|
|
1533
|
+
spinner,
|
|
1534
|
+
);
|
|
1301
1535
|
|
|
1302
1536
|
// Create manifest for this expansion pack
|
|
1303
1537
|
spinner.text = `Creating manifest for ${packId}...`;
|
|
1304
1538
|
const expansionConfig = {
|
|
1305
|
-
installType:
|
|
1539
|
+
installType: "expansion-pack",
|
|
1306
1540
|
expansionPackId: packId,
|
|
1307
1541
|
expansionPackName: pack.name,
|
|
1308
1542
|
expansionPackVersion: pack.version,
|
|
@@ -1310,11 +1544,13 @@ class Installer {
|
|
|
1310
1544
|
};
|
|
1311
1545
|
|
|
1312
1546
|
// Get all files installed in this expansion pack
|
|
1313
|
-
const foundFiles = await resourceLocator.findFiles(
|
|
1547
|
+
const foundFiles = await resourceLocator.findFiles("**/*", {
|
|
1314
1548
|
cwd: expansionDotFolder,
|
|
1315
1549
|
nodir: true,
|
|
1316
1550
|
});
|
|
1317
|
-
const expansionPackFiles = foundFiles.map((f) =>
|
|
1551
|
+
const expansionPackFiles = foundFiles.map((f) =>
|
|
1552
|
+
path.join(`.${packId}`, f),
|
|
1553
|
+
);
|
|
1318
1554
|
|
|
1319
1555
|
await fileManager.createExpansionPackManifest(
|
|
1320
1556
|
installDir,
|
|
@@ -1323,9 +1559,15 @@ class Installer {
|
|
|
1323
1559
|
expansionPackFiles,
|
|
1324
1560
|
);
|
|
1325
1561
|
|
|
1326
|
-
console.log(
|
|
1562
|
+
console.log(
|
|
1563
|
+
chalk.green(
|
|
1564
|
+
`✓ Installed expansion pack: ${pack.name} to ${`.${packId}`}`,
|
|
1565
|
+
),
|
|
1566
|
+
);
|
|
1327
1567
|
} catch (error) {
|
|
1328
|
-
console.error(
|
|
1568
|
+
console.error(
|
|
1569
|
+
`Failed to install expansion pack ${packId}: ${error.message}`,
|
|
1570
|
+
);
|
|
1329
1571
|
console.error(`Stack trace: ${error.stack}`);
|
|
1330
1572
|
}
|
|
1331
1573
|
}
|
|
@@ -1340,17 +1582,17 @@ class Installer {
|
|
|
1340
1582
|
pack,
|
|
1341
1583
|
spinner,
|
|
1342
1584
|
) {
|
|
1343
|
-
const yaml = require(
|
|
1344
|
-
const fs = require(
|
|
1585
|
+
const yaml = require("js-yaml");
|
|
1586
|
+
const fs = require("node:fs").promises;
|
|
1345
1587
|
|
|
1346
1588
|
// Find all agent files in the expansion pack
|
|
1347
|
-
const agentFiles = await resourceLocator.findFiles(
|
|
1589
|
+
const agentFiles = await resourceLocator.findFiles("agents/*.md", {
|
|
1348
1590
|
cwd: expansionDotFolder,
|
|
1349
1591
|
});
|
|
1350
1592
|
|
|
1351
1593
|
for (const agentFile of agentFiles) {
|
|
1352
1594
|
const agentPath = path.join(expansionDotFolder, agentFile);
|
|
1353
|
-
const agentContent = await fs.readFile(agentPath,
|
|
1595
|
+
const agentContent = await fs.readFile(agentPath, "utf8");
|
|
1354
1596
|
|
|
1355
1597
|
// Extract YAML frontmatter to check dependencies
|
|
1356
1598
|
const yamlContent = extractYamlFromAgent(agentContent);
|
|
@@ -1361,39 +1603,55 @@ class Installer {
|
|
|
1361
1603
|
|
|
1362
1604
|
// Check for core dependencies (those that don't exist in the expansion pack)
|
|
1363
1605
|
for (const depType of [
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1606
|
+
"tasks",
|
|
1607
|
+
"templates",
|
|
1608
|
+
"checklists",
|
|
1609
|
+
"workflows",
|
|
1610
|
+
"utils",
|
|
1611
|
+
"data",
|
|
1370
1612
|
]) {
|
|
1371
1613
|
const deps = dependencies[depType] || [];
|
|
1372
1614
|
|
|
1373
1615
|
for (const dep of deps) {
|
|
1374
1616
|
const depFileName =
|
|
1375
|
-
dep.endsWith(
|
|
1617
|
+
dep.endsWith(".md") || dep.endsWith(".yaml")
|
|
1376
1618
|
? dep
|
|
1377
|
-
: depType ===
|
|
1619
|
+
: depType === "templates"
|
|
1378
1620
|
? `${dep}.yaml`
|
|
1379
1621
|
: `${dep}.md`;
|
|
1380
|
-
const expansionDepPath = path.join(
|
|
1622
|
+
const expansionDepPath = path.join(
|
|
1623
|
+
expansionDotFolder,
|
|
1624
|
+
depType,
|
|
1625
|
+
depFileName,
|
|
1626
|
+
);
|
|
1381
1627
|
|
|
1382
1628
|
// Check if dependency exists in expansion pack dot folder
|
|
1383
1629
|
if (!(await fileManager.pathExists(expansionDepPath))) {
|
|
1384
1630
|
// Try to find it in expansion pack source
|
|
1385
|
-
const sourceDepPath = path.join(
|
|
1631
|
+
const sourceDepPath = path.join(
|
|
1632
|
+
pack.path,
|
|
1633
|
+
depType,
|
|
1634
|
+
depFileName,
|
|
1635
|
+
);
|
|
1386
1636
|
|
|
1387
1637
|
if (await fileManager.pathExists(sourceDepPath)) {
|
|
1388
1638
|
// Copy from expansion pack source
|
|
1389
1639
|
spinner.text = `Copying ${packId} dependency ${dep}...`;
|
|
1390
|
-
const destinationPath = path.join(
|
|
1640
|
+
const destinationPath = path.join(
|
|
1641
|
+
expansionDotFolder,
|
|
1642
|
+
depType,
|
|
1643
|
+
depFileName,
|
|
1644
|
+
);
|
|
1391
1645
|
await fileManager.copyFileWithRootReplacement(
|
|
1392
1646
|
sourceDepPath,
|
|
1393
1647
|
destinationPath,
|
|
1394
1648
|
`.${packId}`,
|
|
1395
1649
|
);
|
|
1396
|
-
console.log(
|
|
1650
|
+
console.log(
|
|
1651
|
+
chalk.dim(
|
|
1652
|
+
` Added ${packId} dependency: ${depType}/${depFileName}`,
|
|
1653
|
+
),
|
|
1654
|
+
);
|
|
1397
1655
|
} else {
|
|
1398
1656
|
// Try to find it in core
|
|
1399
1657
|
const coreDepPath = path.join(
|
|
@@ -1406,14 +1664,22 @@ class Installer {
|
|
|
1406
1664
|
spinner.text = `Copying core dependency ${dep} for ${packId}...`;
|
|
1407
1665
|
|
|
1408
1666
|
// Copy from core to expansion pack dot folder with {root} replacement
|
|
1409
|
-
const destinationPath = path.join(
|
|
1667
|
+
const destinationPath = path.join(
|
|
1668
|
+
expansionDotFolder,
|
|
1669
|
+
depType,
|
|
1670
|
+
depFileName,
|
|
1671
|
+
);
|
|
1410
1672
|
await fileManager.copyFileWithRootReplacement(
|
|
1411
1673
|
coreDepPath,
|
|
1412
1674
|
destinationPath,
|
|
1413
1675
|
`.${packId}`,
|
|
1414
1676
|
);
|
|
1415
1677
|
|
|
1416
|
-
console.log(
|
|
1678
|
+
console.log(
|
|
1679
|
+
chalk.dim(
|
|
1680
|
+
` Added core dependency: ${depType}/${depFileName}`,
|
|
1681
|
+
),
|
|
1682
|
+
);
|
|
1417
1683
|
} else {
|
|
1418
1684
|
console.warn(
|
|
1419
1685
|
chalk.yellow(
|
|
@@ -1426,43 +1692,50 @@ class Installer {
|
|
|
1426
1692
|
}
|
|
1427
1693
|
}
|
|
1428
1694
|
} catch (error) {
|
|
1429
|
-
console.warn(
|
|
1695
|
+
console.warn(
|
|
1696
|
+
` Warning: Could not parse agent dependencies: ${error.message}`,
|
|
1697
|
+
);
|
|
1430
1698
|
}
|
|
1431
1699
|
}
|
|
1432
1700
|
}
|
|
1433
1701
|
}
|
|
1434
1702
|
|
|
1435
|
-
async resolveExpansionPackCoreAgents(
|
|
1436
|
-
|
|
1437
|
-
|
|
1703
|
+
async resolveExpansionPackCoreAgents(
|
|
1704
|
+
installDir,
|
|
1705
|
+
expansionDotFolder,
|
|
1706
|
+
packId,
|
|
1707
|
+
spinner,
|
|
1708
|
+
) {
|
|
1709
|
+
const yaml = require("js-yaml");
|
|
1710
|
+
const fs = require("node:fs").promises;
|
|
1438
1711
|
|
|
1439
1712
|
// Find all team files in the expansion pack
|
|
1440
|
-
const teamFiles = await resourceLocator.findFiles(
|
|
1713
|
+
const teamFiles = await resourceLocator.findFiles("agent-teams/*.yaml", {
|
|
1441
1714
|
cwd: expansionDotFolder,
|
|
1442
1715
|
});
|
|
1443
1716
|
|
|
1444
1717
|
// Also get existing agents in the expansion pack
|
|
1445
1718
|
const existingAgents = new Set();
|
|
1446
|
-
const agentFiles = await resourceLocator.findFiles(
|
|
1719
|
+
const agentFiles = await resourceLocator.findFiles("agents/*.md", {
|
|
1447
1720
|
cwd: expansionDotFolder,
|
|
1448
1721
|
});
|
|
1449
1722
|
for (const agentFile of agentFiles) {
|
|
1450
|
-
const agentName = path.basename(agentFile,
|
|
1723
|
+
const agentName = path.basename(agentFile, ".md");
|
|
1451
1724
|
existingAgents.add(agentName);
|
|
1452
1725
|
}
|
|
1453
1726
|
|
|
1454
1727
|
// Process each team file
|
|
1455
1728
|
for (const teamFile of teamFiles) {
|
|
1456
1729
|
const teamPath = path.join(expansionDotFolder, teamFile);
|
|
1457
|
-
const teamContent = await fs.readFile(teamPath,
|
|
1730
|
+
const teamContent = await fs.readFile(teamPath, "utf8");
|
|
1458
1731
|
|
|
1459
1732
|
try {
|
|
1460
1733
|
const teamConfig = yaml.load(teamContent);
|
|
1461
1734
|
const agents = teamConfig.agents || [];
|
|
1462
1735
|
|
|
1463
1736
|
// Add bmad-orchestrator if not present (required for all teams)
|
|
1464
|
-
if (!agents.includes(
|
|
1465
|
-
agents.unshift(
|
|
1737
|
+
if (!agents.includes("bmad-orchestrator")) {
|
|
1738
|
+
agents.unshift("bmad-orchestrator");
|
|
1466
1739
|
}
|
|
1467
1740
|
|
|
1468
1741
|
// Check each agent in the team
|
|
@@ -1471,7 +1744,7 @@ class Installer {
|
|
|
1471
1744
|
// Agent not in expansion pack, try to get from core
|
|
1472
1745
|
const coreAgentPath = path.join(
|
|
1473
1746
|
resourceLocator.getBmadCorePath(),
|
|
1474
|
-
|
|
1747
|
+
"agents",
|
|
1475
1748
|
`${agentId}.md`,
|
|
1476
1749
|
);
|
|
1477
1750
|
|
|
@@ -1479,7 +1752,11 @@ class Installer {
|
|
|
1479
1752
|
spinner.text = `Copying core agent ${agentId} for ${packId}...`;
|
|
1480
1753
|
|
|
1481
1754
|
// Copy agent file with {root} replacement
|
|
1482
|
-
const destinationPath = path.join(
|
|
1755
|
+
const destinationPath = path.join(
|
|
1756
|
+
expansionDotFolder,
|
|
1757
|
+
"agents",
|
|
1758
|
+
`${agentId}.md`,
|
|
1759
|
+
);
|
|
1483
1760
|
await fileManager.copyFileWithRootReplacement(
|
|
1484
1761
|
coreAgentPath,
|
|
1485
1762
|
destinationPath,
|
|
@@ -1490,7 +1767,7 @@ class Installer {
|
|
|
1490
1767
|
console.log(chalk.dim(` Added core agent: ${agentId}`));
|
|
1491
1768
|
|
|
1492
1769
|
// Now resolve this agent's dependencies too
|
|
1493
|
-
const agentContent = await fs.readFile(coreAgentPath,
|
|
1770
|
+
const agentContent = await fs.readFile(coreAgentPath, "utf8");
|
|
1494
1771
|
const yamlContent = extractYamlFromAgent(agentContent, true);
|
|
1495
1772
|
|
|
1496
1773
|
if (yamlContent) {
|
|
@@ -1500,23 +1777,27 @@ class Installer {
|
|
|
1500
1777
|
|
|
1501
1778
|
// Copy all dependencies for this agent
|
|
1502
1779
|
for (const depType of [
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1780
|
+
"tasks",
|
|
1781
|
+
"templates",
|
|
1782
|
+
"checklists",
|
|
1783
|
+
"workflows",
|
|
1784
|
+
"utils",
|
|
1785
|
+
"data",
|
|
1509
1786
|
]) {
|
|
1510
1787
|
const deps = dependencies[depType] || [];
|
|
1511
1788
|
|
|
1512
1789
|
for (const dep of deps) {
|
|
1513
1790
|
const depFileName =
|
|
1514
|
-
dep.endsWith(
|
|
1791
|
+
dep.endsWith(".md") || dep.endsWith(".yaml")
|
|
1515
1792
|
? dep
|
|
1516
|
-
: depType ===
|
|
1793
|
+
: depType === "templates"
|
|
1517
1794
|
? `${dep}.yaml`
|
|
1518
1795
|
: `${dep}.md`;
|
|
1519
|
-
const expansionDepPath = path.join(
|
|
1796
|
+
const expansionDepPath = path.join(
|
|
1797
|
+
expansionDotFolder,
|
|
1798
|
+
depType,
|
|
1799
|
+
depFileName,
|
|
1800
|
+
);
|
|
1520
1801
|
|
|
1521
1802
|
// Check if dependency exists in expansion pack
|
|
1522
1803
|
if (!(await fileManager.pathExists(expansionDepPath))) {
|
|
@@ -1539,16 +1820,20 @@ class Installer {
|
|
|
1539
1820
|
`.${packId}`,
|
|
1540
1821
|
);
|
|
1541
1822
|
console.log(
|
|
1542
|
-
chalk.dim(
|
|
1823
|
+
chalk.dim(
|
|
1824
|
+
` Added agent dependency: ${depType}/${depFileName}`,
|
|
1825
|
+
),
|
|
1543
1826
|
);
|
|
1544
1827
|
} else {
|
|
1545
1828
|
// Try common folder
|
|
1546
1829
|
const sourceBase = path.dirname(
|
|
1547
|
-
path.dirname(
|
|
1830
|
+
path.dirname(
|
|
1831
|
+
path.dirname(path.dirname(__filename)),
|
|
1832
|
+
),
|
|
1548
1833
|
); // Go up to project root
|
|
1549
1834
|
const commonDepPath = path.join(
|
|
1550
1835
|
sourceBase,
|
|
1551
|
-
|
|
1836
|
+
"common",
|
|
1552
1837
|
depType,
|
|
1553
1838
|
depFileName,
|
|
1554
1839
|
);
|
|
@@ -1558,7 +1843,10 @@ class Installer {
|
|
|
1558
1843
|
depType,
|
|
1559
1844
|
depFileName,
|
|
1560
1845
|
);
|
|
1561
|
-
await fileManager.copyFile(
|
|
1846
|
+
await fileManager.copyFile(
|
|
1847
|
+
commonDepPath,
|
|
1848
|
+
destinationDepPath,
|
|
1849
|
+
);
|
|
1562
1850
|
console.log(
|
|
1563
1851
|
chalk.dim(
|
|
1564
1852
|
` Added agent dependency from common: ${depType}/${depFileName}`,
|
|
@@ -1578,45 +1866,50 @@ class Installer {
|
|
|
1578
1866
|
} else {
|
|
1579
1867
|
console.warn(
|
|
1580
1868
|
chalk.yellow(
|
|
1581
|
-
` Warning: Core agent ${agentId} not found for team ${path.basename(teamFile,
|
|
1869
|
+
` Warning: Core agent ${agentId} not found for team ${path.basename(teamFile, ".yaml")}`,
|
|
1582
1870
|
),
|
|
1583
1871
|
);
|
|
1584
1872
|
}
|
|
1585
1873
|
}
|
|
1586
1874
|
}
|
|
1587
1875
|
} catch (error) {
|
|
1588
|
-
console.warn(
|
|
1876
|
+
console.warn(
|
|
1877
|
+
` Warning: Could not parse team file ${teamFile}: ${error.message}`,
|
|
1878
|
+
);
|
|
1589
1879
|
}
|
|
1590
1880
|
}
|
|
1591
1881
|
}
|
|
1592
1882
|
|
|
1593
1883
|
getWebBundleInfo(config) {
|
|
1594
|
-
const webBundleType = config.webBundleType ||
|
|
1884
|
+
const webBundleType = config.webBundleType || "all";
|
|
1595
1885
|
|
|
1596
1886
|
switch (webBundleType) {
|
|
1597
|
-
case
|
|
1598
|
-
return
|
|
1887
|
+
case "all": {
|
|
1888
|
+
return "all bundles";
|
|
1599
1889
|
}
|
|
1600
|
-
case
|
|
1601
|
-
return
|
|
1890
|
+
case "agents": {
|
|
1891
|
+
return "individual agents only";
|
|
1602
1892
|
}
|
|
1603
|
-
case
|
|
1893
|
+
case "teams": {
|
|
1604
1894
|
return config.selectedWebBundleTeams
|
|
1605
|
-
? `teams: ${config.selectedWebBundleTeams.join(
|
|
1606
|
-
:
|
|
1895
|
+
? `teams: ${config.selectedWebBundleTeams.join(", ")}`
|
|
1896
|
+
: "selected teams";
|
|
1607
1897
|
}
|
|
1608
|
-
case
|
|
1898
|
+
case "custom": {
|
|
1609
1899
|
const parts = [];
|
|
1610
|
-
if (
|
|
1611
|
-
|
|
1900
|
+
if (
|
|
1901
|
+
config.selectedWebBundleTeams &&
|
|
1902
|
+
config.selectedWebBundleTeams.length > 0
|
|
1903
|
+
) {
|
|
1904
|
+
parts.push(`teams: ${config.selectedWebBundleTeams.join(", ")}`);
|
|
1612
1905
|
}
|
|
1613
1906
|
if (config.includeIndividualAgents) {
|
|
1614
|
-
parts.push(
|
|
1907
|
+
parts.push("individual agents");
|
|
1615
1908
|
}
|
|
1616
|
-
return parts.length > 0 ? parts.join(
|
|
1909
|
+
return parts.length > 0 ? parts.join(" + ") : "custom selection";
|
|
1617
1910
|
}
|
|
1618
1911
|
default: {
|
|
1619
|
-
return
|
|
1912
|
+
return "selected bundles";
|
|
1620
1913
|
}
|
|
1621
1914
|
}
|
|
1622
1915
|
}
|
|
@@ -1627,29 +1920,33 @@ class Installer {
|
|
|
1627
1920
|
const distDir = configLoader.getDistPath();
|
|
1628
1921
|
|
|
1629
1922
|
if (!(await fileManager.pathExists(distDir))) {
|
|
1630
|
-
console.warn(
|
|
1923
|
+
console.warn(
|
|
1924
|
+
'Web bundles not found. Run "npm run build" to generate them.',
|
|
1925
|
+
);
|
|
1631
1926
|
return;
|
|
1632
1927
|
}
|
|
1633
1928
|
|
|
1634
1929
|
// Ensure web bundles directory exists
|
|
1635
1930
|
await fileManager.ensureDirectory(webBundlesDirectory);
|
|
1636
1931
|
|
|
1637
|
-
const webBundleType = config.webBundleType ||
|
|
1932
|
+
const webBundleType = config.webBundleType || "all";
|
|
1638
1933
|
|
|
1639
|
-
if (webBundleType ===
|
|
1934
|
+
if (webBundleType === "all") {
|
|
1640
1935
|
// Copy the entire dist directory structure
|
|
1641
1936
|
await fileManager.copyDirectory(distDir, webBundlesDirectory);
|
|
1642
|
-
console.log(
|
|
1937
|
+
console.log(
|
|
1938
|
+
chalk.green(`✓ Installed all web bundles to: ${webBundlesDirectory}`),
|
|
1939
|
+
);
|
|
1643
1940
|
} else {
|
|
1644
1941
|
let copiedCount = 0;
|
|
1645
1942
|
|
|
1646
1943
|
// Copy specific selections based on type
|
|
1647
1944
|
if (
|
|
1648
|
-
webBundleType ===
|
|
1649
|
-
(webBundleType ===
|
|
1945
|
+
webBundleType === "agents" ||
|
|
1946
|
+
(webBundleType === "custom" && config.includeIndividualAgents)
|
|
1650
1947
|
) {
|
|
1651
|
-
const agentsSource = path.join(distDir,
|
|
1652
|
-
const agentsTarget = path.join(webBundlesDirectory,
|
|
1948
|
+
const agentsSource = path.join(distDir, "agents");
|
|
1949
|
+
const agentsTarget = path.join(webBundlesDirectory, "agents");
|
|
1653
1950
|
if (await fileManager.pathExists(agentsSource)) {
|
|
1654
1951
|
await fileManager.copyDirectory(agentsSource, agentsTarget);
|
|
1655
1952
|
console.log(chalk.green(`✓ Copied individual agent bundles`));
|
|
@@ -1658,12 +1955,12 @@ class Installer {
|
|
|
1658
1955
|
}
|
|
1659
1956
|
|
|
1660
1957
|
if (
|
|
1661
|
-
(webBundleType ===
|
|
1958
|
+
(webBundleType === "teams" || webBundleType === "custom") &&
|
|
1662
1959
|
config.selectedWebBundleTeams &&
|
|
1663
1960
|
config.selectedWebBundleTeams.length > 0
|
|
1664
1961
|
) {
|
|
1665
|
-
const teamsSource = path.join(distDir,
|
|
1666
|
-
const teamsTarget = path.join(webBundlesDirectory,
|
|
1962
|
+
const teamsSource = path.join(distDir, "teams");
|
|
1963
|
+
const teamsTarget = path.join(webBundlesDirectory, "teams");
|
|
1667
1964
|
await fileManager.ensureDirectory(teamsTarget);
|
|
1668
1965
|
|
|
1669
1966
|
for (const teamId of config.selectedWebBundleTeams) {
|
|
@@ -1680,15 +1977,20 @@ class Installer {
|
|
|
1680
1977
|
}
|
|
1681
1978
|
|
|
1682
1979
|
// Always copy expansion packs if they exist
|
|
1683
|
-
const expansionSource = path.join(distDir,
|
|
1684
|
-
const expansionTarget = path.join(
|
|
1980
|
+
const expansionSource = path.join(distDir, "expansion-packs");
|
|
1981
|
+
const expansionTarget = path.join(
|
|
1982
|
+
webBundlesDirectory,
|
|
1983
|
+
"expansion-packs",
|
|
1984
|
+
);
|
|
1685
1985
|
if (await fileManager.pathExists(expansionSource)) {
|
|
1686
1986
|
await fileManager.copyDirectory(expansionSource, expansionTarget);
|
|
1687
1987
|
console.log(chalk.green(`✓ Copied expansion pack bundles`));
|
|
1688
1988
|
}
|
|
1689
1989
|
|
|
1690
1990
|
console.log(
|
|
1691
|
-
chalk.green(
|
|
1991
|
+
chalk.green(
|
|
1992
|
+
`✓ Installed ${copiedCount} selected web bundles to: ${webBundlesDirectory}`,
|
|
1993
|
+
),
|
|
1692
1994
|
);
|
|
1693
1995
|
}
|
|
1694
1996
|
} catch (error) {
|
|
@@ -1697,20 +1999,22 @@ class Installer {
|
|
|
1697
1999
|
}
|
|
1698
2000
|
|
|
1699
2001
|
async copyCommonItems(installDir, targetSubdir, spinner) {
|
|
1700
|
-
const fs = require(
|
|
1701
|
-
const sourceBase = path.dirname(
|
|
1702
|
-
|
|
2002
|
+
const fs = require("node:fs").promises;
|
|
2003
|
+
const sourceBase = path.dirname(
|
|
2004
|
+
path.dirname(path.dirname(path.dirname(__filename))),
|
|
2005
|
+
); // Go up to project root
|
|
2006
|
+
const commonPath = path.join(sourceBase, "common");
|
|
1703
2007
|
const targetPath = path.join(installDir, targetSubdir);
|
|
1704
2008
|
const copiedFiles = [];
|
|
1705
2009
|
|
|
1706
2010
|
// Check if common/ exists
|
|
1707
2011
|
if (!(await fileManager.pathExists(commonPath))) {
|
|
1708
|
-
console.warn(
|
|
2012
|
+
console.warn("Warning: common/ folder not found");
|
|
1709
2013
|
return copiedFiles;
|
|
1710
2014
|
}
|
|
1711
2015
|
|
|
1712
2016
|
// Copy all items from common/ to target
|
|
1713
|
-
const commonItems = await resourceLocator.findFiles(
|
|
2017
|
+
const commonItems = await resourceLocator.findFiles("**/*", {
|
|
1714
2018
|
cwd: commonPath,
|
|
1715
2019
|
nodir: true,
|
|
1716
2020
|
});
|
|
@@ -1720,16 +2024,16 @@ class Installer {
|
|
|
1720
2024
|
const destinationPath = path.join(targetPath, item);
|
|
1721
2025
|
|
|
1722
2026
|
// Read the file content
|
|
1723
|
-
const content = await fs.readFile(sourcePath,
|
|
2027
|
+
const content = await fs.readFile(sourcePath, "utf8");
|
|
1724
2028
|
|
|
1725
2029
|
// Replace {root} with the target subdirectory
|
|
1726
|
-
const updatedContent = content.replaceAll(
|
|
2030
|
+
const updatedContent = content.replaceAll("{root}", targetSubdir);
|
|
1727
2031
|
|
|
1728
2032
|
// Ensure directory exists
|
|
1729
2033
|
await fileManager.ensureDirectory(path.dirname(destinationPath));
|
|
1730
2034
|
|
|
1731
2035
|
// Write the updated content
|
|
1732
|
-
await fs.writeFile(destinationPath, updatedContent,
|
|
2036
|
+
await fs.writeFile(destinationPath, updatedContent, "utf8");
|
|
1733
2037
|
copiedFiles.push(path.join(targetSubdir, item));
|
|
1734
2038
|
}
|
|
1735
2039
|
|
|
@@ -1738,22 +2042,24 @@ class Installer {
|
|
|
1738
2042
|
}
|
|
1739
2043
|
|
|
1740
2044
|
async copyDocsItems(installDir, targetSubdir, spinner) {
|
|
1741
|
-
const fs = require(
|
|
1742
|
-
const sourceBase = path.dirname(
|
|
1743
|
-
|
|
2045
|
+
const fs = require("node:fs").promises;
|
|
2046
|
+
const sourceBase = path.dirname(
|
|
2047
|
+
path.dirname(path.dirname(path.dirname(__filename))),
|
|
2048
|
+
); // Go up to project root
|
|
2049
|
+
const docsPath = path.join(sourceBase, "docs");
|
|
1744
2050
|
const targetPath = path.join(installDir, targetSubdir);
|
|
1745
2051
|
const copiedFiles = [];
|
|
1746
2052
|
|
|
1747
2053
|
// Specific documentation files to copy
|
|
1748
2054
|
const documentFiles = [
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
2055
|
+
"enhanced-ide-development-workflow.md",
|
|
2056
|
+
"user-guide.md",
|
|
2057
|
+
"working-in-the-brownfield.md",
|
|
1752
2058
|
];
|
|
1753
2059
|
|
|
1754
2060
|
// Check if docs/ exists
|
|
1755
2061
|
if (!(await fileManager.pathExists(docsPath))) {
|
|
1756
|
-
console.warn(
|
|
2062
|
+
console.warn("Warning: docs/ folder not found");
|
|
1757
2063
|
return copiedFiles;
|
|
1758
2064
|
}
|
|
1759
2065
|
|
|
@@ -1765,34 +2071,36 @@ class Installer {
|
|
|
1765
2071
|
// Check if the source file exists
|
|
1766
2072
|
if (await fileManager.pathExists(sourcePath)) {
|
|
1767
2073
|
// Read the file content
|
|
1768
|
-
const content = await fs.readFile(sourcePath,
|
|
2074
|
+
const content = await fs.readFile(sourcePath, "utf8");
|
|
1769
2075
|
|
|
1770
2076
|
// Replace {root} with the target subdirectory
|
|
1771
|
-
const updatedContent = content.replaceAll(
|
|
2077
|
+
const updatedContent = content.replaceAll("{root}", targetSubdir);
|
|
1772
2078
|
|
|
1773
2079
|
// Ensure directory exists
|
|
1774
2080
|
await fileManager.ensureDirectory(path.dirname(destinationPath));
|
|
1775
2081
|
|
|
1776
2082
|
// Write the updated content
|
|
1777
|
-
await fs.writeFile(destinationPath, updatedContent,
|
|
2083
|
+
await fs.writeFile(destinationPath, updatedContent, "utf8");
|
|
1778
2084
|
copiedFiles.push(path.join(targetSubdir, documentFile));
|
|
1779
2085
|
}
|
|
1780
2086
|
}
|
|
1781
2087
|
|
|
1782
2088
|
if (copiedFiles.length > 0) {
|
|
1783
|
-
console.log(
|
|
2089
|
+
console.log(
|
|
2090
|
+
chalk.dim(` Added ${copiedFiles.length} documentation files`),
|
|
2091
|
+
);
|
|
1784
2092
|
}
|
|
1785
2093
|
return copiedFiles;
|
|
1786
2094
|
}
|
|
1787
2095
|
|
|
1788
2096
|
async detectExpansionPacks(installDir) {
|
|
1789
2097
|
const expansionPacks = {};
|
|
1790
|
-
const glob = require(
|
|
2098
|
+
const glob = require("glob");
|
|
1791
2099
|
|
|
1792
2100
|
// Find all dot folders that might be expansion packs
|
|
1793
|
-
const dotFolders = glob.sync(
|
|
2101
|
+
const dotFolders = glob.sync(".*", {
|
|
1794
2102
|
cwd: installDir,
|
|
1795
|
-
ignore: [
|
|
2103
|
+
ignore: [".git", ".git/**", ".xiaoma-core", ".xiaoma-core/**"],
|
|
1796
2104
|
});
|
|
1797
2105
|
|
|
1798
2106
|
for (const folder of dotFolders) {
|
|
@@ -1801,9 +2109,12 @@ class Installer {
|
|
|
1801
2109
|
|
|
1802
2110
|
if (stats) {
|
|
1803
2111
|
// Check if it has a manifest
|
|
1804
|
-
const manifestPath = path.join(folderPath,
|
|
2112
|
+
const manifestPath = path.join(folderPath, "install-manifest.yaml");
|
|
1805
2113
|
if (await fileManager.pathExists(manifestPath)) {
|
|
1806
|
-
const manifest = await fileManager.readExpansionPackManifest(
|
|
2114
|
+
const manifest = await fileManager.readExpansionPackManifest(
|
|
2115
|
+
installDir,
|
|
2116
|
+
folder.slice(1),
|
|
2117
|
+
);
|
|
1807
2118
|
if (manifest) {
|
|
1808
2119
|
expansionPacks[folder.slice(1)] = {
|
|
1809
2120
|
path: folderPath,
|
|
@@ -1813,7 +2124,7 @@ class Installer {
|
|
|
1813
2124
|
}
|
|
1814
2125
|
} else {
|
|
1815
2126
|
// Check if it has a config.yaml (expansion pack without manifest)
|
|
1816
|
-
const configPath = path.join(folderPath,
|
|
2127
|
+
const configPath = path.join(folderPath, "config.yaml");
|
|
1817
2128
|
if (await fileManager.pathExists(configPath)) {
|
|
1818
2129
|
expansionPacks[folder.slice(1)] = {
|
|
1819
2130
|
path: folderPath,
|
|
@@ -1836,46 +2147,52 @@ class Installer {
|
|
|
1836
2147
|
|
|
1837
2148
|
// Back up modified files
|
|
1838
2149
|
if (integrity.modified.length > 0) {
|
|
1839
|
-
spinner.text =
|
|
2150
|
+
spinner.text = "Backing up modified files...";
|
|
1840
2151
|
for (const file of integrity.modified) {
|
|
1841
2152
|
const filePath = path.join(installDir, file);
|
|
1842
2153
|
if (await fileManager.pathExists(filePath)) {
|
|
1843
2154
|
const backupPath = await fileManager.backupFile(filePath);
|
|
1844
|
-
console.log(
|
|
2155
|
+
console.log(
|
|
2156
|
+
chalk.dim(` Backed up: ${file} → ${path.basename(backupPath)}`),
|
|
2157
|
+
);
|
|
1845
2158
|
}
|
|
1846
2159
|
}
|
|
1847
2160
|
}
|
|
1848
2161
|
|
|
1849
2162
|
// Restore missing and modified files
|
|
1850
|
-
spinner.text =
|
|
2163
|
+
spinner.text = "Restoring files...";
|
|
1851
2164
|
const filesToRestore = [...integrity.missing, ...integrity.modified];
|
|
1852
2165
|
|
|
1853
2166
|
for (const file of filesToRestore) {
|
|
1854
2167
|
// Skip the manifest file itself
|
|
1855
|
-
if (file.endsWith(
|
|
2168
|
+
if (file.endsWith("install-manifest.yaml")) continue;
|
|
1856
2169
|
|
|
1857
|
-
const relativePath = file.replace(`.${packId}/`,
|
|
2170
|
+
const relativePath = file.replace(`.${packId}/`, "");
|
|
1858
2171
|
const sourcePath = path.join(pack.path, relativePath);
|
|
1859
2172
|
const destinationPath = path.join(installDir, file);
|
|
1860
2173
|
|
|
1861
2174
|
// Check if this is a common/ file that needs special processing
|
|
1862
|
-
const commonBase = path.dirname(
|
|
1863
|
-
|
|
2175
|
+
const commonBase = path.dirname(
|
|
2176
|
+
path.dirname(path.dirname(path.dirname(__filename))),
|
|
2177
|
+
);
|
|
2178
|
+
const commonSourcePath = path.join(commonBase, "common", relativePath);
|
|
1864
2179
|
|
|
1865
2180
|
if (await fileManager.pathExists(commonSourcePath)) {
|
|
1866
2181
|
// This is a common/ file - needs template processing
|
|
1867
|
-
const fs = require(
|
|
1868
|
-
const content = await fs.readFile(commonSourcePath,
|
|
1869
|
-
const updatedContent = content.replaceAll(
|
|
2182
|
+
const fs = require("node:fs").promises;
|
|
2183
|
+
const content = await fs.readFile(commonSourcePath, "utf8");
|
|
2184
|
+
const updatedContent = content.replaceAll("{root}", `.${packId}`);
|
|
1870
2185
|
await fileManager.ensureDirectory(path.dirname(destinationPath));
|
|
1871
|
-
await fs.writeFile(destinationPath, updatedContent,
|
|
2186
|
+
await fs.writeFile(destinationPath, updatedContent, "utf8");
|
|
1872
2187
|
spinner.text = `Restored: ${file}`;
|
|
1873
2188
|
} else if (await fileManager.pathExists(sourcePath)) {
|
|
1874
2189
|
// Regular file from expansion pack
|
|
1875
2190
|
await fileManager.copyFile(sourcePath, destinationPath);
|
|
1876
2191
|
spinner.text = `Restored: ${file}`;
|
|
1877
2192
|
} else {
|
|
1878
|
-
console.warn(
|
|
2193
|
+
console.warn(
|
|
2194
|
+
chalk.yellow(` Warning: Source file not found: ${file}`),
|
|
2195
|
+
);
|
|
1879
2196
|
}
|
|
1880
2197
|
}
|
|
1881
2198
|
|
|
@@ -1884,11 +2201,15 @@ class Installer {
|
|
|
1884
2201
|
// Show summary
|
|
1885
2202
|
console.log(chalk.green(`\n✓ ${pack.name} repaired!`));
|
|
1886
2203
|
if (integrity.missing.length > 0) {
|
|
1887
|
-
console.log(
|
|
2204
|
+
console.log(
|
|
2205
|
+
chalk.green(` Restored ${integrity.missing.length} missing files`),
|
|
2206
|
+
);
|
|
1888
2207
|
}
|
|
1889
2208
|
if (integrity.modified.length > 0) {
|
|
1890
2209
|
console.log(
|
|
1891
|
-
chalk.green(
|
|
2210
|
+
chalk.green(
|
|
2211
|
+
` Restored ${integrity.modified.length} modified files (backups created)`,
|
|
2212
|
+
),
|
|
1892
2213
|
);
|
|
1893
2214
|
}
|
|
1894
2215
|
} catch (error) {
|
|
@@ -1899,8 +2220,8 @@ class Installer {
|
|
|
1899
2220
|
|
|
1900
2221
|
compareVersions(v1, v2) {
|
|
1901
2222
|
// Simple semver comparison
|
|
1902
|
-
const parts1 = v1.split(
|
|
1903
|
-
const parts2 = v2.split(
|
|
2223
|
+
const parts1 = v1.split(".").map(Number);
|
|
2224
|
+
const parts2 = v2.split(".").map(Number);
|
|
1904
2225
|
|
|
1905
2226
|
for (let index = 0; index < 3; index++) {
|
|
1906
2227
|
const part1 = parts1[index] || 0;
|
|
@@ -1914,21 +2235,21 @@ class Installer {
|
|
|
1914
2235
|
}
|
|
1915
2236
|
|
|
1916
2237
|
async cleanupLegacyYmlFiles(installDir, spinner) {
|
|
1917
|
-
const glob = require(
|
|
1918
|
-
const fs = require(
|
|
2238
|
+
const glob = require("glob");
|
|
2239
|
+
const fs = require("node:fs").promises;
|
|
1919
2240
|
|
|
1920
2241
|
try {
|
|
1921
2242
|
// Find all .yml files in the installation directory
|
|
1922
|
-
const ymlFiles = glob.sync(
|
|
2243
|
+
const ymlFiles = glob.sync("**/*.yml", {
|
|
1923
2244
|
cwd: installDir,
|
|
1924
|
-
ignore: [
|
|
2245
|
+
ignore: ["**/node_modules/**", "**/.git/**"],
|
|
1925
2246
|
});
|
|
1926
2247
|
|
|
1927
2248
|
let deletedCount = 0;
|
|
1928
2249
|
|
|
1929
2250
|
for (const ymlFile of ymlFiles) {
|
|
1930
2251
|
// Check if corresponding .yaml file exists
|
|
1931
|
-
const yamlFile = ymlFile.replace(/\.yml$/,
|
|
2252
|
+
const yamlFile = ymlFile.replace(/\.yml$/, ".yaml");
|
|
1932
2253
|
const ymlPath = path.join(installDir, ymlFile);
|
|
1933
2254
|
const yamlPath = path.join(installDir, yamlFile);
|
|
1934
2255
|
|
|
@@ -1936,15 +2257,21 @@ class Installer {
|
|
|
1936
2257
|
// .yaml counterpart exists, delete the .yml file
|
|
1937
2258
|
await fs.unlink(ymlPath);
|
|
1938
2259
|
deletedCount++;
|
|
1939
|
-
console.log(
|
|
2260
|
+
console.log(
|
|
2261
|
+
chalk.dim(` Removed legacy: ${ymlFile} (replaced by ${yamlFile})`),
|
|
2262
|
+
);
|
|
1940
2263
|
}
|
|
1941
2264
|
}
|
|
1942
2265
|
|
|
1943
2266
|
if (deletedCount > 0) {
|
|
1944
|
-
console.log(
|
|
2267
|
+
console.log(
|
|
2268
|
+
chalk.green(`✓ Cleaned up ${deletedCount} legacy .yml files`),
|
|
2269
|
+
);
|
|
1945
2270
|
}
|
|
1946
2271
|
} catch (error) {
|
|
1947
|
-
console.warn(
|
|
2272
|
+
console.warn(
|
|
2273
|
+
`Warning: Could not cleanup legacy .yml files: ${error.message}`,
|
|
2274
|
+
);
|
|
1948
2275
|
}
|
|
1949
2276
|
}
|
|
1950
2277
|
|
|
@@ -1953,8 +2280,8 @@ class Installer {
|
|
|
1953
2280
|
let currentDir = process.cwd();
|
|
1954
2281
|
|
|
1955
2282
|
while (currentDir !== path.dirname(currentDir)) {
|
|
1956
|
-
const bmadDir = path.join(currentDir,
|
|
1957
|
-
const manifestPath = path.join(bmadDir,
|
|
2283
|
+
const bmadDir = path.join(currentDir, ".xiaoma-core");
|
|
2284
|
+
const manifestPath = path.join(bmadDir, "install-manifest.yaml");
|
|
1958
2285
|
|
|
1959
2286
|
if (await fileManager.pathExists(manifestPath)) {
|
|
1960
2287
|
return currentDir; // Return parent directory, not .xiaoma-core itself
|
|
@@ -1964,8 +2291,8 @@ class Installer {
|
|
|
1964
2291
|
}
|
|
1965
2292
|
|
|
1966
2293
|
// Also check if we're inside a .xiaoma-core directory
|
|
1967
|
-
if (path.basename(process.cwd()) ===
|
|
1968
|
-
const manifestPath = path.join(process.cwd(),
|
|
2294
|
+
if (path.basename(process.cwd()) === ".xiaoma-core") {
|
|
2295
|
+
const manifestPath = path.join(process.cwd(), "install-manifest.yaml");
|
|
1969
2296
|
if (await fileManager.pathExists(manifestPath)) {
|
|
1970
2297
|
return path.dirname(process.cwd()); // Return parent directory
|
|
1971
2298
|
}
|
|
@@ -1975,23 +2302,29 @@ class Installer {
|
|
|
1975
2302
|
}
|
|
1976
2303
|
|
|
1977
2304
|
async flatten(options) {
|
|
1978
|
-
const { spawn } = require(
|
|
1979
|
-
const flattenerPath = path.join(
|
|
2305
|
+
const { spawn } = require("node:child_process");
|
|
2306
|
+
const flattenerPath = path.join(
|
|
2307
|
+
__dirname,
|
|
2308
|
+
"..",
|
|
2309
|
+
"..",
|
|
2310
|
+
"flattener",
|
|
2311
|
+
"main.js",
|
|
2312
|
+
);
|
|
1980
2313
|
|
|
1981
2314
|
const arguments_ = [];
|
|
1982
2315
|
if (options.input) {
|
|
1983
|
-
arguments_.push(
|
|
2316
|
+
arguments_.push("--input", options.input);
|
|
1984
2317
|
}
|
|
1985
2318
|
if (options.output) {
|
|
1986
|
-
arguments_.push(
|
|
2319
|
+
arguments_.push("--output", options.output);
|
|
1987
2320
|
}
|
|
1988
2321
|
|
|
1989
|
-
const child = spawn(
|
|
1990
|
-
stdio:
|
|
2322
|
+
const child = spawn("node", [flattenerPath, ...arguments_], {
|
|
2323
|
+
stdio: "inherit",
|
|
1991
2324
|
cwd: process.cwd(),
|
|
1992
2325
|
});
|
|
1993
2326
|
|
|
1994
|
-
child.on(
|
|
2327
|
+
child.on("exit", (code) => {
|
|
1995
2328
|
process.exit(code);
|
|
1996
2329
|
});
|
|
1997
2330
|
}
|