teamix-evo 0.1.0 → 0.3.0
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/LICENSE +21 -0
- package/README.md +222 -0
- package/dist/core/index.d.ts +442 -0
- package/dist/core/index.js +1521 -0
- package/dist/core/index.js.map +1 -0
- package/dist/index.js +2607 -578
- package/dist/index.js.map +1 -1
- package/package.json +34 -13
package/dist/index.js
CHANGED
|
@@ -1,67 +1,122 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { Command as Command24 } from "commander";
|
|
5
|
+
import { createRequire as createRequire5 } from "module";
|
|
6
|
+
|
|
7
|
+
// src/commands/design/index.ts
|
|
8
|
+
import { Command as Command6 } from "commander";
|
|
9
|
+
|
|
10
|
+
// src/commands/design/init.ts
|
|
11
|
+
import { Command } from "commander";
|
|
12
|
+
|
|
13
|
+
// src/ide/QoderAdapter.ts
|
|
14
|
+
import * as os from "os";
|
|
15
|
+
import * as path from "path";
|
|
16
|
+
var QoderAdapter = class {
|
|
17
|
+
kind = "qoder";
|
|
18
|
+
name = "qoder";
|
|
19
|
+
getProjectRoot() {
|
|
20
|
+
return process.cwd();
|
|
21
|
+
}
|
|
22
|
+
detectIde() {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
getSkillTargetDir(skillName, scope, projectRoot) {
|
|
26
|
+
const base = scope === "global" ? path.join(os.homedir(), ".qoder") : path.join(projectRoot ?? this.getProjectRoot(), ".qoder");
|
|
27
|
+
return path.join(base, "skills", skillName);
|
|
28
|
+
}
|
|
6
29
|
};
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
30
|
+
|
|
31
|
+
// src/ide/ClaudeAdapter.ts
|
|
32
|
+
import * as os2 from "os";
|
|
33
|
+
import * as path2 from "path";
|
|
34
|
+
var ClaudeAdapter = class {
|
|
35
|
+
kind = "claude";
|
|
36
|
+
name = "claude";
|
|
37
|
+
getProjectRoot() {
|
|
38
|
+
return process.cwd();
|
|
39
|
+
}
|
|
40
|
+
detectIde() {
|
|
41
|
+
return Boolean(process.env.CLAUDECODE);
|
|
42
|
+
}
|
|
43
|
+
getSkillTargetDir(skillName, scope, projectRoot) {
|
|
44
|
+
const base = scope === "global" ? path2.join(os2.homedir(), ".claude") : path2.join(projectRoot ?? this.getProjectRoot(), ".claude");
|
|
45
|
+
return path2.join(base, "skills", skillName);
|
|
46
|
+
}
|
|
10
47
|
};
|
|
11
48
|
|
|
49
|
+
// src/ide/index.ts
|
|
50
|
+
var ALL_IDE_KINDS = ["qoder", "claude"];
|
|
51
|
+
function getAdapter(kind) {
|
|
52
|
+
switch (kind) {
|
|
53
|
+
case "qoder":
|
|
54
|
+
return new QoderAdapter();
|
|
55
|
+
case "claude":
|
|
56
|
+
return new ClaudeAdapter();
|
|
57
|
+
default: {
|
|
58
|
+
const _exhaustive = kind;
|
|
59
|
+
throw new Error(`Unsupported IDE kind: ${_exhaustive}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function detectIde() {
|
|
64
|
+
const claude = new ClaudeAdapter();
|
|
65
|
+
if (claude.detectIde()) return claude;
|
|
66
|
+
return new QoderAdapter();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// src/core/design-init.ts
|
|
70
|
+
import * as path9 from "path";
|
|
71
|
+
import * as fs6 from "fs/promises";
|
|
72
|
+
import { createRequire as createRequire2 } from "module";
|
|
73
|
+
import {
|
|
74
|
+
loadDesignPack,
|
|
75
|
+
loadDesignPackageManifest,
|
|
76
|
+
mergeDefaultAndVariant
|
|
77
|
+
} from "@teamix-evo/registry";
|
|
78
|
+
|
|
79
|
+
// src/utils/fs.ts
|
|
80
|
+
import * as fs from "fs/promises";
|
|
81
|
+
import * as path3 from "path";
|
|
82
|
+
|
|
12
83
|
// src/utils/logger.ts
|
|
13
84
|
import { red, yellow, cyan, green, gray } from "kolorist";
|
|
14
|
-
var isDebug
|
|
15
|
-
var
|
|
16
|
-
|
|
17
|
-
"
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
debug(msg) {
|
|
33
|
-
if (isDebug) {
|
|
34
|
-
console.log(gray("\u22A1"), gray(msg));
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
};
|
|
85
|
+
var isDebug = process.env.TEAMIX_DEBUG === "1";
|
|
86
|
+
var logger = {
|
|
87
|
+
info(msg) {
|
|
88
|
+
console.log(cyan("\u2139"), msg);
|
|
89
|
+
},
|
|
90
|
+
warn(msg) {
|
|
91
|
+
console.warn(yellow("\u26A0"), msg);
|
|
92
|
+
},
|
|
93
|
+
error(msg) {
|
|
94
|
+
console.error(red("\u2716"), msg);
|
|
95
|
+
},
|
|
96
|
+
success(msg) {
|
|
97
|
+
console.log(green("\u2714"), msg);
|
|
98
|
+
},
|
|
99
|
+
debug(msg) {
|
|
100
|
+
if (isDebug) {
|
|
101
|
+
console.log(gray("\u22A1"), gray(msg));
|
|
102
|
+
}
|
|
38
103
|
}
|
|
39
|
-
}
|
|
104
|
+
};
|
|
40
105
|
|
|
41
106
|
// src/utils/fs.ts
|
|
42
|
-
var fs_exports = {};
|
|
43
|
-
__export(fs_exports, {
|
|
44
|
-
backupFile: () => backupFile,
|
|
45
|
-
ensureDir: () => ensureDir,
|
|
46
|
-
fileExists: () => fileExists,
|
|
47
|
-
readFileOrNull: () => readFileOrNull,
|
|
48
|
-
writeFileSafe: () => writeFileSafe
|
|
49
|
-
});
|
|
50
|
-
import * as fs2 from "fs/promises";
|
|
51
|
-
import * as path2 from "path";
|
|
52
107
|
async function ensureDir(dir) {
|
|
53
|
-
await
|
|
108
|
+
await fs.mkdir(dir, { recursive: true });
|
|
54
109
|
}
|
|
55
110
|
async function writeFileSafe(filePath, content) {
|
|
56
|
-
const dir =
|
|
111
|
+
const dir = path3.dirname(filePath);
|
|
57
112
|
await ensureDir(dir);
|
|
58
113
|
const tmp = filePath + ".tmp";
|
|
59
|
-
await
|
|
60
|
-
await
|
|
114
|
+
await fs.writeFile(tmp, content, "utf-8");
|
|
115
|
+
await fs.rename(tmp, filePath);
|
|
61
116
|
}
|
|
62
117
|
async function readFileOrNull(filePath) {
|
|
63
118
|
try {
|
|
64
|
-
return await
|
|
119
|
+
return await fs.readFile(filePath, "utf-8");
|
|
65
120
|
} catch (err) {
|
|
66
121
|
if (err.code === "ENOENT") {
|
|
67
122
|
return null;
|
|
@@ -75,96 +130,26 @@ async function backupFile(filePath, projectRoot) {
|
|
|
75
130
|
logger.debug(`Skip backup: ${filePath} does not exist`);
|
|
76
131
|
return;
|
|
77
132
|
}
|
|
78
|
-
const
|
|
133
|
+
const rel2 = path3.relative(projectRoot, filePath);
|
|
79
134
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
80
|
-
const backupPath =
|
|
135
|
+
const backupPath = path3.join(
|
|
81
136
|
projectRoot,
|
|
82
137
|
".teamix-evo",
|
|
83
138
|
".backups",
|
|
84
|
-
`${
|
|
139
|
+
`${rel2}.${timestamp}.bak`
|
|
85
140
|
);
|
|
86
|
-
await ensureDir(
|
|
87
|
-
await
|
|
88
|
-
logger.debug(`Backed up ${
|
|
141
|
+
await ensureDir(path3.dirname(backupPath));
|
|
142
|
+
await fs.writeFile(backupPath, content, "utf-8");
|
|
143
|
+
logger.debug(`Backed up ${rel2} \u2192 ${path3.relative(projectRoot, backupPath)}`);
|
|
89
144
|
}
|
|
90
145
|
async function fileExists(filePath) {
|
|
91
146
|
try {
|
|
92
|
-
await
|
|
147
|
+
await fs.access(filePath);
|
|
93
148
|
return true;
|
|
94
149
|
} catch {
|
|
95
150
|
return false;
|
|
96
151
|
}
|
|
97
152
|
}
|
|
98
|
-
var init_fs = __esm({
|
|
99
|
-
"src/utils/fs.ts"() {
|
|
100
|
-
"use strict";
|
|
101
|
-
init_logger();
|
|
102
|
-
}
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
// src/index.ts
|
|
106
|
-
import { Command as Command5 } from "commander";
|
|
107
|
-
|
|
108
|
-
// src/commands/design/index.ts
|
|
109
|
-
import { Command as Command4 } from "commander";
|
|
110
|
-
|
|
111
|
-
// src/commands/design/init.ts
|
|
112
|
-
import { Command } from "commander";
|
|
113
|
-
|
|
114
|
-
// src/ide/QoderAdapter.ts
|
|
115
|
-
var QoderAdapter = class {
|
|
116
|
-
name = "qoder";
|
|
117
|
-
getProjectRoot() {
|
|
118
|
-
return process.cwd();
|
|
119
|
-
}
|
|
120
|
-
detectIde() {
|
|
121
|
-
return true;
|
|
122
|
-
}
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
// src/ide/index.ts
|
|
126
|
-
function detectIde() {
|
|
127
|
-
return new QoderAdapter();
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// src/core/registry-client.ts
|
|
131
|
-
init_logger();
|
|
132
|
-
import * as path from "path";
|
|
133
|
-
import * as fs from "fs/promises";
|
|
134
|
-
import { createRequire } from "module";
|
|
135
|
-
import { loadVariantManifest } from "@teamix-evo/registry";
|
|
136
|
-
function resolveVariantPackage(packageName, variant) {
|
|
137
|
-
const require2 = createRequire(import.meta.url);
|
|
138
|
-
const pkgJsonPath = require2.resolve(`${packageName}/package.json`);
|
|
139
|
-
const pkgRoot = path.dirname(pkgJsonPath);
|
|
140
|
-
return path.join(pkgRoot, "library", variant);
|
|
141
|
-
}
|
|
142
|
-
async function loadVariantData(packageName, variant) {
|
|
143
|
-
const variantDir = resolveVariantPackage(packageName, variant);
|
|
144
|
-
const require2 = createRequire(import.meta.url);
|
|
145
|
-
const pkgJsonPath = require2.resolve(`${packageName}/package.json`);
|
|
146
|
-
const packageRoot = path.dirname(pkgJsonPath);
|
|
147
|
-
logger.debug(`Resolved variant dir: ${variantDir}`);
|
|
148
|
-
logger.debug(`Package root: ${packageRoot}`);
|
|
149
|
-
const manifest = await loadVariantManifest(variantDir);
|
|
150
|
-
let data = {};
|
|
151
|
-
const dataPath = path.join(variantDir, "_data.json");
|
|
152
|
-
try {
|
|
153
|
-
const raw = await fs.readFile(dataPath, "utf-8");
|
|
154
|
-
data = JSON.parse(raw);
|
|
155
|
-
} catch (err) {
|
|
156
|
-
if (err.code !== "ENOENT") {
|
|
157
|
-
throw err;
|
|
158
|
-
}
|
|
159
|
-
logger.debug(`No _data.json found at ${dataPath}, using empty data`);
|
|
160
|
-
}
|
|
161
|
-
return { manifest, data, variantDir, packageRoot };
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// src/core/installer.ts
|
|
165
|
-
init_fs();
|
|
166
|
-
import * as path3 from "path";
|
|
167
|
-
import * as fs4 from "fs/promises";
|
|
168
153
|
|
|
169
154
|
// src/utils/hash.ts
|
|
170
155
|
import { createHash } from "crypto";
|
|
@@ -173,134 +158,71 @@ function computeHash(content) {
|
|
|
173
158
|
return `sha256:${hash}`;
|
|
174
159
|
}
|
|
175
160
|
|
|
176
|
-
// src/
|
|
177
|
-
import
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
async function installResources(options) {
|
|
193
|
-
const { projectRoot, manifest, data, variantDir, packageRoot } = options;
|
|
194
|
-
const installedResources = [];
|
|
195
|
-
for (const resource of manifest.resources) {
|
|
196
|
-
logger.debug(`Installing resource: ${resource.id} \u2192 ${resource.target}`);
|
|
197
|
-
if (resource.recursive) {
|
|
198
|
-
const results = await installRecursiveResource(
|
|
199
|
-
resource,
|
|
200
|
-
projectRoot,
|
|
201
|
-
data,
|
|
202
|
-
variantDir,
|
|
203
|
-
packageRoot
|
|
204
|
-
);
|
|
205
|
-
installedResources.push(...results);
|
|
206
|
-
} else {
|
|
207
|
-
const result = await installSingleResource(
|
|
208
|
-
resource,
|
|
209
|
-
projectRoot,
|
|
210
|
-
data,
|
|
211
|
-
variantDir,
|
|
212
|
-
packageRoot
|
|
213
|
-
);
|
|
214
|
-
installedResources.push(result);
|
|
215
|
-
}
|
|
161
|
+
// src/core/design-pack-classify.ts
|
|
162
|
+
import * as path4 from "path";
|
|
163
|
+
var TEAMIX_DIR = ".teamix-evo/design";
|
|
164
|
+
var TOKENS_DIR = ".teamix-evo/tokens";
|
|
165
|
+
var TOKENS_PACK_PREFIX = "foundations/tokens/";
|
|
166
|
+
var ROOT_MANAGED_FILES = {
|
|
167
|
+
"DESIGN.md": { target: "DESIGN.md", managedRegions: ["core"] },
|
|
168
|
+
"AGENTS.md": { target: "AGENTS.md", managedRegions: ["teamix-evo"] },
|
|
169
|
+
"CLAUDE.md": { target: "CLAUDE.md", managedRegions: ["teamix-evo"] }
|
|
170
|
+
};
|
|
171
|
+
var FROZEN_FILES = /* @__PURE__ */ new Set([
|
|
172
|
+
"foundations/tokens/tokens.overrides.css"
|
|
173
|
+
]);
|
|
174
|
+
function classifyPackFile(relPath) {
|
|
175
|
+
if (path4.basename(relPath) === "README.md") {
|
|
176
|
+
return null;
|
|
216
177
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
let content;
|
|
226
|
-
if (resource.template) {
|
|
227
|
-
const templateContent = await loadTemplateFile(sourcePath);
|
|
228
|
-
content = renderTemplate(templateContent, data);
|
|
229
|
-
} else {
|
|
230
|
-
content = await fs4.readFile(sourcePath, "utf-8");
|
|
178
|
+
const rootManaged = ROOT_MANAGED_FILES[relPath];
|
|
179
|
+
if (rootManaged) {
|
|
180
|
+
return {
|
|
181
|
+
target: rootManaged.target,
|
|
182
|
+
strategy: "managed",
|
|
183
|
+
managedRegions: rootManaged.managedRegions,
|
|
184
|
+
isFrozen: false
|
|
185
|
+
};
|
|
231
186
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
target: resource.target,
|
|
238
|
-
hash,
|
|
239
|
-
strategy: resource.updateStrategy
|
|
240
|
-
};
|
|
241
|
-
}
|
|
242
|
-
async function installRecursiveResource(resource, projectRoot, data, variantDir, packageRoot) {
|
|
243
|
-
const sourcePath = resolveSourcePath(resource.source, variantDir, packageRoot);
|
|
244
|
-
const targetDir = path3.join(projectRoot, resource.target);
|
|
245
|
-
const results = [];
|
|
246
|
-
await ensureDir(targetDir);
|
|
247
|
-
const entries = await walkDir(sourcePath);
|
|
248
|
-
for (const entry of entries) {
|
|
249
|
-
const relPath = path3.relative(sourcePath, entry);
|
|
250
|
-
let targetFile = path3.join(targetDir, relPath);
|
|
251
|
-
if (resource.template && targetFile.endsWith(".hbs")) {
|
|
252
|
-
targetFile = targetFile.slice(0, -4);
|
|
253
|
-
}
|
|
254
|
-
let content;
|
|
255
|
-
if (resource.template && entry.endsWith(".hbs")) {
|
|
256
|
-
const templateContent = await loadTemplateFile(entry);
|
|
257
|
-
content = renderTemplate(templateContent, data);
|
|
258
|
-
} else {
|
|
259
|
-
content = await fs4.readFile(entry, "utf-8");
|
|
187
|
+
if (relPath.startsWith(TOKENS_PACK_PREFIX)) {
|
|
188
|
+
const rel2 = relPath.slice(TOKENS_PACK_PREFIX.length);
|
|
189
|
+
const target = path4.posix.join(TOKENS_DIR, rel2);
|
|
190
|
+
if (FROZEN_FILES.has(relPath)) {
|
|
191
|
+
return { target, strategy: "frozen", isFrozen: true };
|
|
260
192
|
}
|
|
261
|
-
|
|
262
|
-
const hash = computeHash(content);
|
|
263
|
-
const targetRel = path3.relative(projectRoot, targetFile);
|
|
264
|
-
results.push({
|
|
265
|
-
id: `${resource.id}:${relPath}`,
|
|
266
|
-
target: targetRel,
|
|
267
|
-
hash,
|
|
268
|
-
strategy: resource.updateStrategy
|
|
269
|
-
});
|
|
270
|
-
logger.debug(` Written: ${targetRel}`);
|
|
271
|
-
}
|
|
272
|
-
return results;
|
|
273
|
-
}
|
|
274
|
-
function resolveSourcePath(source, variantDir, packageRoot) {
|
|
275
|
-
if (source.startsWith("_template/")) {
|
|
276
|
-
return path3.join(packageRoot, source);
|
|
193
|
+
return { target, strategy: "regenerable", isFrozen: false };
|
|
277
194
|
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
const fullPath = path3.join(dir, entry.name);
|
|
285
|
-
if (entry.isDirectory()) {
|
|
286
|
-
files.push(...await walkDir(fullPath));
|
|
287
|
-
} else if (entry.isFile()) {
|
|
288
|
-
files.push(fullPath);
|
|
289
|
-
}
|
|
195
|
+
if (FROZEN_FILES.has(relPath)) {
|
|
196
|
+
return {
|
|
197
|
+
target: path4.posix.join(TEAMIX_DIR, relPath),
|
|
198
|
+
strategy: "frozen",
|
|
199
|
+
isFrozen: true
|
|
200
|
+
};
|
|
290
201
|
}
|
|
291
|
-
return
|
|
202
|
+
return {
|
|
203
|
+
target: path4.posix.join(TEAMIX_DIR, relPath),
|
|
204
|
+
strategy: "regenerable",
|
|
205
|
+
isFrozen: false
|
|
206
|
+
};
|
|
292
207
|
}
|
|
293
208
|
|
|
294
209
|
// src/core/state.ts
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
210
|
+
import * as path5 from "path";
|
|
211
|
+
import {
|
|
212
|
+
validateConfig,
|
|
213
|
+
validateInstalled,
|
|
214
|
+
validateSkillsLock,
|
|
215
|
+
DesignPackLockSchema
|
|
216
|
+
} from "@teamix-evo/registry";
|
|
217
|
+
var TEAMIX_DIR2 = ".teamix-evo";
|
|
300
218
|
var CONFIG_FILE = "config.json";
|
|
301
219
|
var MANIFEST_FILE = "manifest.json";
|
|
220
|
+
var DESIGN_DIR = "design";
|
|
221
|
+
var DESIGN_LOCK_FILE = "pack.lock.json";
|
|
222
|
+
var SKILLS_DIR = "skills";
|
|
223
|
+
var SKILLS_LOCK_FILE = "manifest.lock.json";
|
|
302
224
|
function getTeamixDir(projectRoot) {
|
|
303
|
-
return
|
|
225
|
+
return path5.join(projectRoot, TEAMIX_DIR2);
|
|
304
226
|
}
|
|
305
227
|
async function ensureTeamixDir(projectRoot) {
|
|
306
228
|
const dir = getTeamixDir(projectRoot);
|
|
@@ -308,7 +230,7 @@ async function ensureTeamixDir(projectRoot) {
|
|
|
308
230
|
return dir;
|
|
309
231
|
}
|
|
310
232
|
async function readProjectConfig(projectRoot) {
|
|
311
|
-
const configPath =
|
|
233
|
+
const configPath = path5.join(projectRoot, TEAMIX_DIR2, CONFIG_FILE);
|
|
312
234
|
const raw = await readFileOrNull(configPath);
|
|
313
235
|
if (raw === null) return null;
|
|
314
236
|
try {
|
|
@@ -325,12 +247,12 @@ async function readProjectConfig(projectRoot) {
|
|
|
325
247
|
}
|
|
326
248
|
}
|
|
327
249
|
async function writeProjectConfig(projectRoot, config) {
|
|
328
|
-
const configPath =
|
|
250
|
+
const configPath = path5.join(projectRoot, TEAMIX_DIR2, CONFIG_FILE);
|
|
329
251
|
await writeFileSafe(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
330
252
|
logger.debug(`Wrote config \u2192 ${configPath}`);
|
|
331
253
|
}
|
|
332
254
|
async function readInstalledManifest(projectRoot) {
|
|
333
|
-
const manifestPath =
|
|
255
|
+
const manifestPath = path5.join(projectRoot, TEAMIX_DIR2, MANIFEST_FILE);
|
|
334
256
|
const raw = await readFileOrNull(manifestPath);
|
|
335
257
|
if (raw === null) return null;
|
|
336
258
|
try {
|
|
@@ -347,388 +269,879 @@ async function readInstalledManifest(projectRoot) {
|
|
|
347
269
|
}
|
|
348
270
|
}
|
|
349
271
|
async function writeInstalledManifest(projectRoot, manifest) {
|
|
350
|
-
const manifestPath =
|
|
272
|
+
const manifestPath = path5.join(projectRoot, TEAMIX_DIR2, MANIFEST_FILE);
|
|
351
273
|
await writeFileSafe(manifestPath, JSON.stringify(manifest, null, 2) + "\n");
|
|
352
274
|
logger.debug(`Wrote manifest \u2192 ${manifestPath}`);
|
|
353
275
|
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
276
|
+
async function readDesignPackLock(projectRoot) {
|
|
277
|
+
const lockPath = path5.join(
|
|
278
|
+
projectRoot,
|
|
279
|
+
TEAMIX_DIR2,
|
|
280
|
+
DESIGN_DIR,
|
|
281
|
+
DESIGN_LOCK_FILE
|
|
282
|
+
);
|
|
283
|
+
const raw = await readFileOrNull(lockPath);
|
|
284
|
+
if (raw === null) return null;
|
|
360
285
|
try {
|
|
361
|
-
const
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
logger.debug(`IDE: ${ide.name}`);
|
|
366
|
-
await ensureTeamixDir(projectRoot);
|
|
367
|
-
const existingConfig = await readProjectConfig(projectRoot);
|
|
368
|
-
if (existingConfig?.packages?.design) {
|
|
369
|
-
logger.warn(
|
|
370
|
-
`Design system already initialized (variant: ${existingConfig.packages.design.variant}). Use "teamix-evo design update" to update.`
|
|
371
|
-
);
|
|
372
|
-
return;
|
|
286
|
+
const parsed = DesignPackLockSchema.safeParse(JSON.parse(raw));
|
|
287
|
+
if (!parsed.success) {
|
|
288
|
+
logger.warn(`Invalid design pack.lock.json: ${parsed.error.message}`);
|
|
289
|
+
return null;
|
|
373
290
|
}
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
logger.
|
|
377
|
-
`
|
|
378
|
-
);
|
|
379
|
-
logger.debug(`Resources: ${manifest.resources.length}`);
|
|
380
|
-
logger.info("Installing resources...");
|
|
381
|
-
const result = await installResources({
|
|
382
|
-
projectRoot,
|
|
383
|
-
manifest,
|
|
384
|
-
data,
|
|
385
|
-
variantDir,
|
|
386
|
-
packageRoot
|
|
387
|
-
});
|
|
388
|
-
const config = {
|
|
389
|
-
$schema: "https://teamix-evo.dev/schema/config/v1.json",
|
|
390
|
-
schemaVersion: 1,
|
|
391
|
-
ide: ide.name,
|
|
392
|
-
packages: {
|
|
393
|
-
design: {
|
|
394
|
-
variant,
|
|
395
|
-
version: manifest.version
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
};
|
|
399
|
-
await writeProjectConfig(projectRoot, config);
|
|
400
|
-
const installedManifest = {
|
|
401
|
-
schemaVersion: 1,
|
|
402
|
-
installed: [
|
|
403
|
-
{
|
|
404
|
-
package: DESIGN_PACKAGE,
|
|
405
|
-
variant,
|
|
406
|
-
version: manifest.version,
|
|
407
|
-
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
408
|
-
resources: result.resources
|
|
409
|
-
}
|
|
410
|
-
]
|
|
411
|
-
};
|
|
412
|
-
await writeInstalledManifest(projectRoot, installedManifest);
|
|
413
|
-
logger.success(
|
|
414
|
-
`Design system initialized: ${manifest.displayName} v${manifest.version}`
|
|
291
|
+
return parsed.data;
|
|
292
|
+
} catch (err) {
|
|
293
|
+
logger.warn(
|
|
294
|
+
`Failed to parse design pack.lock.json: ${err.message}`
|
|
415
295
|
);
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
async function readDesignVariant(projectRoot) {
|
|
300
|
+
const lock = await readDesignPackLock(projectRoot);
|
|
301
|
+
return lock?.variant.name ?? null;
|
|
302
|
+
}
|
|
303
|
+
function getSkillsSourceDir(projectRoot, skillName) {
|
|
304
|
+
const base = path5.join(projectRoot, TEAMIX_DIR2, SKILLS_DIR);
|
|
305
|
+
return skillName ? path5.join(base, skillName) : base;
|
|
306
|
+
}
|
|
307
|
+
async function readSkillsLock(projectRoot) {
|
|
308
|
+
const lockPath = path5.join(
|
|
309
|
+
projectRoot,
|
|
310
|
+
TEAMIX_DIR2,
|
|
311
|
+
SKILLS_DIR,
|
|
312
|
+
SKILLS_LOCK_FILE
|
|
313
|
+
);
|
|
314
|
+
const raw = await readFileOrNull(lockPath);
|
|
315
|
+
if (raw === null) return null;
|
|
316
|
+
try {
|
|
317
|
+
const data = JSON.parse(raw);
|
|
318
|
+
const result = validateSkillsLock(data);
|
|
319
|
+
if (!result.success) {
|
|
320
|
+
logger.warn(`Invalid skills manifest.lock.json: ${result.error}`);
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
323
|
+
return result.data;
|
|
420
324
|
} catch (err) {
|
|
421
|
-
logger.
|
|
422
|
-
|
|
423
|
-
|
|
325
|
+
logger.warn(
|
|
326
|
+
`Failed to parse skills manifest.lock.json: ${err.message}`
|
|
327
|
+
);
|
|
328
|
+
return null;
|
|
424
329
|
}
|
|
425
|
-
}
|
|
330
|
+
}
|
|
331
|
+
async function writeSkillsLock(projectRoot, lock) {
|
|
332
|
+
const lockPath = path5.join(
|
|
333
|
+
projectRoot,
|
|
334
|
+
TEAMIX_DIR2,
|
|
335
|
+
SKILLS_DIR,
|
|
336
|
+
SKILLS_LOCK_FILE
|
|
337
|
+
);
|
|
338
|
+
await writeFileSafe(lockPath, JSON.stringify(lock, null, 2) + "\n");
|
|
339
|
+
logger.debug(`Wrote skills lock \u2192 ${lockPath}`);
|
|
340
|
+
}
|
|
426
341
|
|
|
427
|
-
// src/
|
|
428
|
-
import
|
|
342
|
+
// src/core/skills-client.ts
|
|
343
|
+
import * as path6 from "path";
|
|
344
|
+
import * as fs2 from "fs/promises";
|
|
345
|
+
import { createRequire } from "module";
|
|
346
|
+
import { loadSkillsPackageManifest } from "@teamix-evo/registry";
|
|
347
|
+
var require2 = createRequire(import.meta.url);
|
|
348
|
+
function resolvePackageRoot(packageName) {
|
|
349
|
+
const pkgJsonPath = require2.resolve(`${packageName}/package.json`);
|
|
350
|
+
return path6.dirname(pkgJsonPath);
|
|
351
|
+
}
|
|
352
|
+
async function loadSkillsData(packageName) {
|
|
353
|
+
const packageRoot = resolvePackageRoot(packageName);
|
|
354
|
+
logger.debug(`Resolved skills package root: ${packageRoot}`);
|
|
355
|
+
const manifest = await loadSkillsPackageManifest(packageRoot);
|
|
356
|
+
let data = {};
|
|
357
|
+
const dataPath = path6.join(packageRoot, "_data.json");
|
|
358
|
+
try {
|
|
359
|
+
const raw = await fs2.readFile(dataPath, "utf-8");
|
|
360
|
+
data = JSON.parse(raw);
|
|
361
|
+
} catch (err) {
|
|
362
|
+
if (err.code !== "ENOENT") {
|
|
363
|
+
throw err;
|
|
364
|
+
}
|
|
365
|
+
logger.debug(`No _data.json found at ${dataPath}, using empty data`);
|
|
366
|
+
}
|
|
367
|
+
return { manifest, data, packageRoot };
|
|
368
|
+
}
|
|
429
369
|
|
|
430
|
-
// src/core/
|
|
431
|
-
|
|
432
|
-
import * as path5 from "path";
|
|
370
|
+
// src/core/skills-installer.ts
|
|
371
|
+
import * as path8 from "path";
|
|
433
372
|
import * as fs5 from "fs/promises";
|
|
434
|
-
import {
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
const summary = { overwritten: 0, managed: 0, skipped: 0, created: 0 };
|
|
451
|
-
const installedPkg = installedManifest.installed.find(
|
|
452
|
-
(p) => p.package === packageName && p.variant === manifest.variant
|
|
453
|
-
);
|
|
454
|
-
const installedMap = /* @__PURE__ */ new Map();
|
|
455
|
-
if (installedPkg) {
|
|
456
|
-
for (const res of installedPkg.resources) {
|
|
457
|
-
installedMap.set(res.id, res);
|
|
373
|
+
import { replaceManagedRegion } from "@teamix-evo/registry";
|
|
374
|
+
|
|
375
|
+
// src/utils/template.ts
|
|
376
|
+
import Handlebars from "handlebars";
|
|
377
|
+
import * as fs3 from "fs/promises";
|
|
378
|
+
Handlebars.registerHelper("lowercase", (str) => {
|
|
379
|
+
return typeof str === "string" ? str.toLowerCase() : String(str ?? "").toLowerCase();
|
|
380
|
+
});
|
|
381
|
+
var compiledCache = /* @__PURE__ */ new Map();
|
|
382
|
+
var MAX_CACHE_SIZE = 64;
|
|
383
|
+
function getCompiledTemplate(templateContent) {
|
|
384
|
+
let compiled = compiledCache.get(templateContent);
|
|
385
|
+
if (!compiled) {
|
|
386
|
+
if (compiledCache.size >= MAX_CACHE_SIZE) {
|
|
387
|
+
const firstKey = compiledCache.keys().next().value;
|
|
388
|
+
compiledCache.delete(firstKey);
|
|
458
389
|
}
|
|
390
|
+
compiled = Handlebars.compile(templateContent, { noEscape: true });
|
|
391
|
+
compiledCache.set(templateContent, compiled);
|
|
459
392
|
}
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
);
|
|
482
|
-
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
return { resources: updatedResources, summary };
|
|
486
|
-
}
|
|
487
|
-
async function updateSingleResource(resource, projectRoot, data, variantDir, packageRoot, installedMap, summary) {
|
|
488
|
-
const targetPath = path5.join(projectRoot, resource.target);
|
|
489
|
-
const exists = await fileExists(targetPath);
|
|
490
|
-
const installed = installedMap.get(resource.id);
|
|
491
|
-
const sourcePath = resolveSourcePath2(resource.source, variantDir, packageRoot);
|
|
492
|
-
let newContent;
|
|
493
|
-
if (resource.template) {
|
|
494
|
-
const templateContent = await loadTemplateFile(sourcePath);
|
|
495
|
-
newContent = renderTemplate(templateContent, data);
|
|
496
|
-
} else {
|
|
497
|
-
newContent = await fs5.readFile(sourcePath, "utf-8");
|
|
393
|
+
return compiled;
|
|
394
|
+
}
|
|
395
|
+
function renderTemplate(templateContent, data) {
|
|
396
|
+
const compiled = getCompiledTemplate(templateContent);
|
|
397
|
+
return compiled(data);
|
|
398
|
+
}
|
|
399
|
+
async function loadTemplateFile(filePath) {
|
|
400
|
+
return fs3.readFile(filePath, "utf-8");
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// src/utils/path.ts
|
|
404
|
+
import * as path7 from "path";
|
|
405
|
+
import * as fs4 from "fs/promises";
|
|
406
|
+
async function walkDir(dir) {
|
|
407
|
+
const files = [];
|
|
408
|
+
const entries = await fs4.readdir(dir, { withFileTypes: true });
|
|
409
|
+
for (const entry of entries) {
|
|
410
|
+
const fullPath = path7.join(dir, entry.name);
|
|
411
|
+
if (entry.isDirectory()) {
|
|
412
|
+
files.push(...await walkDir(fullPath));
|
|
413
|
+
} else if (entry.isFile()) {
|
|
414
|
+
files.push(fullPath);
|
|
415
|
+
}
|
|
498
416
|
}
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
417
|
+
return files;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// src/core/skills-installer.ts
|
|
421
|
+
async function installSkills(options) {
|
|
422
|
+
const { manifest, ides, scope, onlyIds } = options;
|
|
423
|
+
const installed = [];
|
|
424
|
+
const targets = manifest.skills.filter(
|
|
425
|
+
(s) => !onlyIds || onlyIds.includes(s.id)
|
|
426
|
+
);
|
|
427
|
+
for (const skill of targets) {
|
|
428
|
+
const skillIdes = skill.ides.filter((i) => ides.includes(i));
|
|
429
|
+
if (skillIdes.length === 0) {
|
|
430
|
+
logger.warn(
|
|
431
|
+
`Skill "${skill.name}" supports [${skill.ides.join(
|
|
432
|
+
","
|
|
433
|
+
)}], no overlap with [${ides.join(",")}]; skipped.`
|
|
434
|
+
);
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
437
|
+
const sourceRecords = await writeSkillSource(skill, options);
|
|
438
|
+
installed.push(...sourceRecords);
|
|
439
|
+
for (const ide of skillIdes) {
|
|
440
|
+
const mirrorRecords = await mirrorSkillToIde(
|
|
441
|
+
skill,
|
|
442
|
+
ide,
|
|
443
|
+
scope,
|
|
444
|
+
options.projectRoot
|
|
445
|
+
);
|
|
446
|
+
installed.push(...mirrorRecords);
|
|
515
447
|
}
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
448
|
+
}
|
|
449
|
+
return { resources: installed, count: installed.length };
|
|
450
|
+
}
|
|
451
|
+
async function writeSkillSource(skill, options) {
|
|
452
|
+
const { data, packageRoot, projectRoot } = options;
|
|
453
|
+
const sourceAbs = path8.resolve(packageRoot, skill.source);
|
|
454
|
+
const targetDir = getSkillsSourceDir(projectRoot, skill.name);
|
|
455
|
+
const stat4 = await fs5.stat(sourceAbs);
|
|
456
|
+
const records = [];
|
|
457
|
+
if (stat4.isFile()) {
|
|
458
|
+
const targetFile = path8.join(targetDir, "SKILL.md");
|
|
459
|
+
const content = await renderSkillContent(sourceAbs, skill, data);
|
|
460
|
+
await writeFileSafe(targetFile, content);
|
|
461
|
+
records.push(makeSourceRecord(skill, targetFile, content));
|
|
462
|
+
logger.debug(` Wrote source: ${targetFile}`);
|
|
463
|
+
return records;
|
|
464
|
+
}
|
|
465
|
+
await ensureDir(targetDir);
|
|
466
|
+
const entries = await walkDir(sourceAbs);
|
|
467
|
+
for (const entry of entries) {
|
|
468
|
+
const rel2 = path8.relative(sourceAbs, entry);
|
|
469
|
+
let targetFile = path8.join(targetDir, rel2);
|
|
470
|
+
if (skill.template && targetFile.endsWith(".hbs")) {
|
|
471
|
+
targetFile = targetFile.slice(0, -4);
|
|
472
|
+
}
|
|
473
|
+
const content = skill.template && entry.endsWith(".hbs") ? renderTemplate(await loadTemplateFile(entry), { ...data, skill }) : await fs5.readFile(entry, "utf-8");
|
|
474
|
+
await writeFileSafe(targetFile, content);
|
|
475
|
+
const relWritten = path8.relative(targetDir, targetFile);
|
|
476
|
+
records.push(makeSourceRecord(skill, targetFile, content, relWritten));
|
|
477
|
+
logger.debug(` Wrote source: ${targetFile}`);
|
|
478
|
+
}
|
|
479
|
+
return records;
|
|
480
|
+
}
|
|
481
|
+
async function mirrorSkillToIde(skill, ide, scope, projectRoot) {
|
|
482
|
+
const sourceDir = getSkillsSourceDir(projectRoot, skill.name);
|
|
483
|
+
const adapter = getAdapter(ide);
|
|
484
|
+
const targetDir = adapter.getSkillTargetDir(skill.name, scope, projectRoot);
|
|
485
|
+
const records = [];
|
|
486
|
+
const sourceFiles = await walkDir(sourceDir);
|
|
487
|
+
await ensureDir(targetDir);
|
|
488
|
+
for (const src of sourceFiles) {
|
|
489
|
+
const rel2 = path8.relative(sourceDir, src);
|
|
490
|
+
const targetFile = path8.join(targetDir, rel2);
|
|
491
|
+
const content = await fs5.readFile(src, "utf-8");
|
|
492
|
+
await writeFileSafe(targetFile, content);
|
|
493
|
+
records.push(makeMirrorRecord(skill, targetFile, content, ide, scope, rel2));
|
|
494
|
+
logger.debug(` Mirrored ${ide}:${scope}: ${targetFile}`);
|
|
495
|
+
}
|
|
496
|
+
return records;
|
|
497
|
+
}
|
|
498
|
+
async function renderSkillContent(sourceAbs, skill, data) {
|
|
499
|
+
if (skill.template ?? sourceAbs.endsWith(".hbs")) {
|
|
500
|
+
const tpl = await loadTemplateFile(sourceAbs);
|
|
501
|
+
return renderTemplate(tpl, { ...data, skill });
|
|
502
|
+
}
|
|
503
|
+
return fs5.readFile(sourceAbs, "utf-8");
|
|
504
|
+
}
|
|
505
|
+
function makeSourceRecord(skill, targetAbs, content, rel2) {
|
|
506
|
+
const id = rel2 ? `${skill.id}:source:${rel2}` : `${skill.id}:source`;
|
|
507
|
+
return {
|
|
508
|
+
id,
|
|
509
|
+
target: targetAbs,
|
|
510
|
+
hash: computeHash(content),
|
|
511
|
+
strategy: skill.updateStrategy
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
function makeMirrorRecord(skill, targetAbs, content, ide, scope, rel2) {
|
|
515
|
+
const id = rel2 && rel2 !== "SKILL.md" ? `${skill.id}:${rel2}` : skill.id;
|
|
516
|
+
return {
|
|
517
|
+
id,
|
|
518
|
+
target: targetAbs,
|
|
519
|
+
hash: computeHash(content),
|
|
520
|
+
strategy: skill.updateStrategy,
|
|
521
|
+
ide,
|
|
522
|
+
scope
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
async function updateSkills(options) {
|
|
526
|
+
const { manifest, ides, scope, projectRoot } = options;
|
|
527
|
+
const summary = { overwritten: 0, managed: 0, skipped: 0, created: 0 };
|
|
528
|
+
const updated = [];
|
|
529
|
+
for (const skill of manifest.skills) {
|
|
530
|
+
const skillIdes = skill.ides.filter((i) => ides.includes(i));
|
|
531
|
+
if (skillIdes.length === 0) continue;
|
|
532
|
+
const sourceRecords = await rewriteSkillSource(
|
|
533
|
+
skill,
|
|
534
|
+
options,
|
|
535
|
+
summary
|
|
536
|
+
);
|
|
537
|
+
updated.push(...sourceRecords);
|
|
538
|
+
for (const ide of skillIdes) {
|
|
539
|
+
const mirrorRecords = await mirrorSkillToIde(
|
|
540
|
+
skill,
|
|
541
|
+
ide,
|
|
542
|
+
scope,
|
|
543
|
+
projectRoot
|
|
544
|
+
);
|
|
545
|
+
updated.push(...mirrorRecords);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
return { resources: updated, summary };
|
|
549
|
+
}
|
|
550
|
+
async function rewriteSkillSource(skill, options, summary) {
|
|
551
|
+
const { data, packageRoot, projectRoot } = options;
|
|
552
|
+
const sourceAbs = path8.resolve(packageRoot, skill.source);
|
|
553
|
+
const targetDir = getSkillsSourceDir(projectRoot, skill.name);
|
|
554
|
+
const stat4 = await fs5.stat(sourceAbs);
|
|
555
|
+
if (!stat4.isFile()) {
|
|
556
|
+
await ensureDir(targetDir);
|
|
557
|
+
const entries = await walkDir(sourceAbs);
|
|
558
|
+
const records = [];
|
|
559
|
+
for (const entry of entries) {
|
|
560
|
+
const rel2 = path8.relative(sourceAbs, entry);
|
|
561
|
+
let targetFile2 = path8.join(targetDir, rel2);
|
|
562
|
+
if (skill.template && targetFile2.endsWith(".hbs")) {
|
|
563
|
+
targetFile2 = targetFile2.slice(0, -4);
|
|
564
|
+
}
|
|
565
|
+
const content = skill.template && entry.endsWith(".hbs") ? renderTemplate(await loadTemplateFile(entry), { ...data, skill }) : await fs5.readFile(entry, "utf-8");
|
|
566
|
+
const exists2 = await fileExists(targetFile2);
|
|
567
|
+
if (exists2) {
|
|
568
|
+
await backupFile(targetFile2, projectRoot);
|
|
519
569
|
summary.overwritten++;
|
|
520
570
|
} else {
|
|
521
571
|
summary.created++;
|
|
522
572
|
}
|
|
523
|
-
await writeFileSafe(
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
id: resource.id,
|
|
527
|
-
target: resource.target,
|
|
528
|
-
hash: newHash,
|
|
529
|
-
strategy: resource.updateStrategy
|
|
530
|
-
};
|
|
573
|
+
await writeFileSafe(targetFile2, content);
|
|
574
|
+
const relWritten = path8.relative(targetDir, targetFile2);
|
|
575
|
+
records.push(makeSourceRecord(skill, targetFile2, content, relWritten));
|
|
531
576
|
}
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
577
|
+
return records;
|
|
578
|
+
}
|
|
579
|
+
const targetFile = path8.join(targetDir, "SKILL.md");
|
|
580
|
+
const newContent = await renderSkillContent(sourceAbs, skill, data);
|
|
581
|
+
const exists = await fileExists(targetFile);
|
|
582
|
+
if (skill.updateStrategy === "frozen") {
|
|
583
|
+
if (exists) {
|
|
584
|
+
summary.skipped++;
|
|
585
|
+
const current2 = await readFileOrNull(targetFile) ?? newContent;
|
|
586
|
+
return [makeSourceRecord(skill, targetFile, current2)];
|
|
587
|
+
}
|
|
588
|
+
await writeFileSafe(targetFile, newContent);
|
|
589
|
+
summary.created++;
|
|
590
|
+
return [makeSourceRecord(skill, targetFile, newContent)];
|
|
591
|
+
}
|
|
592
|
+
if (skill.updateStrategy === "regenerable" || !exists) {
|
|
593
|
+
if (exists) {
|
|
594
|
+
await backupFile(targetFile, projectRoot);
|
|
595
|
+
summary.overwritten++;
|
|
596
|
+
} else {
|
|
597
|
+
summary.created++;
|
|
598
|
+
}
|
|
599
|
+
await writeFileSafe(targetFile, newContent);
|
|
600
|
+
return [makeSourceRecord(skill, targetFile, newContent)];
|
|
601
|
+
}
|
|
602
|
+
const current = await readFileOrNull(targetFile);
|
|
603
|
+
let merged = current ?? newContent;
|
|
604
|
+
for (const regionId of skill.managedRegions ?? []) {
|
|
605
|
+
const re = new RegExp(
|
|
606
|
+
`<!-- teamix-evo:managed:start id="${escapeRegExp(
|
|
607
|
+
regionId
|
|
608
|
+
)}" -->([\\s\\S]*?)<!-- teamix-evo:managed:end id="${escapeRegExp(
|
|
609
|
+
regionId
|
|
610
|
+
)}" -->`
|
|
611
|
+
);
|
|
612
|
+
const match = newContent.match(re);
|
|
613
|
+
if (match) {
|
|
614
|
+
const region = match[1].replace(/^\n/, "").replace(/\n$/, "");
|
|
615
|
+
try {
|
|
616
|
+
merged = replaceManagedRegion(merged, regionId, region);
|
|
617
|
+
} catch {
|
|
618
|
+
logger.warn(
|
|
619
|
+
`Managed region "${regionId}" not found in ${targetFile}. Skipped.`
|
|
549
620
|
);
|
|
550
|
-
const match = newContent.match(regionPattern);
|
|
551
|
-
if (match) {
|
|
552
|
-
const regionContent = match[1].replace(/^\n/, "").replace(/\n$/, "");
|
|
553
|
-
try {
|
|
554
|
-
updatedContent = replaceManagedRegion(
|
|
555
|
-
updatedContent,
|
|
556
|
-
regionId,
|
|
557
|
-
regionContent
|
|
558
|
-
);
|
|
559
|
-
} catch {
|
|
560
|
-
logger.warn(
|
|
561
|
-
`Managed region "${regionId}" not found in ${resource.target}. Skipping region.`
|
|
562
|
-
);
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
621
|
}
|
|
566
|
-
await backupFile(targetPath, projectRoot);
|
|
567
|
-
await writeFileSafe(targetPath, updatedContent);
|
|
568
|
-
summary.managed++;
|
|
569
|
-
return {
|
|
570
|
-
id: resource.id,
|
|
571
|
-
target: resource.target,
|
|
572
|
-
hash: computeHash(updatedContent),
|
|
573
|
-
strategy: resource.updateStrategy
|
|
574
|
-
};
|
|
575
622
|
}
|
|
576
|
-
default:
|
|
577
|
-
summary.skipped++;
|
|
578
|
-
return installed ?? {
|
|
579
|
-
id: resource.id,
|
|
580
|
-
target: resource.target,
|
|
581
|
-
hash: newHash,
|
|
582
|
-
strategy: resource.updateStrategy
|
|
583
|
-
};
|
|
584
623
|
}
|
|
624
|
+
await backupFile(targetFile, projectRoot);
|
|
625
|
+
await writeFileSafe(targetFile, merged);
|
|
626
|
+
summary.managed++;
|
|
627
|
+
return [makeSourceRecord(skill, targetFile, merged)];
|
|
585
628
|
}
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
629
|
+
function escapeRegExp(str) {
|
|
630
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
631
|
+
}
|
|
632
|
+
async function syncSkillsToIdes(options) {
|
|
633
|
+
const { projectRoot, skills, ides, scope, onlyIds } = options;
|
|
634
|
+
const out = [];
|
|
635
|
+
const targets = skills.filter((s) => !onlyIds || onlyIds.includes(s.id));
|
|
636
|
+
for (const skill of targets) {
|
|
637
|
+
const sourceDir = getSkillsSourceDir(projectRoot, skill.name);
|
|
638
|
+
if (!await fileExists(sourceDir)) {
|
|
639
|
+
logger.warn(
|
|
640
|
+
`Skill "${skill.id}" has no source at ${sourceDir}; skipped.`
|
|
641
|
+
);
|
|
642
|
+
continue;
|
|
643
|
+
}
|
|
644
|
+
for (const ide of ides) {
|
|
645
|
+
const adapter = getAdapter(ide);
|
|
646
|
+
const targetDir = adapter.getSkillTargetDir(
|
|
647
|
+
skill.name,
|
|
648
|
+
scope,
|
|
649
|
+
projectRoot
|
|
650
|
+
);
|
|
651
|
+
await ensureDir(targetDir);
|
|
652
|
+
const sourceFiles = await walkDir(sourceDir);
|
|
653
|
+
for (const src of sourceFiles) {
|
|
654
|
+
const rel2 = path8.relative(sourceDir, src);
|
|
655
|
+
const targetFile = path8.join(targetDir, rel2);
|
|
656
|
+
const content = await fs5.readFile(src, "utf-8");
|
|
657
|
+
await writeFileSafe(targetFile, content);
|
|
658
|
+
out.push({
|
|
659
|
+
id: rel2 === "SKILL.md" ? skill.id : `${skill.id}:${rel2}`,
|
|
660
|
+
target: targetFile,
|
|
661
|
+
hash: computeHash(content),
|
|
662
|
+
strategy: skill.updateStrategy,
|
|
663
|
+
ide,
|
|
664
|
+
scope
|
|
665
|
+
});
|
|
600
666
|
}
|
|
601
|
-
return results;
|
|
602
667
|
}
|
|
603
668
|
}
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
669
|
+
return { resources: out, count: out.length };
|
|
670
|
+
}
|
|
671
|
+
async function removeSkillFiles(records) {
|
|
672
|
+
const removed = [];
|
|
673
|
+
for (const r of records) {
|
|
674
|
+
try {
|
|
675
|
+
await fs5.unlink(r.target);
|
|
676
|
+
removed.push(r.target);
|
|
677
|
+
} catch (err) {
|
|
678
|
+
if (err.code !== "ENOENT") {
|
|
679
|
+
logger.warn(`Failed to remove ${r.target}: ${err.message}`);
|
|
680
|
+
}
|
|
612
681
|
}
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
682
|
+
}
|
|
683
|
+
const parents = new Set(records.map((r) => path8.dirname(r.target)));
|
|
684
|
+
for (const dir of parents) {
|
|
685
|
+
try {
|
|
686
|
+
const entries = await fs5.readdir(dir);
|
|
687
|
+
if (entries.length === 0) await fs5.rmdir(dir);
|
|
688
|
+
} catch {
|
|
619
689
|
}
|
|
620
|
-
await writeFileSafe(targetFile, content);
|
|
621
|
-
const hash = computeHash(content);
|
|
622
|
-
const targetRel = path5.relative(projectRoot, targetFile);
|
|
623
|
-
results.push({
|
|
624
|
-
id: `${resource.id}:${relPath}`,
|
|
625
|
-
target: targetRel,
|
|
626
|
-
hash,
|
|
627
|
-
strategy: resource.updateStrategy
|
|
628
|
-
});
|
|
629
|
-
summary.overwritten++;
|
|
630
690
|
}
|
|
631
|
-
return
|
|
691
|
+
return removed;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// src/core/skills-add.ts
|
|
695
|
+
var DEFAULT_SKILLS_PACKAGE = "@teamix-evo/skills";
|
|
696
|
+
var FLAT_VARIANT = "_flat";
|
|
697
|
+
async function runSkillsAdd(options) {
|
|
698
|
+
const { projectRoot, names: requestedNames } = options;
|
|
699
|
+
const packageName = options.packageName ?? DEFAULT_SKILLS_PACKAGE;
|
|
700
|
+
const ideIdent = options.ide ?? "qoder";
|
|
701
|
+
const isIncremental = !!requestedNames && requestedNames.length > 0;
|
|
702
|
+
await ensureTeamixDir(projectRoot);
|
|
703
|
+
const existingConfig = await readProjectConfig(projectRoot);
|
|
704
|
+
const existingSkillsCfg = existingConfig?.packages?.skills;
|
|
705
|
+
if (!isIncremental && existingSkillsCfg) {
|
|
706
|
+
return { status: "already-added" };
|
|
707
|
+
}
|
|
708
|
+
const ides = options.ides && options.ides.length > 0 ? [...options.ides] : existingSkillsCfg?.ides ? [...existingSkillsCfg.ides] : [];
|
|
709
|
+
const scope = options.scope ?? existingSkillsCfg?.scope;
|
|
710
|
+
if (ides.length === 0) {
|
|
711
|
+
throw new Error("At least one IDE must be selected.");
|
|
712
|
+
}
|
|
713
|
+
if (!scope) {
|
|
714
|
+
throw new Error("Scope must be specified (project | global).");
|
|
715
|
+
}
|
|
716
|
+
const { manifest, data, packageRoot } = await loadSkillsData(packageName);
|
|
717
|
+
const currentDesignVariant = await readDesignVariant(projectRoot);
|
|
718
|
+
if (isIncremental) {
|
|
719
|
+
const known = new Set(manifest.skills.map((s) => s.id));
|
|
720
|
+
const unknown = requestedNames.filter((n) => !known.has(n));
|
|
721
|
+
if (unknown.length > 0) {
|
|
722
|
+
const available = [...known].join(", ");
|
|
723
|
+
throw new Error(
|
|
724
|
+
`Unknown skill id(s): ${unknown.join(", ")}. Available: ${available || "(none)"}.`
|
|
725
|
+
);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
const existingInstalled = await readInstalledManifest(projectRoot);
|
|
729
|
+
const existingPkg = existingInstalled?.installed.find(
|
|
730
|
+
(p) => p.package === packageName
|
|
731
|
+
);
|
|
732
|
+
const existingLock = await readSkillsLock(projectRoot);
|
|
733
|
+
const existingSkillIds = /* @__PURE__ */ new Set([
|
|
734
|
+
...Object.keys(existingLock?.skills ?? {}),
|
|
735
|
+
// Legacy fallback: pre-ADR-0013 installs only had manifest.json. Derive
|
|
736
|
+
// skill ids by stripping the trailing :source / :sub-file suffix.
|
|
737
|
+
...(existingPkg?.resources ?? []).map((r) => r.id.split(":")[0])
|
|
738
|
+
]);
|
|
739
|
+
let onlyIds;
|
|
740
|
+
let skippedSkillIds;
|
|
741
|
+
if (isIncremental) {
|
|
742
|
+
skippedSkillIds = requestedNames.filter((n) => existingSkillIds.has(n));
|
|
743
|
+
onlyIds = requestedNames.filter((n) => !existingSkillIds.has(n));
|
|
744
|
+
} else {
|
|
745
|
+
skippedSkillIds = [];
|
|
746
|
+
onlyIds = manifest.skills.filter((s) => {
|
|
747
|
+
if (!s.variant) return true;
|
|
748
|
+
if (!currentDesignVariant) {
|
|
749
|
+
logger.debug(
|
|
750
|
+
`Skipping variant-bound skill "${s.id}" (variant=${s.variant}): no design pack installed; will be picked up when "design init" runs.`
|
|
751
|
+
);
|
|
752
|
+
return false;
|
|
753
|
+
}
|
|
754
|
+
if (s.variant !== currentDesignVariant) {
|
|
755
|
+
logger.debug(
|
|
756
|
+
`Skipping variant-bound skill "${s.id}" (variant=${s.variant}): current design variant is "${currentDesignVariant}".`
|
|
757
|
+
);
|
|
758
|
+
return false;
|
|
759
|
+
}
|
|
760
|
+
return true;
|
|
761
|
+
}).map((s) => s.id);
|
|
762
|
+
}
|
|
763
|
+
if (isIncremental && onlyIds.length === 0) {
|
|
764
|
+
return {
|
|
765
|
+
status: "installed",
|
|
766
|
+
packageName,
|
|
767
|
+
version: existingSkillsCfg?.version ?? manifest.version,
|
|
768
|
+
ides,
|
|
769
|
+
scope,
|
|
770
|
+
skillCount: 0,
|
|
771
|
+
fileCount: 0,
|
|
772
|
+
resources: [],
|
|
773
|
+
addedSkillIds: [],
|
|
774
|
+
skippedSkillIds
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
const result = await installSkills({
|
|
778
|
+
projectRoot,
|
|
779
|
+
manifest,
|
|
780
|
+
data,
|
|
781
|
+
packageRoot,
|
|
782
|
+
ides,
|
|
783
|
+
scope,
|
|
784
|
+
onlyIds
|
|
785
|
+
});
|
|
786
|
+
const config = existingConfig ?? {
|
|
787
|
+
$schema: "https://teamix-evo.dev/schema/config/v1.json",
|
|
788
|
+
schemaVersion: 1,
|
|
789
|
+
ide: ideIdent,
|
|
790
|
+
packages: {}
|
|
791
|
+
};
|
|
792
|
+
config.packages.skills = {
|
|
793
|
+
variant: FLAT_VARIANT,
|
|
794
|
+
version: manifest.version,
|
|
795
|
+
ides,
|
|
796
|
+
scope
|
|
797
|
+
};
|
|
798
|
+
await writeProjectConfig(projectRoot, config);
|
|
799
|
+
const installedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
800
|
+
const installedManifest = existingInstalled ?? {
|
|
801
|
+
schemaVersion: 1,
|
|
802
|
+
installed: []
|
|
803
|
+
};
|
|
804
|
+
const idx = installedManifest.installed.findIndex(
|
|
805
|
+
(p) => p.package === packageName
|
|
806
|
+
);
|
|
807
|
+
const mergedResources = mergeInstalledResources(
|
|
808
|
+
existingPkg?.resources ?? [],
|
|
809
|
+
result.resources
|
|
810
|
+
);
|
|
811
|
+
const entry = {
|
|
812
|
+
package: packageName,
|
|
813
|
+
variant: FLAT_VARIANT,
|
|
814
|
+
version: manifest.version,
|
|
815
|
+
installedAt,
|
|
816
|
+
resources: mergedResources
|
|
817
|
+
};
|
|
818
|
+
if (idx >= 0) installedManifest.installed[idx] = entry;
|
|
819
|
+
else installedManifest.installed.push(entry);
|
|
820
|
+
await writeInstalledManifest(projectRoot, installedManifest);
|
|
821
|
+
const lock = existingLock ?? {
|
|
822
|
+
schemaVersion: 1,
|
|
823
|
+
skills: {}
|
|
824
|
+
};
|
|
825
|
+
for (const skillId of onlyIds) {
|
|
826
|
+
const skillDef = manifest.skills.find((s) => s.id === skillId);
|
|
827
|
+
if (!skillDef) continue;
|
|
828
|
+
const mirroredTo = skillDef.ides.filter((i) => ides.includes(i));
|
|
829
|
+
lock.skills[skillId] = {
|
|
830
|
+
version: skillDef.version,
|
|
831
|
+
from: packageName,
|
|
832
|
+
installedAt,
|
|
833
|
+
scope,
|
|
834
|
+
mirroredTo
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
await writeSkillsLock(projectRoot, lock);
|
|
838
|
+
return {
|
|
839
|
+
status: "installed",
|
|
840
|
+
packageName,
|
|
841
|
+
version: manifest.version,
|
|
842
|
+
ides,
|
|
843
|
+
scope,
|
|
844
|
+
skillCount: onlyIds.length,
|
|
845
|
+
fileCount: result.count,
|
|
846
|
+
resources: result.resources,
|
|
847
|
+
addedSkillIds: onlyIds,
|
|
848
|
+
skippedSkillIds
|
|
849
|
+
};
|
|
632
850
|
}
|
|
633
|
-
function
|
|
634
|
-
|
|
635
|
-
|
|
851
|
+
function mergeInstalledResources(existing, next) {
|
|
852
|
+
const map = /* @__PURE__ */ new Map();
|
|
853
|
+
const key = (r) => `${r.id}|${r.ide ?? ""}|${r.scope ?? ""}`;
|
|
854
|
+
for (const r of existing) map.set(key(r), r);
|
|
855
|
+
for (const r of next) map.set(key(r), r);
|
|
856
|
+
return [...map.values()];
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
// src/core/design-init.ts
|
|
860
|
+
var BASELINE_DESIGN_RULES_SKILL = "teamix-evo-design-rules";
|
|
861
|
+
var DEFAULT_SKILLS_PACKAGE2 = "@teamix-evo/skills";
|
|
862
|
+
var DEFAULT_AUTO_SKILL_IDES = ["qoder", "claude"];
|
|
863
|
+
var DEFAULT_AUTO_SKILL_SCOPE = "project";
|
|
864
|
+
var DEFAULT_DESIGN_PACKAGE = "@teamix-evo/design";
|
|
865
|
+
var require3 = createRequire2(import.meta.url);
|
|
866
|
+
async function runDesignInit(options) {
|
|
867
|
+
const { projectRoot, variant, ide } = options;
|
|
868
|
+
const packageName = options.packageName ?? DEFAULT_DESIGN_PACKAGE;
|
|
869
|
+
await ensureTeamixDir(projectRoot);
|
|
870
|
+
const existingConfig = await readProjectConfig(projectRoot);
|
|
871
|
+
if (existingConfig?.packages?.design) {
|
|
872
|
+
return {
|
|
873
|
+
status: "already-initialized",
|
|
874
|
+
existingVariant: existingConfig.packages.design.variant
|
|
875
|
+
};
|
|
876
|
+
}
|
|
877
|
+
const packageRoot = options.packageRoot ?? resolveDesignPackageRoot(packageName);
|
|
878
|
+
const catalog = await loadDesignPackageManifest(packageRoot);
|
|
879
|
+
const variantEntry = catalog.variants.find((v) => v.name === variant);
|
|
880
|
+
if (!variantEntry) {
|
|
881
|
+
const known = catalog.variants.map((v) => v.name).join(", ");
|
|
882
|
+
throw new Error(
|
|
883
|
+
`Design variant not found: "${variant}". Known variants: ${known || "(none)"}. Hint: run "teamix-evo design list-variants" to see all.`
|
|
884
|
+
);
|
|
885
|
+
}
|
|
886
|
+
const defaultDir = path9.join(packageRoot, "default");
|
|
887
|
+
const variantDir = path9.join(packageRoot, "variants", variant);
|
|
888
|
+
const defaultPack = await loadDesignPack(defaultDir);
|
|
889
|
+
const variantPack = await loadDesignPack(variantDir);
|
|
890
|
+
const merge = await mergeDefaultAndVariant(defaultDir, variantDir);
|
|
891
|
+
const installed = [];
|
|
892
|
+
for (const file of merge.files) {
|
|
893
|
+
const result = await installPackFile(file, projectRoot);
|
|
894
|
+
if (result) installed.push(result);
|
|
636
895
|
}
|
|
637
|
-
|
|
896
|
+
const lock = {
|
|
897
|
+
schemaVersion: 1,
|
|
898
|
+
default: { version: defaultPack.version, from: packageName },
|
|
899
|
+
variant: {
|
|
900
|
+
name: variantPack.name,
|
|
901
|
+
displayName: variantPack.displayName,
|
|
902
|
+
version: variantPack.version,
|
|
903
|
+
from: packageName
|
|
904
|
+
},
|
|
905
|
+
linked: variantPack.linked,
|
|
906
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
907
|
+
};
|
|
908
|
+
await writeFileSafe(
|
|
909
|
+
path9.join(projectRoot, ".teamix-evo", "design", "pack.lock.json"),
|
|
910
|
+
JSON.stringify(lock, null, 2) + "\n"
|
|
911
|
+
);
|
|
912
|
+
const config = {
|
|
913
|
+
$schema: "https://teamix-evo.dev/schema/config/v1.json",
|
|
914
|
+
schemaVersion: 1,
|
|
915
|
+
ide,
|
|
916
|
+
packages: {
|
|
917
|
+
design: {
|
|
918
|
+
variant,
|
|
919
|
+
version: variantPack.version,
|
|
920
|
+
tailwind: "v4"
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
};
|
|
924
|
+
await writeProjectConfig(projectRoot, config);
|
|
925
|
+
const installedManifest = {
|
|
926
|
+
schemaVersion: 1,
|
|
927
|
+
installed: [
|
|
928
|
+
{
|
|
929
|
+
package: packageName,
|
|
930
|
+
variant,
|
|
931
|
+
version: variantPack.version,
|
|
932
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
933
|
+
resources: installed
|
|
934
|
+
}
|
|
935
|
+
]
|
|
936
|
+
};
|
|
937
|
+
await writeInstalledManifest(projectRoot, installedManifest);
|
|
938
|
+
const skills = await tryAutoInstallVariantSkills({
|
|
939
|
+
projectRoot,
|
|
940
|
+
variant,
|
|
941
|
+
ide
|
|
942
|
+
});
|
|
943
|
+
return {
|
|
944
|
+
status: "installed",
|
|
945
|
+
packageName,
|
|
946
|
+
variant,
|
|
947
|
+
version: variantPack.version,
|
|
948
|
+
count: installed.length,
|
|
949
|
+
resources: installed,
|
|
950
|
+
merge: {
|
|
951
|
+
overrides: merge.overrides,
|
|
952
|
+
variantAdds: merge.variantAdds,
|
|
953
|
+
defaultPassThrough: merge.defaultPassThrough
|
|
954
|
+
},
|
|
955
|
+
skills
|
|
956
|
+
};
|
|
638
957
|
}
|
|
639
|
-
async function
|
|
640
|
-
const
|
|
641
|
-
const
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
958
|
+
async function tryAutoInstallVariantSkills(args) {
|
|
959
|
+
const { projectRoot, variant, ide } = args;
|
|
960
|
+
const variantSkillId = `${BASELINE_DESIGN_RULES_SKILL}-${variant}`;
|
|
961
|
+
const desired = [BASELINE_DESIGN_RULES_SKILL, variantSkillId];
|
|
962
|
+
let manifestSkillIds;
|
|
963
|
+
try {
|
|
964
|
+
const { manifest } = await loadSkillsData(DEFAULT_SKILLS_PACKAGE2);
|
|
965
|
+
manifestSkillIds = new Set(manifest.skills.map((s) => s.id));
|
|
966
|
+
} catch (err) {
|
|
967
|
+
logger.warn(
|
|
968
|
+
`Skipping skills auto-install: could not load skills manifest (${err.message}).`
|
|
969
|
+
);
|
|
970
|
+
return {
|
|
971
|
+
attempted: [],
|
|
972
|
+
addedSkillIds: [],
|
|
973
|
+
skippedSkillIds: [],
|
|
974
|
+
missing: desired
|
|
975
|
+
};
|
|
976
|
+
}
|
|
977
|
+
const present = desired.filter((id) => manifestSkillIds.has(id));
|
|
978
|
+
const missing = desired.filter((id) => !manifestSkillIds.has(id));
|
|
979
|
+
if (missing.length > 0) {
|
|
980
|
+
logger.warn(
|
|
981
|
+
`Skills auto-install: not found in manifest, skipping: ${missing.join(", ")}.`
|
|
982
|
+
);
|
|
983
|
+
}
|
|
984
|
+
if (present.length === 0) {
|
|
985
|
+
return {
|
|
986
|
+
attempted: desired,
|
|
987
|
+
addedSkillIds: [],
|
|
988
|
+
skippedSkillIds: [],
|
|
989
|
+
missing
|
|
990
|
+
};
|
|
991
|
+
}
|
|
992
|
+
try {
|
|
993
|
+
const result = await runSkillsAdd({
|
|
994
|
+
projectRoot,
|
|
995
|
+
names: present,
|
|
996
|
+
ides: DEFAULT_AUTO_SKILL_IDES,
|
|
997
|
+
scope: DEFAULT_AUTO_SKILL_SCOPE,
|
|
998
|
+
ide
|
|
999
|
+
});
|
|
1000
|
+
if (result.status !== "installed") {
|
|
1001
|
+
return {
|
|
1002
|
+
attempted: desired,
|
|
1003
|
+
addedSkillIds: [],
|
|
1004
|
+
skippedSkillIds: present,
|
|
1005
|
+
missing
|
|
1006
|
+
};
|
|
648
1007
|
}
|
|
1008
|
+
return {
|
|
1009
|
+
attempted: desired,
|
|
1010
|
+
addedSkillIds: result.addedSkillIds,
|
|
1011
|
+
skippedSkillIds: result.skippedSkillIds,
|
|
1012
|
+
missing
|
|
1013
|
+
};
|
|
1014
|
+
} catch (err) {
|
|
1015
|
+
logger.warn(
|
|
1016
|
+
`Skills auto-install failed (continuing): ${err.message}`
|
|
1017
|
+
);
|
|
1018
|
+
return {
|
|
1019
|
+
attempted: desired,
|
|
1020
|
+
addedSkillIds: [],
|
|
1021
|
+
skippedSkillIds: [],
|
|
1022
|
+
missing
|
|
1023
|
+
};
|
|
649
1024
|
}
|
|
650
|
-
return files;
|
|
651
1025
|
}
|
|
652
|
-
function
|
|
653
|
-
|
|
1026
|
+
async function installPackFile(file, projectRoot) {
|
|
1027
|
+
const cls = classifyPackFile(file.relPath);
|
|
1028
|
+
if (cls === null) return null;
|
|
1029
|
+
const targetAbs = path9.join(projectRoot, cls.target);
|
|
1030
|
+
if (cls.isFrozen && await fileExists(targetAbs)) {
|
|
1031
|
+
const existing = await fs6.readFile(targetAbs, "utf-8");
|
|
1032
|
+
return {
|
|
1033
|
+
id: `pack:${file.relPath}`,
|
|
1034
|
+
target: cls.target,
|
|
1035
|
+
hash: computeHash(existing),
|
|
1036
|
+
strategy: cls.strategy
|
|
1037
|
+
};
|
|
1038
|
+
}
|
|
1039
|
+
const content = await fs6.readFile(file.sourcePath, "utf-8");
|
|
1040
|
+
await writeFileSafe(targetAbs, content);
|
|
1041
|
+
return {
|
|
1042
|
+
id: `pack:${file.relPath}`,
|
|
1043
|
+
target: cls.target,
|
|
1044
|
+
hash: computeHash(content),
|
|
1045
|
+
strategy: cls.strategy
|
|
1046
|
+
};
|
|
1047
|
+
}
|
|
1048
|
+
function resolveDesignPackageRoot(packageName) {
|
|
1049
|
+
const pkgJson = require3.resolve(`${packageName}/package.json`);
|
|
1050
|
+
return path9.dirname(pkgJson);
|
|
1051
|
+
}
|
|
1052
|
+
async function listDesignVariants(packageName = DEFAULT_DESIGN_PACKAGE, packageRoot) {
|
|
1053
|
+
const root = packageRoot ?? resolveDesignPackageRoot(packageName);
|
|
1054
|
+
const catalog = await loadDesignPackageManifest(root);
|
|
1055
|
+
return {
|
|
1056
|
+
packageName,
|
|
1057
|
+
defaultDescription: catalog.default.description,
|
|
1058
|
+
variants: catalog.variants.map((v) => ({
|
|
1059
|
+
name: v.name,
|
|
1060
|
+
displayName: v.displayName,
|
|
1061
|
+
version: v.version,
|
|
1062
|
+
description: v.description,
|
|
1063
|
+
linked: v.linked
|
|
1064
|
+
}))
|
|
1065
|
+
};
|
|
654
1066
|
}
|
|
655
1067
|
|
|
656
|
-
// src/commands/design/
|
|
657
|
-
|
|
658
|
-
var DESIGN_PACKAGE2 = "@teamix-evo/design";
|
|
659
|
-
var updateCommand = new Command2("update").description("\u66F4\u65B0\u8BBE\u8BA1\u4F53\u7CFB\u8D44\u6E90").action(async () => {
|
|
1068
|
+
// src/commands/design/init.ts
|
|
1069
|
+
var initCommand = new Command("init").description("\u521D\u59CB\u5316\u8BBE\u8BA1\u4F53\u7CFB\u8D44\u6E90\uFF08\u5FC5\u987B\u663E\u5F0F\u6307\u5B9A\u4E1A\u52A1\u53D8\u4F53\uFF09").argument("<variant>", '\u4E1A\u52A1\u53D8\u4F53\u540D\u79F0\uFF08\u5982 "opentrek"\u3001"uni-manager"\uFF09').action(async (variant) => {
|
|
660
1070
|
try {
|
|
661
1071
|
const ide = detectIde();
|
|
662
1072
|
const projectRoot = ide.getProjectRoot();
|
|
663
|
-
logger.info(
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
'No installed manifest found. Try re-initializing with "teamix-evo design init".'
|
|
1073
|
+
logger.info(`Initializing design system: variant="${variant}"`);
|
|
1074
|
+
logger.debug(`Project root: ${projectRoot}`);
|
|
1075
|
+
logger.debug(`IDE: ${ide.name}`);
|
|
1076
|
+
logger.info(`Loading variant "${variant}"...`);
|
|
1077
|
+
logger.info("Installing resources...");
|
|
1078
|
+
const result = await runDesignInit({
|
|
1079
|
+
projectRoot,
|
|
1080
|
+
variant,
|
|
1081
|
+
ide: ide.name
|
|
1082
|
+
});
|
|
1083
|
+
if (result.status === "already-initialized") {
|
|
1084
|
+
logger.warn(
|
|
1085
|
+
`Design system already initialized (variant: ${result.existingVariant}). Use "teamix-evo design update" to update.`
|
|
677
1086
|
);
|
|
678
|
-
process.exitCode = 1;
|
|
679
1087
|
return;
|
|
680
1088
|
}
|
|
681
|
-
logger.
|
|
682
|
-
|
|
683
|
-
logger.info(
|
|
684
|
-
`Current: v${currentVersion} \u2192 Available: v${manifest.version}`
|
|
1089
|
+
logger.success(
|
|
1090
|
+
`Design system initialized: ${result.packageName} (${result.variant} v${result.version})`
|
|
685
1091
|
);
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
packageRoot,
|
|
692
|
-
installedManifest,
|
|
693
|
-
packageName: DESIGN_PACKAGE2
|
|
694
|
-
});
|
|
695
|
-
config.packages.design.version = manifest.version;
|
|
696
|
-
await writeProjectConfig(projectRoot, config);
|
|
697
|
-
const updatedManifest = { ...installedManifest };
|
|
698
|
-
const pkgIdx = updatedManifest.installed.findIndex(
|
|
699
|
-
(p) => p.package === DESIGN_PACKAGE2 && p.variant === variant
|
|
1092
|
+
logger.info(` Variant: ${result.variant}`);
|
|
1093
|
+
logger.info(` Tailwind: v4`);
|
|
1094
|
+
logger.info(` Resources: ${result.count} files installed`);
|
|
1095
|
+
logger.info(
|
|
1096
|
+
` Merge: ${result.merge.overrides.length} overrides / ${result.merge.variantAdds.length} variant-only / ${result.merge.defaultPassThrough.length} default-passthrough`
|
|
700
1097
|
);
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
1098
|
+
if (result.skills) {
|
|
1099
|
+
const { addedSkillIds, skippedSkillIds, missing } = result.skills;
|
|
1100
|
+
if (addedSkillIds.length > 0) {
|
|
1101
|
+
logger.info(
|
|
1102
|
+
` Skills: auto-installed ${addedSkillIds.join(", ")}`
|
|
1103
|
+
);
|
|
1104
|
+
}
|
|
1105
|
+
if (skippedSkillIds.length > 0) {
|
|
1106
|
+
logger.info(
|
|
1107
|
+
` Skills: already installed (skipped) ${skippedSkillIds.join(", ")}`
|
|
1108
|
+
);
|
|
1109
|
+
}
|
|
1110
|
+
if (missing.length > 0) {
|
|
1111
|
+
logger.info(
|
|
1112
|
+
` Skills: not in manifest (skipped) ${missing.join(", ")}`
|
|
1113
|
+
);
|
|
1114
|
+
}
|
|
712
1115
|
}
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
logger.
|
|
716
|
-
|
|
1116
|
+
logger.info("");
|
|
1117
|
+
logger.info('Run "teamix-evo design update" to update resources later.');
|
|
1118
|
+
logger.info(
|
|
1119
|
+
'Run "teamix-evo design list-variants" to see all available variants.'
|
|
717
1120
|
);
|
|
718
|
-
logger.info(` Created: ${summary.created}`);
|
|
719
|
-
logger.info(` Overwritten: ${summary.overwritten}`);
|
|
720
|
-
logger.info(` Managed: ${summary.managed}`);
|
|
721
|
-
logger.info(` Skipped: ${summary.skipped}`);
|
|
722
1121
|
} catch (err) {
|
|
723
|
-
logger.error(`Failed to
|
|
1122
|
+
logger.error(`Failed to initialize: ${err.message}`);
|
|
724
1123
|
logger.debug(err.stack ?? "");
|
|
725
1124
|
process.exitCode = 1;
|
|
726
1125
|
}
|
|
727
1126
|
});
|
|
728
1127
|
|
|
1128
|
+
// src/commands/design/update.ts
|
|
1129
|
+
import { Command as Command2 } from "commander";
|
|
1130
|
+
var updateCommand = new Command2("update").description("(v0.7) \u66F4\u65B0\u8BBE\u8BA1\u4F53\u7CFB\u8D44\u6E90 \u2014 \u5F53\u524D\u672A\u5B9E\u73B0,\u89C1 ADR 0010 / PLAN \xA712.6").action(() => {
|
|
1131
|
+
logger.warn(
|
|
1132
|
+
"design update is not yet implemented for the design pack model (default + variants/, ADR 0010)."
|
|
1133
|
+
);
|
|
1134
|
+
logger.info(
|
|
1135
|
+
"Workaround: clean `.teamix-evo/design/` and re-run `teamix-evo design init <variant>`."
|
|
1136
|
+
);
|
|
1137
|
+
logger.info(
|
|
1138
|
+
"Tracking: PLAN \xA712.6 v0.7 \u2014 semantic upgrade flow + tokens.overrides.css preservation."
|
|
1139
|
+
);
|
|
1140
|
+
process.exitCode = 0;
|
|
1141
|
+
});
|
|
1142
|
+
|
|
729
1143
|
// src/commands/design/list.ts
|
|
730
1144
|
import { Command as Command3 } from "commander";
|
|
731
|
-
init_logger();
|
|
732
1145
|
var listCommand = new Command3("list").description("\u5217\u51FA\u5DF2\u5B89\u88C5\u7684\u8BBE\u8BA1\u53D8\u4F53").action(async () => {
|
|
733
1146
|
try {
|
|
734
1147
|
const ide = detectIde();
|
|
@@ -739,11 +1152,11 @@ var listCommand = new Command3("list").description("\u5217\u51FA\u5DF2\u5B89\u88
|
|
|
739
1152
|
logger.info('Run "teamix-evo design init [variant]" to get started.');
|
|
740
1153
|
return;
|
|
741
1154
|
}
|
|
742
|
-
const { variant, version } = config.packages.design;
|
|
1155
|
+
const { variant, version: version2 } = config.packages.design;
|
|
743
1156
|
logger.info("Installed design system:");
|
|
744
1157
|
logger.info(` Package: @teamix-evo/design`);
|
|
745
1158
|
logger.info(` Variant: ${variant}`);
|
|
746
|
-
logger.info(` Version: ${
|
|
1159
|
+
logger.info(` Version: ${version2}`);
|
|
747
1160
|
logger.info(` IDE: ${config.ide}`);
|
|
748
1161
|
const manifest = await readInstalledManifest(projectRoot);
|
|
749
1162
|
if (manifest) {
|
|
@@ -763,15 +1176,1631 @@ var listCommand = new Command3("list").description("\u5217\u51FA\u5DF2\u5B89\u88
|
|
|
763
1176
|
}
|
|
764
1177
|
});
|
|
765
1178
|
|
|
766
|
-
// src/commands/design/
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
1179
|
+
// src/commands/design/list-variants.ts
|
|
1180
|
+
import { Command as Command4 } from "commander";
|
|
1181
|
+
var listVariantsCommand = new Command4("list-variants").description("\u5217\u51FA @teamix-evo/design \u5305\u5185\u63D0\u4F9B\u7684\u6240\u6709\u53D8\u4F53").action(async () => {
|
|
1182
|
+
try {
|
|
1183
|
+
const result = await listDesignVariants();
|
|
1184
|
+
logger.info(`Available design variants in ${result.packageName}:`);
|
|
1185
|
+
logger.info("");
|
|
1186
|
+
logger.info(" default \u2014 B \u7AEF\u901A\u7528\u57FA\u7EBF(\u59CB\u7EC8\u5185\u7F6E,\u65E0\u9700\u9009\u62E9)");
|
|
1187
|
+
if (result.defaultDescription) {
|
|
1188
|
+
logger.info(` ${result.defaultDescription}`);
|
|
1189
|
+
}
|
|
1190
|
+
logger.info("");
|
|
1191
|
+
if (result.variants.length === 0) {
|
|
1192
|
+
logger.info(" (no variants beyond default)");
|
|
1193
|
+
return;
|
|
1194
|
+
}
|
|
1195
|
+
for (const v of result.variants) {
|
|
1196
|
+
logger.info(` ${v.name} (${v.displayName}) \u2014 v${v.version}`);
|
|
1197
|
+
if (v.description) logger.info(` ${v.description}`);
|
|
1198
|
+
if (v.linked) {
|
|
1199
|
+
const links = [];
|
|
1200
|
+
if (v.linked["biz-ui"]) links.push(`biz-ui: ${v.linked["biz-ui"]}`);
|
|
1201
|
+
if (v.linked.templates) links.push(`templates: ${v.linked.templates}`);
|
|
1202
|
+
if (links.length) logger.info(` linked: ${links.join(" / ")}`);
|
|
1203
|
+
}
|
|
1204
|
+
logger.info("");
|
|
1205
|
+
}
|
|
1206
|
+
logger.info("Install a variant: teamix-evo design init <name>");
|
|
1207
|
+
} catch (err) {
|
|
1208
|
+
logger.error(`Failed to list variants: ${err.message}`);
|
|
1209
|
+
logger.debug(err.stack ?? "");
|
|
1210
|
+
process.exitCode = 1;
|
|
1211
|
+
}
|
|
1212
|
+
});
|
|
1213
|
+
|
|
1214
|
+
// src/commands/design/uninstall.ts
|
|
1215
|
+
import { Command as Command5 } from "commander";
|
|
1216
|
+
import * as fs7 from "fs/promises";
|
|
1217
|
+
import * as path10 from "path";
|
|
1218
|
+
import * as prompts from "@clack/prompts";
|
|
1219
|
+
var DESIGN_PACKAGE = "@teamix-evo/design";
|
|
1220
|
+
var uninstallCommand = new Command5("uninstall").description("\u5378\u8F7D\u8BBE\u8BA1\u4F53\u7CFB\u8D44\u6E90\uFF08\u9ED8\u8BA4\u4F1A\u5220\u9664 frozen / regenerable \u8D44\u6E90\u6587\u4EF6\uFF09").option("-y, --yes", "\u8DF3\u8FC7\u786E\u8BA4").option(
|
|
1221
|
+
"--keep-files",
|
|
1222
|
+
"\u4EC5\u6E05\u7406 .teamix-evo \u4E2D\u7684\u8BB0\u8D26\u4FE1\u606F\uFF0C\u4E0D\u5220\u9664\u5DF2\u843D\u5730\u8D44\u6E90\u6587\u4EF6"
|
|
1223
|
+
).action(async (opts) => {
|
|
1224
|
+
try {
|
|
1225
|
+
const ide = detectIde();
|
|
1226
|
+
const projectRoot = ide.getProjectRoot();
|
|
1227
|
+
const config = await readProjectConfig(projectRoot);
|
|
1228
|
+
if (!config?.packages?.design) {
|
|
1229
|
+
logger.info("Design system is not installed. Nothing to do.");
|
|
1230
|
+
return;
|
|
1231
|
+
}
|
|
1232
|
+
const installedManifest = await readInstalledManifest(projectRoot);
|
|
1233
|
+
const pkg = installedManifest?.installed.find(
|
|
1234
|
+
(p) => p.package === DESIGN_PACKAGE
|
|
1235
|
+
);
|
|
1236
|
+
const resources = pkg?.resources ?? [];
|
|
1237
|
+
const removable = opts.keepFiles ? [] : resources.filter((r) => r.strategy !== "managed");
|
|
1238
|
+
const kept = resources.length - removable.length;
|
|
1239
|
+
logger.info(
|
|
1240
|
+
`Will remove ${removable.length} file(s); keep ${kept} managed file(s).`
|
|
1241
|
+
);
|
|
1242
|
+
if (!opts.yes) {
|
|
1243
|
+
const confirm4 = await prompts.confirm({
|
|
1244
|
+
message: "\u786E\u8BA4\u5378\u8F7D\u8BBE\u8BA1\u4F53\u7CFB\uFF1F",
|
|
1245
|
+
initialValue: false
|
|
1246
|
+
});
|
|
1247
|
+
if (prompts.isCancel(confirm4) || !confirm4) {
|
|
1248
|
+
logger.info("Cancelled.");
|
|
1249
|
+
return;
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
let removed = 0;
|
|
1253
|
+
for (const r of removable) {
|
|
1254
|
+
const target = path10.isAbsolute(r.target) ? r.target : path10.join(projectRoot, r.target);
|
|
1255
|
+
try {
|
|
1256
|
+
await fs7.unlink(target);
|
|
1257
|
+
removed++;
|
|
1258
|
+
} catch (err) {
|
|
1259
|
+
if (err.code !== "ENOENT") {
|
|
1260
|
+
logger.warn(
|
|
1261
|
+
`Failed to remove ${target}: ${err.message}`
|
|
1262
|
+
);
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
if (installedManifest) {
|
|
1267
|
+
installedManifest.installed = installedManifest.installed.filter(
|
|
1268
|
+
(p) => p.package !== DESIGN_PACKAGE
|
|
1269
|
+
);
|
|
1270
|
+
await writeInstalledManifest(projectRoot, installedManifest);
|
|
1271
|
+
}
|
|
1272
|
+
delete config.packages.design;
|
|
1273
|
+
await writeProjectConfig(projectRoot, config);
|
|
1274
|
+
logger.success(`Uninstalled ${DESIGN_PACKAGE}`);
|
|
1275
|
+
logger.info(` Removed: ${removed} files`);
|
|
1276
|
+
if (kept > 0) {
|
|
1277
|
+
logger.info(
|
|
1278
|
+
` Kept: ${kept} managed files (you may delete manually)`
|
|
1279
|
+
);
|
|
1280
|
+
}
|
|
1281
|
+
} catch (err) {
|
|
1282
|
+
logger.error(`Failed to uninstall: ${err.message}`);
|
|
1283
|
+
logger.debug(err.stack ?? "");
|
|
1284
|
+
process.exitCode = 1;
|
|
1285
|
+
}
|
|
1286
|
+
});
|
|
1287
|
+
|
|
1288
|
+
// src/commands/design/index.ts
|
|
1289
|
+
var designCommand = new Command6("design").description(
|
|
1290
|
+
"\u7BA1\u7406\u8BBE\u8BA1\u4F53\u7CFB\u8D44\u6E90"
|
|
1291
|
+
);
|
|
1292
|
+
designCommand.addCommand(initCommand);
|
|
1293
|
+
designCommand.addCommand(updateCommand);
|
|
1294
|
+
designCommand.addCommand(listCommand);
|
|
1295
|
+
designCommand.addCommand(listVariantsCommand);
|
|
1296
|
+
designCommand.addCommand(uninstallCommand);
|
|
1297
|
+
|
|
1298
|
+
// src/commands/skills/index.ts
|
|
1299
|
+
import { Command as Command13 } from "commander";
|
|
1300
|
+
|
|
1301
|
+
// src/commands/skills/add.ts
|
|
1302
|
+
import { Command as Command7 } from "commander";
|
|
1303
|
+
import * as prompts2 from "@clack/prompts";
|
|
1304
|
+
|
|
1305
|
+
// src/utils/global-root.ts
|
|
1306
|
+
import { existsSync } from "fs";
|
|
1307
|
+
import * as fs8 from "fs/promises";
|
|
1308
|
+
import * as os3 from "os";
|
|
1309
|
+
import * as path11 from "path";
|
|
1310
|
+
var GLOBAL_META_DIR = ".teamix-evo-global";
|
|
1311
|
+
var TEAMIX_DIR3 = ".teamix-evo";
|
|
1312
|
+
var CONFIG_FILE2 = "config.json";
|
|
1313
|
+
function getGlobalMetaRoot() {
|
|
1314
|
+
return path11.join(os3.homedir(), GLOBAL_META_DIR);
|
|
1315
|
+
}
|
|
1316
|
+
function isTeamixEvoProject(dir) {
|
|
1317
|
+
return existsSync(path11.join(dir, TEAMIX_DIR3, CONFIG_FILE2));
|
|
1318
|
+
}
|
|
1319
|
+
async function ensureGlobalMetaRoot() {
|
|
1320
|
+
const root = getGlobalMetaRoot();
|
|
1321
|
+
await fs8.mkdir(root, { recursive: true });
|
|
1322
|
+
return root;
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
// src/commands/skills/add.ts
|
|
1326
|
+
var addCommand = new Command7("add").description(
|
|
1327
|
+
"\u5411\u9879\u76EE\uFF08\u6216\u5168\u5C40 IDE \u914D\u7F6E\uFF09\u6DFB\u52A0 teamix-evo skills\uFF1B\u4E0D\u4F20 names \u5219\u6DFB\u52A0 manifest \u5185\u5168\u90E8 skill"
|
|
1328
|
+
).argument(
|
|
1329
|
+
"[names...]",
|
|
1330
|
+
"\u53EF\u9009\uFF1A\u4EC5\u6DFB\u52A0\u6307\u5B9A skill id\uFF08\u589E\u91CF\u6A21\u5F0F\uFF09\uFF1B\u7701\u7565\u5219\u6DFB\u52A0\u5168\u90E8"
|
|
1331
|
+
).option("--ide <list>", '\u9017\u53F7\u5206\u9694\u7684 IDE \u5217\u8868\uFF0C\u5982 "qoder,claude"').option(
|
|
1332
|
+
"--scope <scope>",
|
|
1333
|
+
"project | global\uFF08\u9ED8\u8BA4 project\uFF1B\u589E\u91CF\u6A21\u5F0F\u4E0B\u9ED8\u8BA4\u590D\u7528\u5DF2\u6709\u914D\u7F6E\uFF09"
|
|
1334
|
+
).option("-y, --yes", "\u4F7F\u7528\u9ED8\u8BA4\u503C\uFF0C\u8DF3\u8FC7\u4EA4\u4E92").action(async (names, opts) => {
|
|
1335
|
+
try {
|
|
1336
|
+
const ide = detectIde();
|
|
1337
|
+
const cwd = ide.getProjectRoot();
|
|
1338
|
+
const isIncremental = names.length > 0;
|
|
1339
|
+
const { ides, scope } = await resolveIdesAndScope({
|
|
1340
|
+
opts,
|
|
1341
|
+
projectRoot: cwd,
|
|
1342
|
+
isIncremental
|
|
1343
|
+
});
|
|
1344
|
+
let projectRoot = cwd;
|
|
1345
|
+
if (scope === "global" && !isTeamixEvoProject(cwd)) {
|
|
1346
|
+
projectRoot = await ensureGlobalMetaRoot();
|
|
1347
|
+
logger.info(`Global skill install \u2014 meta root: ${projectRoot}`);
|
|
1348
|
+
}
|
|
1349
|
+
logger.info(
|
|
1350
|
+
isIncremental ? `Adding skills [${names.join(",")}]: ides=[${ides.join(
|
|
1351
|
+
","
|
|
1352
|
+
)}], scope="${scope}"` : `Adding skills (all): ides=[${ides.join(",")}], scope="${scope}"`
|
|
1353
|
+
);
|
|
1354
|
+
logger.debug(`Project root: ${projectRoot}`);
|
|
1355
|
+
const result = await runSkillsAdd({
|
|
1356
|
+
projectRoot,
|
|
1357
|
+
ides,
|
|
1358
|
+
scope,
|
|
1359
|
+
ide: ide.name,
|
|
1360
|
+
names: isIncremental ? names : void 0
|
|
1361
|
+
});
|
|
1362
|
+
if (result.status === "already-added") {
|
|
1363
|
+
logger.warn(
|
|
1364
|
+
`Skills already added. Use "teamix-evo skills add <name>" to add specific skills, "teamix-evo skills update" to refresh, or "teamix-evo skills uninstall" to remove.`
|
|
1365
|
+
);
|
|
1366
|
+
return;
|
|
1367
|
+
}
|
|
1368
|
+
if (result.addedSkillIds.length === 0 && result.skippedSkillIds.length > 0) {
|
|
1369
|
+
logger.warn(
|
|
1370
|
+
`\u5DF2\u5B58\u5728\uFF0C\u65E0\u9700\u6DFB\u52A0\uFF1A${result.skippedSkillIds.join(
|
|
1371
|
+
", "
|
|
1372
|
+
)}\u3002\u5982\u9700\u5237\u65B0\u5185\u5BB9\u8BF7\u8FD0\u884C "teamix-evo skills update"\u3002`
|
|
1373
|
+
);
|
|
1374
|
+
return;
|
|
1375
|
+
}
|
|
1376
|
+
logger.success(`Skills added: ${result.skillCount} skill(s)`);
|
|
1377
|
+
logger.info(` IDEs: ${result.ides.join(", ")}`);
|
|
1378
|
+
logger.info(` Scope: ${result.scope}`);
|
|
1379
|
+
if (result.addedSkillIds.length > 0) {
|
|
1380
|
+
logger.info(` Added: ${result.addedSkillIds.join(", ")}`);
|
|
1381
|
+
}
|
|
1382
|
+
if (result.skippedSkillIds.length > 0) {
|
|
1383
|
+
logger.info(
|
|
1384
|
+
` Skipped: ${result.skippedSkillIds.join(", ")} (already added)`
|
|
1385
|
+
);
|
|
1386
|
+
}
|
|
1387
|
+
logger.info(` Files: ${result.fileCount}`);
|
|
1388
|
+
logger.info("");
|
|
1389
|
+
logger.info('Run "teamix-evo skills list" to see installed skills.');
|
|
1390
|
+
} catch (err) {
|
|
1391
|
+
logger.error(`Failed to add skills: ${err.message}`);
|
|
1392
|
+
logger.debug(err.stack ?? "");
|
|
1393
|
+
process.exitCode = 1;
|
|
1394
|
+
}
|
|
1395
|
+
});
|
|
1396
|
+
async function resolveIdesAndScope(args) {
|
|
1397
|
+
const { opts, projectRoot, isIncremental } = args;
|
|
1398
|
+
if (isIncremental && !opts.ide && !opts.scope && !opts.yes) {
|
|
1399
|
+
const existing = await readProjectConfig(projectRoot);
|
|
1400
|
+
const cfg = existing?.packages?.skills;
|
|
1401
|
+
if (cfg && cfg.ides && cfg.ides.length > 0 && cfg.scope) {
|
|
1402
|
+
logger.debug(
|
|
1403
|
+
`Reusing existing skills config: ides=[${cfg.ides.join(",")}], scope="${cfg.scope}"`
|
|
1404
|
+
);
|
|
1405
|
+
return {
|
|
1406
|
+
ides: [...cfg.ides],
|
|
1407
|
+
scope: cfg.scope
|
|
1408
|
+
};
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
if (opts.ide || opts.yes) {
|
|
1412
|
+
const ides = opts.ide ? parseIdeList(opts.ide) : [...ALL_IDE_KINDS];
|
|
1413
|
+
const scope = parseScope(opts.scope);
|
|
1414
|
+
if (ides.length === 0) {
|
|
1415
|
+
throw new Error("At least one IDE must be selected.");
|
|
1416
|
+
}
|
|
1417
|
+
return { ides, scope };
|
|
1418
|
+
}
|
|
1419
|
+
const idesAns = await prompts2.multiselect({
|
|
1420
|
+
message: "\u9009\u62E9\u8981\u6CE8\u5165\u6280\u80FD\u7684 AI IDE\uFF08\u81F3\u5C11\u4E00\u4E2A\uFF09",
|
|
1421
|
+
options: ALL_IDE_KINDS.map((k) => ({
|
|
1422
|
+
value: k,
|
|
1423
|
+
label: k === "qoder" ? "Qoder" : "Claude Code"
|
|
1424
|
+
})),
|
|
1425
|
+
initialValues: [...ALL_IDE_KINDS],
|
|
1426
|
+
required: true
|
|
1427
|
+
});
|
|
1428
|
+
if (prompts2.isCancel(idesAns)) {
|
|
1429
|
+
throw new Error("Cancelled by user.");
|
|
1430
|
+
}
|
|
1431
|
+
const scopeAns = await prompts2.select({
|
|
1432
|
+
message: "\u5B89\u88C5\u8303\u56F4\uFF1F",
|
|
1433
|
+
options: [
|
|
1434
|
+
{ value: "project", label: "\u9879\u76EE\u7EA7\uFF08.qoder/.claude \u5728\u5F53\u524D\u9879\u76EE\uFF09" },
|
|
1435
|
+
{ value: "global", label: "\u5168\u5C40\uFF08~/.qoder/~/.claude\uFF09" }
|
|
1436
|
+
],
|
|
1437
|
+
initialValue: "project"
|
|
1438
|
+
});
|
|
1439
|
+
if (prompts2.isCancel(scopeAns)) {
|
|
1440
|
+
throw new Error("Cancelled by user.");
|
|
1441
|
+
}
|
|
1442
|
+
return { ides: idesAns, scope: scopeAns };
|
|
1443
|
+
}
|
|
1444
|
+
function parseIdeList(input) {
|
|
1445
|
+
const parts = input.split(",").map((s) => s.trim().toLowerCase()).filter(Boolean);
|
|
1446
|
+
const result = [];
|
|
1447
|
+
for (const p of parts) {
|
|
1448
|
+
if (p === "qoder" || p === "claude") {
|
|
1449
|
+
if (!result.includes(p)) result.push(p);
|
|
1450
|
+
} else {
|
|
1451
|
+
throw new Error(`Unknown IDE: "${p}". Expected qoder | claude.`);
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
return result;
|
|
1455
|
+
}
|
|
1456
|
+
function parseScope(input) {
|
|
1457
|
+
const v = (input ?? "project").toLowerCase();
|
|
1458
|
+
if (v === "project" || v === "global") return v;
|
|
1459
|
+
throw new Error(`Invalid --scope: "${input}". Expected project | global.`);
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
// src/commands/skills/list.ts
|
|
1463
|
+
import { Command as Command8 } from "commander";
|
|
1464
|
+
var SKILLS_PACKAGE = "@teamix-evo/skills";
|
|
1465
|
+
var listCommand2 = new Command8("list").alias("ls").description(
|
|
1466
|
+
"\u5217\u51FA teamix-evo skills\uFF08\u9ED8\u8BA4\u5C55\u793A\u5168\u90E8 skill \u5E76\u6807\u6CE8\u5DF2\u88C5/\u672A\u88C5\uFF1B--installed \u4EC5\u770B\u5DF2\u88C5\uFF09"
|
|
1467
|
+
).option("--installed", "\u4EC5\u5C55\u793A\u5DF2\u5B89\u88C5\u7684 skill\uFF08\u9690\u85CF\u672A\u5B89\u88C5\u9879\uFF09").action(async (opts) => {
|
|
1468
|
+
try {
|
|
1469
|
+
const ide = detectIde();
|
|
1470
|
+
const projectRoot = ide.getProjectRoot();
|
|
1471
|
+
const config = await readProjectConfig(projectRoot);
|
|
1472
|
+
const installedManifest = await readInstalledManifest(projectRoot);
|
|
1473
|
+
const pkg = installedManifest?.installed.find(
|
|
1474
|
+
(p) => p.package === SKILLS_PACKAGE
|
|
1475
|
+
);
|
|
1476
|
+
const installedBySkill = /* @__PURE__ */ new Map();
|
|
1477
|
+
for (const r of pkg?.resources ?? []) {
|
|
1478
|
+
const skillId = r.id.split(":")[0];
|
|
1479
|
+
installedBySkill.set(skillId, (installedBySkill.get(skillId) ?? 0) + 1);
|
|
1480
|
+
}
|
|
1481
|
+
if (opts.installed) {
|
|
1482
|
+
if (!config?.packages?.skills || !pkg) {
|
|
1483
|
+
logger.info("No skills installed.");
|
|
1484
|
+
logger.info('Run "teamix-evo skills add" to get started.');
|
|
1485
|
+
return;
|
|
1486
|
+
}
|
|
1487
|
+
printInstalledHeader(config.packages.skills, pkg.installedAt);
|
|
1488
|
+
logger.info("");
|
|
1489
|
+
logger.info("Installed skills:");
|
|
1490
|
+
for (const [skillId, count] of installedBySkill) {
|
|
1491
|
+
logger.info(` \u2713 ${skillId} (${count} file${count > 1 ? "s" : ""})`);
|
|
1492
|
+
}
|
|
1493
|
+
logger.info("");
|
|
1494
|
+
logger.info(
|
|
1495
|
+
` Total: ${pkg.resources.length} files (${installedBySkill.size} skills \xD7 ides \xD7 scope)`
|
|
1496
|
+
);
|
|
1497
|
+
return;
|
|
1498
|
+
}
|
|
1499
|
+
const { manifest } = await loadSkillsData(SKILLS_PACKAGE);
|
|
1500
|
+
const skills = [...manifest.skills].sort(
|
|
1501
|
+
(a, b) => a.id.localeCompare(b.id)
|
|
1502
|
+
);
|
|
1503
|
+
const isInstalled = !!config?.packages?.skills && !!pkg;
|
|
1504
|
+
if (isInstalled) {
|
|
1505
|
+
printInstalledHeader(config.packages.skills, pkg.installedAt);
|
|
1506
|
+
} else {
|
|
1507
|
+
logger.info("Skills package not yet added.");
|
|
1508
|
+
logger.info(
|
|
1509
|
+
'Run "teamix-evo skills add" to add all, or "teamix-evo skills add <id>" for specific skills.'
|
|
1510
|
+
);
|
|
1511
|
+
}
|
|
1512
|
+
logger.info("");
|
|
1513
|
+
logger.info(`Available skills (${SKILLS_PACKAGE}@${manifest.version}):`);
|
|
1514
|
+
let installedCount = 0;
|
|
1515
|
+
for (const s of skills) {
|
|
1516
|
+
const fileCount = installedBySkill.get(s.id);
|
|
1517
|
+
const installed = fileCount !== void 0;
|
|
1518
|
+
if (installed) installedCount++;
|
|
1519
|
+
const mark = installed ? "\u2713" : "\u25CB";
|
|
1520
|
+
const tail = installed ? `[installed, ${fileCount} file${fileCount > 1 ? "s" : ""}]` : `[not installed \u2014 run "teamix-evo skills add ${s.id}"]`;
|
|
1521
|
+
logger.info(` ${mark} ${s.id}@${s.version} ${tail}`);
|
|
1522
|
+
if (s.description) {
|
|
1523
|
+
logger.info(` ${s.description}`);
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
logger.info("");
|
|
1527
|
+
logger.info(
|
|
1528
|
+
` Total: ${skills.length} skill(s) \u2014 ${installedCount} installed, ${skills.length - installedCount} available`
|
|
1529
|
+
);
|
|
1530
|
+
} catch (err) {
|
|
1531
|
+
logger.error(`Failed to list: ${err.message}`);
|
|
1532
|
+
process.exitCode = 1;
|
|
1533
|
+
}
|
|
1534
|
+
});
|
|
1535
|
+
function printInstalledHeader(cfg, installedAt) {
|
|
1536
|
+
logger.info("Installed skills package:");
|
|
1537
|
+
logger.info(` Package: ${SKILLS_PACKAGE}`);
|
|
1538
|
+
logger.info(` Version: ${cfg.version ?? "(unknown)"}`);
|
|
1539
|
+
logger.info(` IDEs: ${(cfg.ides ?? []).join(", ") || "(unknown)"}`);
|
|
1540
|
+
logger.info(` Scope: ${cfg.scope ?? "(unknown)"}`);
|
|
1541
|
+
logger.info(` Installed: ${new Date(installedAt).toLocaleString()}`);
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
// src/commands/skills/update.ts
|
|
1545
|
+
import { Command as Command9 } from "commander";
|
|
1546
|
+
var SKILLS_PACKAGE2 = "@teamix-evo/skills";
|
|
1547
|
+
var FLAT_VARIANT2 = "_flat";
|
|
1548
|
+
var updateCommand2 = new Command9("update").description("\u66F4\u65B0\u5DF2\u5B89\u88C5\u7684 teamix-evo skills").action(async () => {
|
|
1549
|
+
try {
|
|
1550
|
+
const ide = detectIde();
|
|
1551
|
+
const projectRoot = ide.getProjectRoot();
|
|
1552
|
+
const config = await readProjectConfig(projectRoot);
|
|
1553
|
+
if (!config?.packages?.skills) {
|
|
1554
|
+
logger.error('Skills not added. Run "teamix-evo skills add" first.');
|
|
1555
|
+
process.exitCode = 1;
|
|
1556
|
+
return;
|
|
1557
|
+
}
|
|
1558
|
+
const installedManifest = await readInstalledManifest(projectRoot);
|
|
1559
|
+
if (!installedManifest) {
|
|
1560
|
+
logger.error("No installed manifest found.");
|
|
1561
|
+
process.exitCode = 1;
|
|
1562
|
+
return;
|
|
1563
|
+
}
|
|
1564
|
+
const skillsEntry = config.packages.skills;
|
|
1565
|
+
const ides = skillsEntry.ides ?? [
|
|
1566
|
+
"qoder",
|
|
1567
|
+
"claude"
|
|
1568
|
+
];
|
|
1569
|
+
const scope = skillsEntry.scope ?? "project";
|
|
1570
|
+
logger.info(`Updating skills (ides=[${ides.join(",")}], scope=${scope})`);
|
|
1571
|
+
const { manifest, data, packageRoot } = await loadSkillsData(
|
|
1572
|
+
SKILLS_PACKAGE2
|
|
1573
|
+
);
|
|
1574
|
+
logger.info(
|
|
1575
|
+
`Current: v${skillsEntry.version} \u2192 Available: v${manifest.version}`
|
|
1576
|
+
);
|
|
1577
|
+
const result = await updateSkills({
|
|
1578
|
+
projectRoot,
|
|
1579
|
+
manifest,
|
|
1580
|
+
data,
|
|
1581
|
+
packageRoot,
|
|
1582
|
+
ides,
|
|
1583
|
+
scope
|
|
1584
|
+
});
|
|
1585
|
+
config.packages.skills.version = manifest.version;
|
|
1586
|
+
await writeProjectConfig(projectRoot, config);
|
|
1587
|
+
const installedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1588
|
+
const idx = installedManifest.installed.findIndex(
|
|
1589
|
+
(p) => p.package === SKILLS_PACKAGE2
|
|
1590
|
+
);
|
|
1591
|
+
const entry = {
|
|
1592
|
+
package: SKILLS_PACKAGE2,
|
|
1593
|
+
variant: FLAT_VARIANT2,
|
|
1594
|
+
version: manifest.version,
|
|
1595
|
+
installedAt,
|
|
1596
|
+
resources: result.resources
|
|
1597
|
+
};
|
|
1598
|
+
if (idx >= 0) installedManifest.installed[idx] = entry;
|
|
1599
|
+
else installedManifest.installed.push(entry);
|
|
1600
|
+
await writeInstalledManifest(projectRoot, installedManifest);
|
|
1601
|
+
const existingLock = await readSkillsLock(projectRoot) ?? {
|
|
1602
|
+
schemaVersion: 1,
|
|
1603
|
+
skills: {}
|
|
1604
|
+
};
|
|
1605
|
+
const lock = {
|
|
1606
|
+
schemaVersion: 1,
|
|
1607
|
+
skills: { ...existingLock.skills }
|
|
1608
|
+
};
|
|
1609
|
+
for (const skill of manifest.skills) {
|
|
1610
|
+
const mirroredTo = skill.ides.filter((i) => ides.includes(i));
|
|
1611
|
+
if (mirroredTo.length === 0) continue;
|
|
1612
|
+
lock.skills[skill.id] = {
|
|
1613
|
+
version: skill.version,
|
|
1614
|
+
from: SKILLS_PACKAGE2,
|
|
1615
|
+
installedAt,
|
|
1616
|
+
scope,
|
|
1617
|
+
mirroredTo
|
|
1618
|
+
};
|
|
1619
|
+
}
|
|
1620
|
+
await writeSkillsLock(projectRoot, lock);
|
|
1621
|
+
const { summary } = result;
|
|
1622
|
+
logger.success(`Skills updated to v${manifest.version}`);
|
|
1623
|
+
logger.info(` Created: ${summary.created}`);
|
|
1624
|
+
logger.info(` Overwritten: ${summary.overwritten}`);
|
|
1625
|
+
logger.info(` Managed: ${summary.managed}`);
|
|
1626
|
+
logger.info(` Skipped: ${summary.skipped}`);
|
|
1627
|
+
} catch (err) {
|
|
1628
|
+
logger.error(`Failed to update skills: ${err.message}`);
|
|
1629
|
+
logger.debug(err.stack ?? "");
|
|
1630
|
+
process.exitCode = 1;
|
|
1631
|
+
}
|
|
1632
|
+
});
|
|
1633
|
+
|
|
1634
|
+
// src/commands/skills/uninstall.ts
|
|
1635
|
+
import { Command as Command10 } from "commander";
|
|
1636
|
+
import * as prompts3 from "@clack/prompts";
|
|
1637
|
+
import * as path12 from "path";
|
|
1638
|
+
import * as fs9 from "fs/promises";
|
|
1639
|
+
var SKILLS_PACKAGE3 = "@teamix-evo/skills";
|
|
1640
|
+
var uninstallCommand2 = new Command10("uninstall").description("\u5378\u8F7D\u5DF2\u5B89\u88C5\u7684 teamix-evo skills\uFF08\u5220\u9664\u6CE8\u5165\u5230 IDE \u7684\u6280\u80FD\u6587\u4EF6\uFF09").option("-y, --yes", "\u8DF3\u8FC7\u786E\u8BA4").action(async (opts) => {
|
|
1641
|
+
try {
|
|
1642
|
+
const ide = detectIde();
|
|
1643
|
+
const projectRoot = ide.getProjectRoot();
|
|
1644
|
+
const config = await readProjectConfig(projectRoot);
|
|
1645
|
+
if (!config?.packages?.skills) {
|
|
1646
|
+
logger.info("Skills are not installed. Nothing to do.");
|
|
1647
|
+
return;
|
|
1648
|
+
}
|
|
1649
|
+
const installedManifest = await readInstalledManifest(projectRoot);
|
|
1650
|
+
const pkg = installedManifest?.installed.find(
|
|
1651
|
+
(p) => p.package === SKILLS_PACKAGE3
|
|
1652
|
+
);
|
|
1653
|
+
const resources = pkg?.resources ?? [];
|
|
1654
|
+
logger.info(
|
|
1655
|
+
`Will remove ${resources.length} skill file(s) installed by ${SKILLS_PACKAGE3}.`
|
|
1656
|
+
);
|
|
1657
|
+
if (!opts.yes) {
|
|
1658
|
+
const confirm4 = await prompts3.confirm({
|
|
1659
|
+
message: "\u786E\u8BA4\u5378\u8F7D\uFF1F\u6B64\u64CD\u4F5C\u4F1A\u5220\u9664\u4E0A\u8FF0\u6587\u4EF6\u3002",
|
|
1660
|
+
initialValue: false
|
|
1661
|
+
});
|
|
1662
|
+
if (prompts3.isCancel(confirm4) || !confirm4) {
|
|
1663
|
+
logger.info("Cancelled.");
|
|
1664
|
+
return;
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
const removed = await removeSkillFiles(resources);
|
|
1668
|
+
logger.debug(`Removed ${removed.length} files`);
|
|
1669
|
+
const skillsRoot = getSkillsSourceDir(projectRoot);
|
|
1670
|
+
try {
|
|
1671
|
+
await fs9.rm(skillsRoot, { recursive: true, force: true });
|
|
1672
|
+
logger.debug(`Removed source dir ${skillsRoot}`);
|
|
1673
|
+
} catch (err) {
|
|
1674
|
+
logger.warn(
|
|
1675
|
+
`Failed to remove ${skillsRoot}: ${err.message}`
|
|
1676
|
+
);
|
|
1677
|
+
}
|
|
1678
|
+
if (installedManifest && pkg) {
|
|
1679
|
+
installedManifest.installed = installedManifest.installed.filter(
|
|
1680
|
+
(p) => p.package !== SKILLS_PACKAGE3
|
|
1681
|
+
);
|
|
1682
|
+
await writeInstalledManifest(projectRoot, installedManifest);
|
|
1683
|
+
}
|
|
1684
|
+
delete config.packages.skills;
|
|
1685
|
+
await writeProjectConfig(projectRoot, config);
|
|
1686
|
+
logger.success(`Uninstalled ${SKILLS_PACKAGE3}`);
|
|
1687
|
+
logger.info(` Removed: ${removed.length} files`);
|
|
1688
|
+
logger.info(` Source: ${path12.relative(projectRoot, skillsRoot)} (cleaned)`);
|
|
1689
|
+
} catch (err) {
|
|
1690
|
+
logger.error(`Failed to uninstall: ${err.message}`);
|
|
1691
|
+
logger.debug(err.stack ?? "");
|
|
1692
|
+
process.exitCode = 1;
|
|
1693
|
+
}
|
|
1694
|
+
});
|
|
1695
|
+
|
|
1696
|
+
// src/commands/skills/sync.ts
|
|
1697
|
+
import { Command as Command11 } from "commander";
|
|
1698
|
+
|
|
1699
|
+
// src/core/skills-sync.ts
|
|
1700
|
+
import * as fs10 from "fs/promises";
|
|
1701
|
+
var SKILLS_PACKAGE_DEFAULT = "@teamix-evo/skills";
|
|
1702
|
+
async function runSkillsSync(options) {
|
|
1703
|
+
const { projectRoot, names } = options;
|
|
1704
|
+
const lock = await readSkillsLock(projectRoot);
|
|
1705
|
+
if (!lock || Object.keys(lock.skills).length === 0) {
|
|
1706
|
+
return {
|
|
1707
|
+
status: "no-skills",
|
|
1708
|
+
syncedSkillIds: [],
|
|
1709
|
+
fileCount: 0,
|
|
1710
|
+
resources: [],
|
|
1711
|
+
missingSourceIds: []
|
|
1712
|
+
};
|
|
1713
|
+
}
|
|
1714
|
+
const skillIds = Object.keys(lock.skills);
|
|
1715
|
+
const targets = names ? skillIds.filter((id) => names.includes(id)) : skillIds;
|
|
1716
|
+
const allResources = [];
|
|
1717
|
+
const synced = [];
|
|
1718
|
+
const missing = [];
|
|
1719
|
+
for (const skillId of targets) {
|
|
1720
|
+
const lockEntry = lock.skills[skillId];
|
|
1721
|
+
if (!lockEntry) continue;
|
|
1722
|
+
const sourceDir = getSkillsSourceDir(projectRoot, skillId);
|
|
1723
|
+
if (!await dirExists(sourceDir)) {
|
|
1724
|
+
logger.warn(`Skill "${skillId}" has no source at ${sourceDir}; skipped.`);
|
|
1725
|
+
missing.push(skillId);
|
|
1726
|
+
continue;
|
|
1727
|
+
}
|
|
1728
|
+
const ides = options.ides ?? lockEntry.mirroredTo;
|
|
1729
|
+
const scope = options.scope ?? lockEntry.scope;
|
|
1730
|
+
if (ides.length === 0) {
|
|
1731
|
+
logger.warn(`Skill "${skillId}" has no IDE mirror targets; skipped.`);
|
|
1732
|
+
continue;
|
|
1733
|
+
}
|
|
1734
|
+
const result = await syncSkillsToIdes({
|
|
1735
|
+
projectRoot,
|
|
1736
|
+
skills: [
|
|
1737
|
+
{
|
|
1738
|
+
id: skillId,
|
|
1739
|
+
name: skillId,
|
|
1740
|
+
updateStrategy: "regenerable"
|
|
1741
|
+
}
|
|
1742
|
+
],
|
|
1743
|
+
ides,
|
|
1744
|
+
scope
|
|
1745
|
+
});
|
|
1746
|
+
allResources.push(...result.resources);
|
|
1747
|
+
synced.push(skillId);
|
|
1748
|
+
lock.skills[skillId] = {
|
|
1749
|
+
...lockEntry,
|
|
1750
|
+
mirroredTo: ides,
|
|
1751
|
+
scope,
|
|
1752
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1753
|
+
};
|
|
1754
|
+
}
|
|
1755
|
+
await writeSkillsLock(projectRoot, lock);
|
|
1756
|
+
await refreshMirrorRecords(projectRoot, allResources);
|
|
1757
|
+
return {
|
|
1758
|
+
status: "synced",
|
|
1759
|
+
syncedSkillIds: synced,
|
|
1760
|
+
fileCount: allResources.length,
|
|
1761
|
+
resources: allResources,
|
|
1762
|
+
missingSourceIds: missing
|
|
1763
|
+
};
|
|
1764
|
+
}
|
|
1765
|
+
async function dirExists(p) {
|
|
1766
|
+
try {
|
|
1767
|
+
const stat4 = await fs10.stat(p);
|
|
1768
|
+
return stat4.isDirectory();
|
|
1769
|
+
} catch {
|
|
1770
|
+
return false;
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1773
|
+
async function refreshMirrorRecords(projectRoot, newMirrorRecords) {
|
|
1774
|
+
const installed = await readInstalledManifest(projectRoot);
|
|
1775
|
+
if (!installed) return;
|
|
1776
|
+
const pkg = installed.installed.find((p) => p.package === SKILLS_PACKAGE_DEFAULT);
|
|
1777
|
+
if (!pkg) return;
|
|
1778
|
+
const sourceOnly = pkg.resources.filter((r) => r.ide === void 0);
|
|
1779
|
+
pkg.resources = [...sourceOnly, ...newMirrorRecords];
|
|
1780
|
+
pkg.installedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1781
|
+
await writeInstalledManifest(projectRoot, installed);
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
// src/commands/skills/sync.ts
|
|
1785
|
+
var syncCommand = new Command11("sync").description(
|
|
1786
|
+
"\u628A .teamix-evo/skills/ \u4E0B\u7684\u6E90\u91CD\u65B0\u955C\u50CF\u5230 IDE \u8DEF\u5F84\uFF08.qoder / .claude\uFF09"
|
|
1787
|
+
).argument(
|
|
1788
|
+
"[names...]",
|
|
1789
|
+
"\u53EF\u9009\uFF1A\u4EC5\u540C\u6B65\u6307\u5B9A skill id\uFF1B\u7701\u7565\u5219\u540C\u6B65\u5168\u90E8\u5DF2\u8BB0\u5F55\u5728 lock \u5185\u7684 skill"
|
|
1790
|
+
).option(
|
|
1791
|
+
"--ide <list>",
|
|
1792
|
+
"\u9017\u53F7\u5206\u9694\u7684 IDE \u5217\u8868\uFF08\u8986\u76D6 lock \u4E2D\u8BB0\u5F55\u7684 mirroredTo\uFF09"
|
|
1793
|
+
).option(
|
|
1794
|
+
"--scope <scope>",
|
|
1795
|
+
"project | global\uFF08\u8986\u76D6 lock \u4E2D\u8BB0\u5F55\u7684 scope\uFF09"
|
|
1796
|
+
).action(async (names, opts) => {
|
|
1797
|
+
try {
|
|
1798
|
+
const ide = detectIde();
|
|
1799
|
+
const projectRoot = ide.getProjectRoot();
|
|
1800
|
+
const ides = opts.ide ? parseIdeList2(opts.ide) : void 0;
|
|
1801
|
+
const scope = opts.scope ? parseScope2(opts.scope) : void 0;
|
|
1802
|
+
const result = await runSkillsSync({
|
|
1803
|
+
projectRoot,
|
|
1804
|
+
ides,
|
|
1805
|
+
scope,
|
|
1806
|
+
names: names.length > 0 ? names : void 0
|
|
1807
|
+
});
|
|
1808
|
+
if (result.status === "no-skills") {
|
|
1809
|
+
logger.info(
|
|
1810
|
+
"No skills recorded in .teamix-evo/skills/manifest.lock.json. Nothing to sync."
|
|
1811
|
+
);
|
|
1812
|
+
return;
|
|
1813
|
+
}
|
|
1814
|
+
logger.success(
|
|
1815
|
+
`Synced ${result.syncedSkillIds.length} skill(s) \u2192 ${result.fileCount} file(s)`
|
|
1816
|
+
);
|
|
1817
|
+
if (result.syncedSkillIds.length > 0) {
|
|
1818
|
+
logger.info(` Skills: ${result.syncedSkillIds.join(", ")}`);
|
|
1819
|
+
}
|
|
1820
|
+
if (result.missingSourceIds.length > 0) {
|
|
1821
|
+
logger.warn(
|
|
1822
|
+
` Missing source: ${result.missingSourceIds.join(
|
|
1823
|
+
", "
|
|
1824
|
+
)} (run "skills add <id>" to (re)install)`
|
|
1825
|
+
);
|
|
1826
|
+
}
|
|
1827
|
+
} catch (err) {
|
|
1828
|
+
logger.error(`Failed to sync skills: ${err.message}`);
|
|
1829
|
+
logger.debug(err.stack ?? "");
|
|
1830
|
+
process.exitCode = 1;
|
|
1831
|
+
}
|
|
1832
|
+
});
|
|
1833
|
+
function parseIdeList2(input) {
|
|
1834
|
+
const parts = input.split(",").map((s) => s.trim().toLowerCase()).filter(Boolean);
|
|
1835
|
+
const result = [];
|
|
1836
|
+
for (const p of parts) {
|
|
1837
|
+
if (p === "qoder" || p === "claude") {
|
|
1838
|
+
if (!result.includes(p)) result.push(p);
|
|
1839
|
+
} else {
|
|
1840
|
+
throw new Error(
|
|
1841
|
+
`Unknown IDE: "${p}". Expected one of: ${ALL_IDE_KINDS.join(", ")}.`
|
|
1842
|
+
);
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
return result;
|
|
1846
|
+
}
|
|
1847
|
+
function parseScope2(input) {
|
|
1848
|
+
const v = input.toLowerCase();
|
|
1849
|
+
if (v === "project" || v === "global") return v;
|
|
1850
|
+
throw new Error(`Invalid --scope: "${input}". Expected project | global.`);
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1853
|
+
// src/commands/skills/doctor.ts
|
|
1854
|
+
import { Command as Command12 } from "commander";
|
|
1855
|
+
|
|
1856
|
+
// src/core/skills-doctor.ts
|
|
1857
|
+
import * as path13 from "path";
|
|
1858
|
+
import * as fs11 from "fs/promises";
|
|
1859
|
+
async function runSkillsDoctor(options) {
|
|
1860
|
+
const { projectRoot } = options;
|
|
1861
|
+
const lock = await readSkillsLock(projectRoot);
|
|
1862
|
+
if (!lock || Object.keys(lock.skills).length === 0) {
|
|
1863
|
+
return { status: "no-skills", findings: [] };
|
|
1864
|
+
}
|
|
1865
|
+
const findings = [];
|
|
1866
|
+
for (const [skillId, entry] of Object.entries(lock.skills)) {
|
|
1867
|
+
const sourceDir = getSkillsSourceDir(projectRoot, skillId);
|
|
1868
|
+
if (!await dirExists2(sourceDir)) {
|
|
1869
|
+
findings.push({
|
|
1870
|
+
kind: "missing-source",
|
|
1871
|
+
skillId,
|
|
1872
|
+
path: sourceDir,
|
|
1873
|
+
detail: 'Run "teamix-evo skills add" to reinstall.'
|
|
1874
|
+
});
|
|
1875
|
+
continue;
|
|
1876
|
+
}
|
|
1877
|
+
const sourceFiles = await walkDir(sourceDir);
|
|
1878
|
+
const sourceContents = /* @__PURE__ */ new Map();
|
|
1879
|
+
for (const f of sourceFiles) {
|
|
1880
|
+
const rel2 = path13.relative(sourceDir, f);
|
|
1881
|
+
sourceContents.set(rel2, await fs11.readFile(f, "utf-8"));
|
|
1882
|
+
}
|
|
1883
|
+
for (const ide of entry.mirroredTo) {
|
|
1884
|
+
const adapter = getAdapter(ide);
|
|
1885
|
+
const mirrorDir = adapter.getSkillTargetDir(
|
|
1886
|
+
skillId,
|
|
1887
|
+
entry.scope,
|
|
1888
|
+
projectRoot
|
|
1889
|
+
);
|
|
1890
|
+
if (!await dirExists2(mirrorDir)) {
|
|
1891
|
+
findings.push({
|
|
1892
|
+
kind: "missing-mirror",
|
|
1893
|
+
skillId,
|
|
1894
|
+
ide,
|
|
1895
|
+
scope: entry.scope,
|
|
1896
|
+
path: mirrorDir,
|
|
1897
|
+
detail: 'Run "teamix-evo skills sync" to re-mirror.'
|
|
1898
|
+
});
|
|
1899
|
+
continue;
|
|
1900
|
+
}
|
|
1901
|
+
for (const [rel2, sourceContent] of sourceContents.entries()) {
|
|
1902
|
+
const mirrorFile = path13.join(mirrorDir, rel2);
|
|
1903
|
+
if (!await fileExists(mirrorFile)) {
|
|
1904
|
+
findings.push({
|
|
1905
|
+
kind: "missing-mirror",
|
|
1906
|
+
skillId,
|
|
1907
|
+
ide,
|
|
1908
|
+
scope: entry.scope,
|
|
1909
|
+
path: mirrorFile,
|
|
1910
|
+
detail: 'Run "teamix-evo skills sync" to re-mirror.'
|
|
1911
|
+
});
|
|
1912
|
+
continue;
|
|
1913
|
+
}
|
|
1914
|
+
const mirrorContent = await fs11.readFile(mirrorFile, "utf-8");
|
|
1915
|
+
if (computeHash(mirrorContent) !== computeHash(sourceContent)) {
|
|
1916
|
+
findings.push({
|
|
1917
|
+
kind: "mirror-drift",
|
|
1918
|
+
skillId,
|
|
1919
|
+
ide,
|
|
1920
|
+
scope: entry.scope,
|
|
1921
|
+
path: mirrorFile,
|
|
1922
|
+
detail: 'Mirror differs from source. Re-run "teamix-evo skills sync" to overwrite.'
|
|
1923
|
+
});
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
return {
|
|
1929
|
+
status: findings.length === 0 ? "clean" : "drift",
|
|
1930
|
+
findings
|
|
1931
|
+
};
|
|
1932
|
+
}
|
|
1933
|
+
async function dirExists2(p) {
|
|
1934
|
+
try {
|
|
1935
|
+
const stat4 = await fs11.stat(p);
|
|
1936
|
+
return stat4.isDirectory();
|
|
1937
|
+
} catch {
|
|
1938
|
+
return false;
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
|
|
1942
|
+
// src/commands/skills/doctor.ts
|
|
1943
|
+
var doctorCommand = new Command12("doctor").description(
|
|
1944
|
+
"\u68C0\u67E5 .teamix-evo/skills/ \u6E90\u4E0E IDE \u955C\u50CF\u662F\u5426\u6F02\u79FB\uFF1B\u63D0\u793A\u5982\u4F55\u4FEE\u590D"
|
|
1945
|
+
).action(async () => {
|
|
1946
|
+
try {
|
|
1947
|
+
const ide = detectIde();
|
|
1948
|
+
const projectRoot = ide.getProjectRoot();
|
|
1949
|
+
const result = await runSkillsDoctor({ projectRoot });
|
|
1950
|
+
if (result.status === "no-skills") {
|
|
1951
|
+
logger.info(
|
|
1952
|
+
'No skills recorded. Run "teamix-evo skills add" first.'
|
|
1953
|
+
);
|
|
1954
|
+
return;
|
|
1955
|
+
}
|
|
1956
|
+
if (result.status === "clean") {
|
|
1957
|
+
logger.success("Skills are in sync. No drift detected.");
|
|
1958
|
+
return;
|
|
1959
|
+
}
|
|
1960
|
+
logger.warn(
|
|
1961
|
+
`Found ${result.findings.length} drift issue(s). Run "teamix-evo skills sync" to repair mirrors.`
|
|
1962
|
+
);
|
|
1963
|
+
for (const f of result.findings) {
|
|
1964
|
+
const idePart = f.ide ? ` [${f.ide}]` : "";
|
|
1965
|
+
logger.info(` - ${f.kind}${idePart}: ${f.skillId}`);
|
|
1966
|
+
logger.info(` ${f.path}`);
|
|
1967
|
+
if (f.detail) logger.info(` ${f.detail}`);
|
|
1968
|
+
}
|
|
1969
|
+
process.exitCode = 1;
|
|
1970
|
+
} catch (err) {
|
|
1971
|
+
logger.error(`Failed to run doctor: ${err.message}`);
|
|
1972
|
+
logger.debug(err.stack ?? "");
|
|
1973
|
+
process.exitCode = 1;
|
|
1974
|
+
}
|
|
1975
|
+
});
|
|
1976
|
+
|
|
1977
|
+
// src/commands/skills/index.ts
|
|
1978
|
+
var skillsCommand = new Command13("skills").description(
|
|
1979
|
+
"\u7BA1\u7406 teamix-evo skills\uFF08\u5411 AI IDE \u6CE8\u5165\u6280\u80FD\uFF1Bsource-mirror \u6A21\u578B\u89C1 ADR 0013\uFF09"
|
|
1980
|
+
);
|
|
1981
|
+
skillsCommand.addCommand(addCommand);
|
|
1982
|
+
skillsCommand.addCommand(listCommand2);
|
|
1983
|
+
skillsCommand.addCommand(updateCommand2);
|
|
1984
|
+
skillsCommand.addCommand(syncCommand);
|
|
1985
|
+
skillsCommand.addCommand(doctorCommand);
|
|
1986
|
+
skillsCommand.addCommand(uninstallCommand2);
|
|
1987
|
+
|
|
1988
|
+
// src/commands/ui/index.ts
|
|
1989
|
+
import { Command as Command17 } from "commander";
|
|
1990
|
+
|
|
1991
|
+
// src/commands/ui/init.ts
|
|
1992
|
+
import { Command as Command14 } from "commander";
|
|
1993
|
+
import * as prompts4 from "@clack/prompts";
|
|
1994
|
+
|
|
1995
|
+
// src/core/ui-init.ts
|
|
1996
|
+
var DEFAULT_UI_ALIASES = {
|
|
1997
|
+
components: "src/components/ui",
|
|
1998
|
+
hooks: "src/hooks",
|
|
1999
|
+
utils: "src/lib/utils",
|
|
2000
|
+
lib: "src/lib",
|
|
2001
|
+
business: "src/components/business",
|
|
2002
|
+
templates: "src/templates"
|
|
2003
|
+
};
|
|
2004
|
+
var DEFAULT_UI_ICON_LIBRARY = "lucide";
|
|
2005
|
+
async function runUiInit(options) {
|
|
2006
|
+
const { projectRoot } = options;
|
|
2007
|
+
const ideIdent = options.ide ?? "qoder";
|
|
2008
|
+
await ensureTeamixDir(projectRoot);
|
|
2009
|
+
const existingConfig = await readProjectConfig(projectRoot);
|
|
2010
|
+
if (existingConfig?.packages?.ui) {
|
|
2011
|
+
return { status: "already-initialized" };
|
|
2012
|
+
}
|
|
2013
|
+
const aliases = {
|
|
2014
|
+
components: options.aliases?.components ?? DEFAULT_UI_ALIASES.components,
|
|
2015
|
+
hooks: options.aliases?.hooks ?? DEFAULT_UI_ALIASES.hooks,
|
|
2016
|
+
utils: options.aliases?.utils ?? DEFAULT_UI_ALIASES.utils,
|
|
2017
|
+
lib: options.aliases?.lib ?? DEFAULT_UI_ALIASES.lib,
|
|
2018
|
+
business: options.aliases?.business ?? DEFAULT_UI_ALIASES.business,
|
|
2019
|
+
templates: options.aliases?.templates ?? DEFAULT_UI_ALIASES.templates
|
|
2020
|
+
};
|
|
2021
|
+
const iconLibrary = options.iconLibrary ?? DEFAULT_UI_ICON_LIBRARY;
|
|
2022
|
+
const tsx = options.tsx ?? true;
|
|
2023
|
+
const rsc = options.rsc ?? false;
|
|
2024
|
+
const config = existingConfig ?? {
|
|
2025
|
+
$schema: "https://teamix-evo.dev/schema/config/v1.json",
|
|
2026
|
+
schemaVersion: 1,
|
|
2027
|
+
ide: ideIdent,
|
|
2028
|
+
packages: {}
|
|
2029
|
+
};
|
|
2030
|
+
config.packages.ui = {
|
|
2031
|
+
variant: "_flat",
|
|
2032
|
+
version: "0.0.0",
|
|
2033
|
+
aliases,
|
|
2034
|
+
iconLibrary,
|
|
2035
|
+
tsx,
|
|
2036
|
+
rsc
|
|
2037
|
+
};
|
|
2038
|
+
await writeProjectConfig(projectRoot, config);
|
|
2039
|
+
return {
|
|
2040
|
+
status: "installed",
|
|
2041
|
+
aliases,
|
|
2042
|
+
iconLibrary,
|
|
2043
|
+
tsx,
|
|
2044
|
+
rsc
|
|
2045
|
+
};
|
|
2046
|
+
}
|
|
2047
|
+
|
|
2048
|
+
// src/commands/ui/init.ts
|
|
2049
|
+
var initCommand2 = new Command14("init").description(
|
|
2050
|
+
"\u521D\u59CB\u5316 teamix-evo ui \u914D\u7F6E\uFF08\u8BE2\u95EE aliases / iconLibrary / tsx / rsc\uFF09"
|
|
2051
|
+
).option("-y, --yes", "\u4F7F\u7528\u9ED8\u8BA4\u503C\uFF0C\u8DF3\u8FC7\u4EA4\u4E92").option(
|
|
2052
|
+
"--components <path>",
|
|
2053
|
+
"\u7EC4\u4EF6 alias \u8DEF\u5F84",
|
|
2054
|
+
DEFAULT_UI_ALIASES.components
|
|
2055
|
+
).option("--hooks <path>", "hooks alias \u8DEF\u5F84", DEFAULT_UI_ALIASES.hooks).option("--utils <path>", "utils alias \u8DEF\u5F84", DEFAULT_UI_ALIASES.utils).option("--lib <path>", "lib alias \u8DEF\u5F84", DEFAULT_UI_ALIASES.lib).option("--icon-library <name>", "\u9ED8\u8BA4 icon \u5E93\uFF08\u58F0\u660E\u6027\uFF09", "lucide").option("--tsx", "\u4F7F\u7528 TSX", true).option("--rsc", "\u4F7F\u7528 React Server Components").action(async (opts) => {
|
|
2056
|
+
try {
|
|
2057
|
+
const ide = detectIde();
|
|
2058
|
+
const projectRoot = ide.getProjectRoot();
|
|
2059
|
+
const cfg = await resolveConfig(opts);
|
|
2060
|
+
const result = await runUiInit({
|
|
2061
|
+
projectRoot,
|
|
2062
|
+
aliases: cfg.aliases,
|
|
2063
|
+
iconLibrary: cfg.iconLibrary,
|
|
2064
|
+
tsx: cfg.tsx,
|
|
2065
|
+
rsc: cfg.rsc,
|
|
2066
|
+
ide: ide.name
|
|
2067
|
+
});
|
|
2068
|
+
if (result.status === "already-initialized") {
|
|
2069
|
+
logger.warn(
|
|
2070
|
+
"UI already initialized. Edit `.teamix-evo/config.json` directly to change aliases, or run `teamix-evo ui list`."
|
|
2071
|
+
);
|
|
2072
|
+
return;
|
|
2073
|
+
}
|
|
2074
|
+
logger.success("UI initialized.");
|
|
2075
|
+
logger.info(` components: ${result.aliases.components}`);
|
|
2076
|
+
logger.info(` hooks: ${result.aliases.hooks}`);
|
|
2077
|
+
logger.info(` utils: ${result.aliases.utils}`);
|
|
2078
|
+
logger.info(` lib: ${result.aliases.lib}`);
|
|
2079
|
+
logger.info(` iconLibrary: ${result.iconLibrary}`);
|
|
2080
|
+
logger.info(` tsx: ${result.tsx}, rsc: ${result.rsc}`);
|
|
2081
|
+
logger.info("");
|
|
2082
|
+
logger.info("Next: `npx teamix-evo ui add button`");
|
|
2083
|
+
} catch (err) {
|
|
2084
|
+
logger.error(`Failed to initialize ui: ${err.message}`);
|
|
2085
|
+
logger.debug(err.stack ?? "");
|
|
2086
|
+
process.exitCode = 1;
|
|
2087
|
+
}
|
|
2088
|
+
});
|
|
2089
|
+
async function resolveConfig(opts) {
|
|
2090
|
+
if (opts.yes) {
|
|
2091
|
+
return {
|
|
2092
|
+
aliases: {
|
|
2093
|
+
components: opts.components ?? DEFAULT_UI_ALIASES.components,
|
|
2094
|
+
hooks: opts.hooks ?? DEFAULT_UI_ALIASES.hooks,
|
|
2095
|
+
utils: opts.utils ?? DEFAULT_UI_ALIASES.utils,
|
|
2096
|
+
lib: opts.lib ?? DEFAULT_UI_ALIASES.lib,
|
|
2097
|
+
business: DEFAULT_UI_ALIASES.business,
|
|
2098
|
+
templates: DEFAULT_UI_ALIASES.templates
|
|
2099
|
+
},
|
|
2100
|
+
iconLibrary: opts.iconLibrary ?? "lucide",
|
|
2101
|
+
tsx: opts.tsx ?? true,
|
|
2102
|
+
rsc: opts.rsc ?? false
|
|
2103
|
+
};
|
|
2104
|
+
}
|
|
2105
|
+
const components = await prompts4.text({
|
|
2106
|
+
message: "components \u8DEF\u5F84\uFF08\u6CE8\u5165\u6309\u94AE\u7B49\u7EC4\u4EF6\u6E90\u7801\u7684\u76EE\u5F55\uFF09",
|
|
2107
|
+
initialValue: opts.components ?? DEFAULT_UI_ALIASES.components
|
|
2108
|
+
});
|
|
2109
|
+
if (prompts4.isCancel(components)) throw new Error("Cancelled by user.");
|
|
2110
|
+
const hooks = await prompts4.text({
|
|
2111
|
+
message: "hooks \u8DEF\u5F84",
|
|
2112
|
+
initialValue: opts.hooks ?? DEFAULT_UI_ALIASES.hooks
|
|
2113
|
+
});
|
|
2114
|
+
if (prompts4.isCancel(hooks)) throw new Error("Cancelled by user.");
|
|
2115
|
+
const utils = await prompts4.text({
|
|
2116
|
+
message: "utils \u8DEF\u5F84\uFF08cn \u7B49\u5DE5\u5177\uFF09",
|
|
2117
|
+
initialValue: opts.utils ?? DEFAULT_UI_ALIASES.utils
|
|
2118
|
+
});
|
|
2119
|
+
if (prompts4.isCancel(utils)) throw new Error("Cancelled by user.");
|
|
2120
|
+
const lib = await prompts4.text({
|
|
2121
|
+
message: "lib \u8DEF\u5F84\uFF08\u5171\u4EAB\u4EE3\u7801\u6839\uFF09",
|
|
2122
|
+
initialValue: opts.lib ?? DEFAULT_UI_ALIASES.lib
|
|
2123
|
+
});
|
|
2124
|
+
if (prompts4.isCancel(lib)) throw new Error("Cancelled by user.");
|
|
2125
|
+
const iconLibrary = await prompts4.text({
|
|
2126
|
+
message: "icon \u5E93\uFF08\u58F0\u660E\u6027\uFF0C\u7EC4\u4EF6\u6E90\u7801\u5DF2 hardcode lucide-react\uFF09",
|
|
2127
|
+
initialValue: opts.iconLibrary ?? "lucide"
|
|
2128
|
+
});
|
|
2129
|
+
if (prompts4.isCancel(iconLibrary)) throw new Error("Cancelled by user.");
|
|
2130
|
+
const tsxAns = await prompts4.confirm({
|
|
2131
|
+
message: "\u4F7F\u7528 TSX\uFF1F",
|
|
2132
|
+
initialValue: opts.tsx ?? true
|
|
2133
|
+
});
|
|
2134
|
+
if (prompts4.isCancel(tsxAns)) throw new Error("Cancelled by user.");
|
|
2135
|
+
const rscAns = await prompts4.confirm({
|
|
2136
|
+
message: "\u4F7F\u7528 React Server Components\uFF1F",
|
|
2137
|
+
initialValue: opts.rsc ?? false
|
|
2138
|
+
});
|
|
2139
|
+
if (prompts4.isCancel(rscAns)) throw new Error("Cancelled by user.");
|
|
2140
|
+
return {
|
|
2141
|
+
aliases: {
|
|
2142
|
+
components,
|
|
2143
|
+
hooks,
|
|
2144
|
+
utils,
|
|
2145
|
+
lib,
|
|
2146
|
+
business: DEFAULT_UI_ALIASES.business,
|
|
2147
|
+
templates: DEFAULT_UI_ALIASES.templates
|
|
2148
|
+
},
|
|
2149
|
+
iconLibrary,
|
|
2150
|
+
tsx: tsxAns,
|
|
2151
|
+
rsc: rscAns
|
|
2152
|
+
};
|
|
2153
|
+
}
|
|
2154
|
+
|
|
2155
|
+
// src/commands/ui/add.ts
|
|
2156
|
+
import { Command as Command15 } from "commander";
|
|
2157
|
+
|
|
2158
|
+
// src/core/ui-client.ts
|
|
2159
|
+
import * as path14 from "path";
|
|
2160
|
+
import * as fs12 from "fs/promises";
|
|
2161
|
+
import { createRequire as createRequire3 } from "module";
|
|
2162
|
+
import { loadUiPackageManifest } from "@teamix-evo/registry";
|
|
2163
|
+
var require4 = createRequire3(import.meta.url);
|
|
2164
|
+
function resolvePackageRoot2(packageName) {
|
|
2165
|
+
const pkgJsonPath = require4.resolve(`${packageName}/package.json`);
|
|
2166
|
+
return path14.dirname(pkgJsonPath);
|
|
2167
|
+
}
|
|
2168
|
+
async function loadUiData(packageName) {
|
|
2169
|
+
const packageRoot = resolvePackageRoot2(packageName);
|
|
2170
|
+
logger.debug(`Resolved ui package root: ${packageRoot}`);
|
|
2171
|
+
const manifest = await loadUiPackageManifest(packageRoot);
|
|
2172
|
+
let data = {};
|
|
2173
|
+
const dataPath = path14.join(packageRoot, "_data.json");
|
|
2174
|
+
try {
|
|
2175
|
+
const raw = await fs12.readFile(dataPath, "utf-8");
|
|
2176
|
+
data = JSON.parse(raw);
|
|
2177
|
+
} catch (err) {
|
|
2178
|
+
if (err.code !== "ENOENT") {
|
|
2179
|
+
throw err;
|
|
2180
|
+
}
|
|
2181
|
+
logger.debug(`No _data.json found at ${dataPath}, using empty data`);
|
|
2182
|
+
}
|
|
2183
|
+
return { manifest, data, packageRoot };
|
|
2184
|
+
}
|
|
2185
|
+
|
|
2186
|
+
// src/core/ui-installer.ts
|
|
2187
|
+
import * as path15 from "path";
|
|
2188
|
+
import * as fs13 from "fs/promises";
|
|
2189
|
+
import { resolveUiEntryOrder } from "@teamix-evo/registry";
|
|
2190
|
+
|
|
2191
|
+
// src/utils/transform-imports.ts
|
|
2192
|
+
var SOURCE_ROOT_TO_ALIAS_KEY = {
|
|
2193
|
+
components: "components",
|
|
2194
|
+
hooks: "hooks",
|
|
2195
|
+
utils: "utils",
|
|
2196
|
+
lib: "lib"
|
|
2197
|
+
};
|
|
2198
|
+
function rewriteImports(source, aliases) {
|
|
2199
|
+
return source.replace(
|
|
2200
|
+
/(['"])@\/([a-z][a-z0-9-]*)(\/[^'"]*)?\1/g,
|
|
2201
|
+
(full, quote, root, rest) => {
|
|
2202
|
+
const aliasKey = SOURCE_ROOT_TO_ALIAS_KEY[root];
|
|
2203
|
+
if (!aliasKey) return full;
|
|
2204
|
+
const alias = aliases[aliasKey];
|
|
2205
|
+
const normalized = aliasToImportPath(alias);
|
|
2206
|
+
const flatRest = flattenRestPath(rest);
|
|
2207
|
+
return `${quote}${normalized}${flatRest}${quote}`;
|
|
2208
|
+
}
|
|
2209
|
+
);
|
|
2210
|
+
}
|
|
2211
|
+
function flattenRestPath(rest) {
|
|
2212
|
+
if (!rest) return "";
|
|
2213
|
+
const segments = rest.split("/");
|
|
2214
|
+
if (segments.length === 3) {
|
|
2215
|
+
return `/${segments[2]}`;
|
|
2216
|
+
}
|
|
2217
|
+
return rest;
|
|
2218
|
+
}
|
|
2219
|
+
function aliasToImportPath(alias) {
|
|
2220
|
+
const trimmed = alias.replace(/^\.\//, "").replace(/\/$/, "");
|
|
2221
|
+
if (trimmed.startsWith("src/")) {
|
|
2222
|
+
return `@/${trimmed.slice("src/".length)}`;
|
|
2223
|
+
}
|
|
2224
|
+
return `@/${trimmed}`;
|
|
2225
|
+
}
|
|
2226
|
+
|
|
2227
|
+
// src/core/ui-installer.ts
|
|
2228
|
+
var DESIGN_COMPONENTS_DIR = ".teamix-evo/design/components";
|
|
2229
|
+
async function installUiEntries(options) {
|
|
2230
|
+
const {
|
|
2231
|
+
projectRoot,
|
|
2232
|
+
manifest,
|
|
2233
|
+
packageRoot,
|
|
2234
|
+
aliases,
|
|
2235
|
+
requested,
|
|
2236
|
+
skipExisting = true
|
|
2237
|
+
} = options;
|
|
2238
|
+
const orderedIds = resolveUiEntryOrder(manifest.entries, requested);
|
|
2239
|
+
const idToEntry = new Map(manifest.entries.map((e) => [e.id, e]));
|
|
2240
|
+
const resources = [];
|
|
2241
|
+
const npmDeps = {};
|
|
2242
|
+
const metaFiles = [];
|
|
2243
|
+
let written = 0;
|
|
2244
|
+
let skipped = 0;
|
|
2245
|
+
for (const id of orderedIds) {
|
|
2246
|
+
const entry = idToEntry.get(id);
|
|
2247
|
+
if (!entry) continue;
|
|
2248
|
+
if (entry.dependencies) {
|
|
2249
|
+
for (const [name, range] of Object.entries(entry.dependencies)) {
|
|
2250
|
+
npmDeps[name] = range;
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
2253
|
+
for (const file of entry.files) {
|
|
2254
|
+
const targetAbs = resolveTargetPath(projectRoot, aliases, entry, file);
|
|
2255
|
+
const exists = await fileExists(targetAbs);
|
|
2256
|
+
if (exists && skipExisting && (entry.updateStrategy ?? "frozen") === "frozen") {
|
|
2257
|
+
logger.info(` skip (frozen, exists): ${rel(projectRoot, targetAbs)}`);
|
|
2258
|
+
skipped++;
|
|
2259
|
+
continue;
|
|
2260
|
+
}
|
|
2261
|
+
const sourceAbs = path15.resolve(packageRoot, file.source);
|
|
2262
|
+
const raw = await fs13.readFile(sourceAbs, "utf-8");
|
|
2263
|
+
const transformed = rewriteImports(raw, aliases);
|
|
2264
|
+
await writeFileSafe(targetAbs, transformed);
|
|
2265
|
+
written++;
|
|
2266
|
+
logger.info(` write: ${rel(projectRoot, targetAbs)}`);
|
|
2267
|
+
resources.push({
|
|
2268
|
+
id: `${entry.id}:${file.targetName}`,
|
|
2269
|
+
target: targetAbs,
|
|
2270
|
+
hash: computeHash(transformed),
|
|
2271
|
+
strategy: entry.updateStrategy ?? "frozen"
|
|
2272
|
+
});
|
|
2273
|
+
}
|
|
2274
|
+
if (entry.meta) {
|
|
2275
|
+
const metaSourceAbs = path15.resolve(packageRoot, entry.meta);
|
|
2276
|
+
const metaContent = await fs13.readFile(metaSourceAbs, "utf-8");
|
|
2277
|
+
const metaTargetAbs = path15.join(
|
|
2278
|
+
projectRoot,
|
|
2279
|
+
DESIGN_COMPONENTS_DIR,
|
|
2280
|
+
`${entry.id}.meta.md`
|
|
2281
|
+
);
|
|
2282
|
+
await writeFileSafe(metaTargetAbs, metaContent);
|
|
2283
|
+
metaFiles.push(metaTargetAbs);
|
|
2284
|
+
resources.push({
|
|
2285
|
+
id: `${entry.id}:meta`,
|
|
2286
|
+
target: metaTargetAbs,
|
|
2287
|
+
hash: computeHash(metaContent),
|
|
2288
|
+
strategy: "regenerable"
|
|
2289
|
+
});
|
|
2290
|
+
logger.info(` meta: ${rel(projectRoot, metaTargetAbs)}`);
|
|
2291
|
+
}
|
|
2292
|
+
}
|
|
2293
|
+
return {
|
|
2294
|
+
orderedIds,
|
|
2295
|
+
resources,
|
|
2296
|
+
npmDependencies: npmDeps,
|
|
2297
|
+
written,
|
|
2298
|
+
skipped,
|
|
2299
|
+
metaFiles
|
|
2300
|
+
};
|
|
2301
|
+
}
|
|
2302
|
+
function resolveTargetPath(projectRoot, aliases, entry, file) {
|
|
2303
|
+
const aliasDir = aliases[file.targetAlias];
|
|
2304
|
+
if (!aliasDir) {
|
|
2305
|
+
throw new Error(
|
|
2306
|
+
`Entry "${entry.id}" requires alias "${file.targetAlias}" but it is not configured.`
|
|
2307
|
+
);
|
|
2308
|
+
}
|
|
2309
|
+
return path15.join(projectRoot, aliasDir, file.targetName);
|
|
2310
|
+
}
|
|
2311
|
+
function rel(projectRoot, abs) {
|
|
2312
|
+
return path15.relative(projectRoot, abs);
|
|
2313
|
+
}
|
|
2314
|
+
|
|
2315
|
+
// src/core/ui-add.ts
|
|
2316
|
+
var DEFAULT_UI_PACKAGE = "@teamix-evo/ui";
|
|
2317
|
+
async function runUiAdd(options) {
|
|
2318
|
+
const { projectRoot, ids, overwrite } = options;
|
|
2319
|
+
const packageName = options.packageName ?? DEFAULT_UI_PACKAGE;
|
|
2320
|
+
if (ids.length === 0) {
|
|
2321
|
+
throw new Error("At least one entry id must be provided.");
|
|
2322
|
+
}
|
|
2323
|
+
const config = await readProjectConfig(projectRoot);
|
|
2324
|
+
const uiCfg = config?.packages?.ui;
|
|
2325
|
+
if (!config || !uiCfg?.aliases) {
|
|
2326
|
+
throw new Error(
|
|
2327
|
+
"UI not initialized. Run `runUiInit` (or `teamix-evo ui init`) first."
|
|
2328
|
+
);
|
|
2329
|
+
}
|
|
2330
|
+
const { manifest, packageRoot } = await loadUiData(packageName);
|
|
2331
|
+
const knownIds = new Set(manifest.entries.map((e) => e.id));
|
|
2332
|
+
const unknown = ids.filter((id) => !knownIds.has(id));
|
|
2333
|
+
if (unknown.length > 0) {
|
|
2334
|
+
throw new Error(
|
|
2335
|
+
`Unknown entry id(s): ${unknown.map((s) => `"${s}"`).join(", ")}. Run \`teamix-evo ui list\` to see options.`
|
|
2336
|
+
);
|
|
2337
|
+
}
|
|
2338
|
+
const result = await installUiEntries({
|
|
2339
|
+
projectRoot,
|
|
2340
|
+
manifest,
|
|
2341
|
+
packageRoot,
|
|
2342
|
+
aliases: uiCfg.aliases,
|
|
2343
|
+
requested: ids,
|
|
2344
|
+
skipExisting: !overwrite
|
|
2345
|
+
});
|
|
2346
|
+
const installed = await readInstalledManifest(
|
|
2347
|
+
projectRoot
|
|
2348
|
+
) ?? { schemaVersion: 1, installed: [] };
|
|
2349
|
+
const idx = installed.installed.findIndex((p) => p.package === packageName);
|
|
2350
|
+
const prior = idx >= 0 ? installed.installed[idx] : null;
|
|
2351
|
+
const mergedResources = mergeResources(
|
|
2352
|
+
prior?.resources ?? [],
|
|
2353
|
+
result.resources
|
|
2354
|
+
);
|
|
2355
|
+
const entry = {
|
|
2356
|
+
package: packageName,
|
|
2357
|
+
variant: "_flat",
|
|
2358
|
+
version: manifest.version,
|
|
2359
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2360
|
+
resources: mergedResources
|
|
2361
|
+
};
|
|
2362
|
+
if (idx >= 0) installed.installed[idx] = entry;
|
|
2363
|
+
else installed.installed.push(entry);
|
|
2364
|
+
await writeInstalledManifest(projectRoot, installed);
|
|
2365
|
+
if (uiCfg.version !== manifest.version) {
|
|
2366
|
+
uiCfg.version = manifest.version;
|
|
2367
|
+
await writeProjectConfig(projectRoot, config);
|
|
2368
|
+
}
|
|
2369
|
+
return {
|
|
2370
|
+
packageName,
|
|
2371
|
+
orderedIds: result.orderedIds,
|
|
2372
|
+
written: result.written,
|
|
2373
|
+
skipped: result.skipped,
|
|
2374
|
+
metaFiles: result.metaFiles,
|
|
2375
|
+
npmDependencies: result.npmDependencies,
|
|
2376
|
+
resources: result.resources
|
|
2377
|
+
};
|
|
2378
|
+
}
|
|
2379
|
+
function mergeResources(prior, next) {
|
|
2380
|
+
const merged = /* @__PURE__ */ new Map();
|
|
2381
|
+
for (const r of prior) merged.set(r.id, r);
|
|
2382
|
+
for (const r of next) merged.set(r.id, r);
|
|
2383
|
+
return Array.from(merged.values());
|
|
2384
|
+
}
|
|
2385
|
+
|
|
2386
|
+
// src/commands/ui/add.ts
|
|
2387
|
+
var addCommand2 = new Command15("add").description(
|
|
2388
|
+
"\u5B89\u88C5\u4E00\u4E2A\u6216\u591A\u4E2A ui entry\uFF08\u6309 id\uFF0C\u81EA\u52A8\u5C55\u5F00 registryDependencies\uFF09"
|
|
2389
|
+
).argument("<ids...>", 'entry id \u5217\u8868\uFF0C\u5982 "button" "dialog"').option("--overwrite", "\u5373\u4F7F\u76EE\u6807\u6587\u4EF6\u5DF2\u5B58\u5728\u4E5F\u8986\u76D6\uFF08\u7ED5\u8FC7 frozen \u8DF3\u8FC7\uFF09").action(async (ids, opts) => {
|
|
2390
|
+
try {
|
|
2391
|
+
const ide = detectIde();
|
|
2392
|
+
const projectRoot = ide.getProjectRoot();
|
|
2393
|
+
logger.info(`Installing entries: ${ids.join(", ")}`);
|
|
2394
|
+
const result = await runUiAdd({
|
|
2395
|
+
projectRoot,
|
|
2396
|
+
ids,
|
|
2397
|
+
overwrite: opts.overwrite
|
|
2398
|
+
});
|
|
2399
|
+
logger.success(
|
|
2400
|
+
`UI add complete: ${result.written} written, ${result.skipped} skipped, ${result.metaFiles.length} meta.`
|
|
2401
|
+
);
|
|
2402
|
+
logger.info("");
|
|
2403
|
+
logger.info(`Resolved order: ${result.orderedIds.join(" \u2192 ")}`);
|
|
2404
|
+
const npmDeps = Object.entries(result.npmDependencies);
|
|
2405
|
+
if (npmDeps.length > 0) {
|
|
2406
|
+
logger.info("");
|
|
2407
|
+
logger.info("Install npm dependencies in your project:");
|
|
2408
|
+
const installCmd = npmDeps.map(([name, range]) => `${name}@${range}`).join(" ");
|
|
2409
|
+
logger.info(` pnpm add ${installCmd}`);
|
|
2410
|
+
logger.info(` # or: npm install ${installCmd}`);
|
|
2411
|
+
}
|
|
2412
|
+
if (result.metaFiles.length > 0) {
|
|
2413
|
+
logger.info("");
|
|
2414
|
+
logger.info(
|
|
2415
|
+
"Component meta dropped under .teamix-evo/design/components/ (AI-readable)."
|
|
2416
|
+
);
|
|
2417
|
+
}
|
|
2418
|
+
} catch (err) {
|
|
2419
|
+
const message = err.message;
|
|
2420
|
+
if (message.startsWith("UI not initialized")) {
|
|
2421
|
+
logger.error("UI not initialized. Run `npx teamix-evo ui init` first.");
|
|
2422
|
+
} else {
|
|
2423
|
+
logger.error(`Failed to add ui entries: ${message}`);
|
|
2424
|
+
}
|
|
2425
|
+
logger.debug(err.stack ?? "");
|
|
2426
|
+
process.exitCode = 1;
|
|
2427
|
+
}
|
|
2428
|
+
});
|
|
2429
|
+
|
|
2430
|
+
// src/commands/ui/list.ts
|
|
2431
|
+
import { Command as Command16 } from "commander";
|
|
2432
|
+
|
|
2433
|
+
// src/core/ui-list.ts
|
|
2434
|
+
var DEFAULT_UI_PACKAGE2 = "@teamix-evo/ui";
|
|
2435
|
+
async function runUiList(options) {
|
|
2436
|
+
const { projectRoot, installedOnly } = options;
|
|
2437
|
+
const packageName = options.packageName ?? DEFAULT_UI_PACKAGE2;
|
|
2438
|
+
const { manifest } = await loadUiData(packageName);
|
|
2439
|
+
const installedManifest = await readInstalledManifest(projectRoot);
|
|
2440
|
+
const installedIds = /* @__PURE__ */ new Set();
|
|
2441
|
+
const uiPkg = installedManifest?.installed.find(
|
|
2442
|
+
(p) => p.package === packageName
|
|
2443
|
+
);
|
|
2444
|
+
for (const r of uiPkg?.resources ?? []) {
|
|
2445
|
+
const colon = r.id.indexOf(":");
|
|
2446
|
+
installedIds.add(colon >= 0 ? r.id.slice(0, colon) : r.id);
|
|
2447
|
+
}
|
|
2448
|
+
const entries = manifest.entries.filter((e) => !installedOnly || installedIds.has(e.id)).map((e) => ({
|
|
2449
|
+
id: e.id,
|
|
2450
|
+
type: e.type,
|
|
2451
|
+
description: e.description,
|
|
2452
|
+
installed: installedIds.has(e.id)
|
|
2453
|
+
}));
|
|
2454
|
+
return {
|
|
2455
|
+
packageName,
|
|
2456
|
+
total: manifest.entries.length,
|
|
2457
|
+
installedCount: installedIds.size,
|
|
2458
|
+
entries
|
|
2459
|
+
};
|
|
2460
|
+
}
|
|
2461
|
+
|
|
2462
|
+
// src/commands/ui/list.ts
|
|
2463
|
+
var listCommand3 = new Command16("list").description("\u5217\u51FA @teamix-evo/ui \u7684\u6240\u6709 entry \u53CA\u5DF2\u5B89\u88C5\u72B6\u6001").option("--installed", "\u4EC5\u5C55\u793A\u5DF2\u5B89\u88C5\u7684 entry").action(async (opts) => {
|
|
2464
|
+
try {
|
|
2465
|
+
const ide = detectIde();
|
|
2466
|
+
const projectRoot = ide.getProjectRoot();
|
|
2467
|
+
const { entries, total, installedCount } = await runUiList({
|
|
2468
|
+
projectRoot,
|
|
2469
|
+
installedOnly: opts.installed
|
|
2470
|
+
});
|
|
2471
|
+
if (entries.length === 0) {
|
|
2472
|
+
logger.info(
|
|
2473
|
+
opts.installed ? "No ui entries installed." : "No ui entries available."
|
|
2474
|
+
);
|
|
2475
|
+
return;
|
|
2476
|
+
}
|
|
2477
|
+
const idWidth = Math.max(...entries.map((e) => e.id.length), 4);
|
|
2478
|
+
const typeWidth = Math.max(...entries.map((e) => e.type.length), 4);
|
|
2479
|
+
logger.info(
|
|
2480
|
+
`${"ID".padEnd(idWidth)} ${"TYPE".padEnd(
|
|
2481
|
+
typeWidth
|
|
2482
|
+
)} STATUS DESCRIPTION`
|
|
2483
|
+
);
|
|
2484
|
+
logger.info(
|
|
2485
|
+
`${"\u2500".repeat(idWidth)} ${"\u2500".repeat(
|
|
2486
|
+
typeWidth
|
|
2487
|
+
)} \u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`
|
|
2488
|
+
);
|
|
2489
|
+
for (const e of entries) {
|
|
2490
|
+
const status = e.installed ? "INSTALLED" : "\u2013";
|
|
2491
|
+
logger.info(
|
|
2492
|
+
`${e.id.padEnd(idWidth)} ${e.type.padEnd(
|
|
2493
|
+
typeWidth
|
|
2494
|
+
)} ${status.padEnd(7)} ${e.description}`
|
|
2495
|
+
);
|
|
2496
|
+
}
|
|
2497
|
+
logger.info("");
|
|
2498
|
+
logger.info(
|
|
2499
|
+
`Total: ${total} entr${total === 1 ? "y" : "ies"}, ${installedCount} installed.`
|
|
2500
|
+
);
|
|
2501
|
+
} catch (err) {
|
|
2502
|
+
logger.error(`Failed to list ui entries: ${err.message}`);
|
|
2503
|
+
logger.debug(err.stack ?? "");
|
|
2504
|
+
process.exitCode = 1;
|
|
2505
|
+
}
|
|
2506
|
+
});
|
|
2507
|
+
|
|
2508
|
+
// src/commands/ui/index.ts
|
|
2509
|
+
var uiCommand = new Command17("ui").description(
|
|
2510
|
+
"\u7BA1\u7406 teamix-evo ui \u7EC4\u4EF6\uFF08\u6E90\u7801\u6CE8\u5165\u5F0F\u5B89\u88C5\uFF0Cshadcn \u98CE\u683C\uFF09"
|
|
2511
|
+
);
|
|
2512
|
+
uiCommand.addCommand(initCommand2);
|
|
2513
|
+
uiCommand.addCommand(addCommand2);
|
|
2514
|
+
uiCommand.addCommand(listCommand3);
|
|
2515
|
+
|
|
2516
|
+
// src/commands/biz-ui/index.ts
|
|
2517
|
+
import { Command as Command20 } from "commander";
|
|
2518
|
+
|
|
2519
|
+
// src/commands/biz-ui/add.ts
|
|
2520
|
+
import { Command as Command18 } from "commander";
|
|
2521
|
+
|
|
2522
|
+
// src/core/variant-ui-add.ts
|
|
2523
|
+
import * as path16 from "path";
|
|
2524
|
+
import { createRequire as createRequire4 } from "module";
|
|
2525
|
+
import {
|
|
2526
|
+
loadVariantUiPackageCatalog,
|
|
2527
|
+
loadVariantUiPackageManifest
|
|
2528
|
+
} from "@teamix-evo/registry";
|
|
2529
|
+
var require5 = createRequire4(import.meta.url);
|
|
2530
|
+
function resolvePackageRoot3(packageName) {
|
|
2531
|
+
const pkgJsonPath = require5.resolve(`${packageName}/package.json`);
|
|
2532
|
+
return path16.dirname(pkgJsonPath);
|
|
2533
|
+
}
|
|
2534
|
+
async function runVariantUiAdd(packageName, options) {
|
|
2535
|
+
const { projectRoot, variant, ids, overwrite } = options;
|
|
2536
|
+
const fullPackageName = options.packageName ?? `@teamix-evo/${packageName}`;
|
|
2537
|
+
if (ids.length === 0) {
|
|
2538
|
+
throw new Error("At least one entry id must be provided.");
|
|
2539
|
+
}
|
|
2540
|
+
const config = await readProjectConfig(projectRoot);
|
|
2541
|
+
const uiCfg = config?.packages?.ui;
|
|
2542
|
+
if (!config || !uiCfg?.aliases) {
|
|
2543
|
+
throw new Error(
|
|
2544
|
+
`UI not initialized. Run \`teamix-evo ui init\` first \u2014 \`${packageName} add\` writes into the same alias map (business / templates).`
|
|
2545
|
+
);
|
|
2546
|
+
}
|
|
2547
|
+
const packageRoot = options.packageRoot ?? resolvePackageRoot3(fullPackageName);
|
|
2548
|
+
const catalog = await loadVariantUiPackageCatalog(packageRoot);
|
|
2549
|
+
if (!catalog.variants.some((v) => v.name === variant)) {
|
|
2550
|
+
const known = catalog.variants.map((v) => v.name).join(", ");
|
|
2551
|
+
throw new Error(
|
|
2552
|
+
`Variant "${variant}" not found in ${fullPackageName}. Known variants: ${known}. Hint: \`teamix-evo ${packageName} list-variants\` shows all.`
|
|
2553
|
+
);
|
|
2554
|
+
}
|
|
2555
|
+
const variantDir = path16.join(packageRoot, "variants", variant);
|
|
2556
|
+
const variantManifest = await loadVariantUiPackageManifest(variantDir);
|
|
2557
|
+
const knownIds = new Set(variantManifest.entries.map((e) => e.id));
|
|
2558
|
+
const unknown = ids.filter((id) => !knownIds.has(id));
|
|
2559
|
+
if (unknown.length > 0) {
|
|
2560
|
+
throw new Error(
|
|
2561
|
+
`Unknown entry id(s) in ${packageName}#${variant}: ${unknown.map((s) => `"${s}"`).join(", ")}. Run \`teamix-evo ${packageName} list --variant ${variant}\` to see options.`
|
|
2562
|
+
);
|
|
2563
|
+
}
|
|
2564
|
+
const adaptedManifest = {
|
|
2565
|
+
schemaVersion: 1,
|
|
2566
|
+
package: "ui",
|
|
2567
|
+
version: variantManifest.version,
|
|
2568
|
+
engines: variantManifest.engines,
|
|
2569
|
+
entries: variantManifest.entries
|
|
2570
|
+
};
|
|
2571
|
+
const result = await installUiEntries({
|
|
2572
|
+
projectRoot,
|
|
2573
|
+
manifest: adaptedManifest,
|
|
2574
|
+
packageRoot: variantDir,
|
|
2575
|
+
// sources resolved relative to variant dir
|
|
2576
|
+
aliases: uiCfg.aliases,
|
|
2577
|
+
requested: ids,
|
|
2578
|
+
skipExisting: !overwrite
|
|
2579
|
+
});
|
|
2580
|
+
const installed = await readInstalledManifest(
|
|
2581
|
+
projectRoot
|
|
2582
|
+
) ?? { schemaVersion: 1, installed: [] };
|
|
2583
|
+
const idx = installed.installed.findIndex(
|
|
2584
|
+
(p) => p.package === fullPackageName && p.variant === variant
|
|
2585
|
+
);
|
|
2586
|
+
const prior = idx >= 0 ? installed.installed[idx] : null;
|
|
2587
|
+
const mergedResources = mergeResources2(
|
|
2588
|
+
prior?.resources ?? [],
|
|
2589
|
+
result.resources
|
|
2590
|
+
);
|
|
2591
|
+
const entry = {
|
|
2592
|
+
package: fullPackageName,
|
|
2593
|
+
variant,
|
|
2594
|
+
version: variantManifest.version,
|
|
2595
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2596
|
+
resources: mergedResources
|
|
2597
|
+
};
|
|
2598
|
+
if (idx >= 0) installed.installed[idx] = entry;
|
|
2599
|
+
else installed.installed.push(entry);
|
|
2600
|
+
await writeInstalledManifest(projectRoot, installed);
|
|
2601
|
+
return {
|
|
2602
|
+
packageName: fullPackageName,
|
|
2603
|
+
variant,
|
|
2604
|
+
orderedIds: result.orderedIds,
|
|
2605
|
+
written: result.written,
|
|
2606
|
+
skipped: result.skipped,
|
|
2607
|
+
metaFiles: result.metaFiles,
|
|
2608
|
+
npmDependencies: result.npmDependencies,
|
|
2609
|
+
resources: result.resources
|
|
2610
|
+
};
|
|
2611
|
+
}
|
|
2612
|
+
function mergeResources2(prior, next) {
|
|
2613
|
+
const merged = /* @__PURE__ */ new Map();
|
|
2614
|
+
for (const r of prior) merged.set(r.id, r);
|
|
2615
|
+
for (const r of next) merged.set(r.id, r);
|
|
2616
|
+
return Array.from(merged.values());
|
|
2617
|
+
}
|
|
2618
|
+
async function runBizUiAdd(options) {
|
|
2619
|
+
return runVariantUiAdd("biz-ui", options);
|
|
2620
|
+
}
|
|
2621
|
+
async function runTemplatesAdd(options) {
|
|
2622
|
+
return runVariantUiAdd("templates", options);
|
|
2623
|
+
}
|
|
2624
|
+
async function listVariantUi(packageName, packageRoot) {
|
|
2625
|
+
const fullPackageName = `@teamix-evo/${packageName}`;
|
|
2626
|
+
const root = packageRoot ?? resolvePackageRoot3(fullPackageName);
|
|
2627
|
+
const catalog = await loadVariantUiPackageCatalog(root);
|
|
2628
|
+
return {
|
|
2629
|
+
packageName: fullPackageName,
|
|
2630
|
+
variants: catalog.variants.map((v) => ({
|
|
2631
|
+
name: v.name,
|
|
2632
|
+
displayName: v.displayName,
|
|
2633
|
+
version: v.version,
|
|
2634
|
+
description: v.description
|
|
2635
|
+
}))
|
|
2636
|
+
};
|
|
2637
|
+
}
|
|
2638
|
+
async function listBizUiVariants(packageRoot) {
|
|
2639
|
+
return listVariantUi("biz-ui", packageRoot);
|
|
2640
|
+
}
|
|
2641
|
+
async function listTemplatesVariants(packageRoot) {
|
|
2642
|
+
return listVariantUi("templates", packageRoot);
|
|
2643
|
+
}
|
|
2644
|
+
|
|
2645
|
+
// src/commands/biz-ui/add.ts
|
|
2646
|
+
var addCommand3 = new Command18("add").description(
|
|
2647
|
+
"\u5B89\u88C5\u4E00\u4E2A\u6216\u591A\u4E2A\u4E1A\u52A1 UI \u7EC4\u4EF6(\u6309 id,\u81EA\u52A8\u5C55\u5F00 ui \u5305\u7684 registryDependencies)"
|
|
2648
|
+
).argument("<ids...>", '\u7EC4\u4EF6 id \u5217\u8868,\u5982 "tenant-switcher" "org-picker"').option("--variant <name>", '\u53D8\u4F53 id(\u5FC5\u586B,\u5982 "opentrek"\u3001"uni-manager")').option("--overwrite", "\u5373\u4F7F\u76EE\u6807\u6587\u4EF6\u5DF2\u5B58\u5728\u4E5F\u8986\u76D6").action(
|
|
2649
|
+
async (ids, opts) => {
|
|
2650
|
+
try {
|
|
2651
|
+
if (!opts.variant) {
|
|
2652
|
+
throw new Error(
|
|
2653
|
+
"--variant <name> is required. Run `teamix-evo biz-ui list-variants` to see available variants."
|
|
2654
|
+
);
|
|
2655
|
+
}
|
|
2656
|
+
const ide = detectIde();
|
|
2657
|
+
const projectRoot = ide.getProjectRoot();
|
|
2658
|
+
logger.info(
|
|
2659
|
+
`Installing biz-ui entries from variant "${opts.variant}": ${ids.join(", ")}`
|
|
2660
|
+
);
|
|
2661
|
+
const result = await runBizUiAdd({
|
|
2662
|
+
projectRoot,
|
|
2663
|
+
variant: opts.variant,
|
|
2664
|
+
ids,
|
|
2665
|
+
overwrite: opts.overwrite
|
|
2666
|
+
});
|
|
2667
|
+
logger.success(
|
|
2668
|
+
`biz-ui add complete: ${result.written} written, ${result.skipped} skipped, ${result.metaFiles.length} meta.`
|
|
2669
|
+
);
|
|
2670
|
+
logger.info("");
|
|
2671
|
+
logger.info(`Variant: ${result.variant}`);
|
|
2672
|
+
logger.info(`Resolved order: ${result.orderedIds.join(" \u2192 ")}`);
|
|
2673
|
+
const npmDeps = Object.entries(result.npmDependencies);
|
|
2674
|
+
if (npmDeps.length > 0) {
|
|
2675
|
+
logger.info("");
|
|
2676
|
+
logger.info("Install npm dependencies in your project:");
|
|
2677
|
+
const installCmd = npmDeps.map(([name, range]) => `${name}@${range}`).join(" ");
|
|
2678
|
+
logger.info(` pnpm add ${installCmd}`);
|
|
2679
|
+
}
|
|
2680
|
+
} catch (err) {
|
|
2681
|
+
logger.error(`Failed: ${err.message}`);
|
|
2682
|
+
logger.debug(err.stack ?? "");
|
|
2683
|
+
process.exitCode = 1;
|
|
2684
|
+
}
|
|
2685
|
+
}
|
|
2686
|
+
);
|
|
2687
|
+
|
|
2688
|
+
// src/commands/biz-ui/list-variants.ts
|
|
2689
|
+
import { Command as Command19 } from "commander";
|
|
2690
|
+
var listVariantsCommand2 = new Command19("list-variants").description("\u5217\u51FA @teamix-evo/biz-ui \u5305\u5185\u63D0\u4F9B\u7684\u6240\u6709\u4E1A\u52A1\u53D8\u4F53").action(async () => {
|
|
2691
|
+
try {
|
|
2692
|
+
const result = await listBizUiVariants();
|
|
2693
|
+
logger.info(`Available biz-ui variants in ${result.packageName}:`);
|
|
2694
|
+
logger.info("");
|
|
2695
|
+
if (result.variants.length === 0) {
|
|
2696
|
+
logger.info(" (no variants yet)");
|
|
2697
|
+
return;
|
|
2698
|
+
}
|
|
2699
|
+
for (const v of result.variants) {
|
|
2700
|
+
logger.info(` ${v.name} (${v.displayName}) \u2014 v${v.version}`);
|
|
2701
|
+
if (v.description) logger.info(` ${v.description}`);
|
|
2702
|
+
logger.info("");
|
|
2703
|
+
}
|
|
2704
|
+
logger.info("Install from a variant: teamix-evo biz-ui add <id> --variant <name>");
|
|
2705
|
+
} catch (err) {
|
|
2706
|
+
logger.error(`Failed: ${err.message}`);
|
|
2707
|
+
process.exitCode = 1;
|
|
2708
|
+
}
|
|
2709
|
+
});
|
|
2710
|
+
|
|
2711
|
+
// src/commands/biz-ui/index.ts
|
|
2712
|
+
var bizUiCommand = new Command20("biz-ui").description(
|
|
2713
|
+
"\u7BA1\u7406\u4E1A\u52A1 UI \u7EC4\u4EF6(\u53D8\u4F53\u611F\u77E5 \u2014 \u4E0E design / templates \u540C\u53D8\u4F53\u540D\u7A7A\u95F4)"
|
|
2714
|
+
);
|
|
2715
|
+
bizUiCommand.addCommand(addCommand3);
|
|
2716
|
+
bizUiCommand.addCommand(listVariantsCommand2);
|
|
2717
|
+
|
|
2718
|
+
// src/commands/templates/index.ts
|
|
2719
|
+
import { Command as Command23 } from "commander";
|
|
2720
|
+
|
|
2721
|
+
// src/commands/templates/add.ts
|
|
2722
|
+
import { Command as Command21 } from "commander";
|
|
2723
|
+
var addCommand4 = new Command21("add").description(
|
|
2724
|
+
"\u5B89\u88C5\u4E00\u4E2A\u6216\u591A\u4E2A\u9875\u9762\u6A21\u677F(\u6309 id,\u81EA\u52A8\u5C55\u5F00 ui \u5305\u7684 registryDependencies)"
|
|
2725
|
+
).argument("<ids...>", '\u6A21\u677F id \u5217\u8868,\u5982 "list-detail-page"').option("--variant <name>", '\u53D8\u4F53 id(\u5FC5\u586B,\u5982 "opentrek"\u3001"uni-manager")').option("--overwrite", "\u5373\u4F7F\u76EE\u6807\u6587\u4EF6\u5DF2\u5B58\u5728\u4E5F\u8986\u76D6").action(
|
|
2726
|
+
async (ids, opts) => {
|
|
2727
|
+
try {
|
|
2728
|
+
if (!opts.variant) {
|
|
2729
|
+
throw new Error(
|
|
2730
|
+
"--variant <name> is required. Run `teamix-evo templates list-variants` to see available variants."
|
|
2731
|
+
);
|
|
2732
|
+
}
|
|
2733
|
+
const ide = detectIde();
|
|
2734
|
+
const projectRoot = ide.getProjectRoot();
|
|
2735
|
+
logger.info(
|
|
2736
|
+
`Installing templates from variant "${opts.variant}": ${ids.join(", ")}`
|
|
2737
|
+
);
|
|
2738
|
+
const result = await runTemplatesAdd({
|
|
2739
|
+
projectRoot,
|
|
2740
|
+
variant: opts.variant,
|
|
2741
|
+
ids,
|
|
2742
|
+
overwrite: opts.overwrite
|
|
2743
|
+
});
|
|
2744
|
+
logger.success(
|
|
2745
|
+
`templates add complete: ${result.written} written, ${result.skipped} skipped, ${result.metaFiles.length} meta.`
|
|
2746
|
+
);
|
|
2747
|
+
logger.info("");
|
|
2748
|
+
logger.info(`Variant: ${result.variant}`);
|
|
2749
|
+
logger.info(`Resolved order: ${result.orderedIds.join(" \u2192 ")}`);
|
|
2750
|
+
const npmDeps = Object.entries(result.npmDependencies);
|
|
2751
|
+
if (npmDeps.length > 0) {
|
|
2752
|
+
logger.info("");
|
|
2753
|
+
logger.info("Install npm dependencies in your project:");
|
|
2754
|
+
const installCmd = npmDeps.map(([name, range]) => `${name}@${range}`).join(" ");
|
|
2755
|
+
logger.info(` pnpm add ${installCmd}`);
|
|
2756
|
+
}
|
|
2757
|
+
} catch (err) {
|
|
2758
|
+
logger.error(`Failed: ${err.message}`);
|
|
2759
|
+
logger.debug(err.stack ?? "");
|
|
2760
|
+
process.exitCode = 1;
|
|
2761
|
+
}
|
|
2762
|
+
}
|
|
2763
|
+
);
|
|
2764
|
+
|
|
2765
|
+
// src/commands/templates/list-variants.ts
|
|
2766
|
+
import { Command as Command22 } from "commander";
|
|
2767
|
+
var listVariantsCommand3 = new Command22("list-variants").description("\u5217\u51FA @teamix-evo/templates \u5305\u5185\u63D0\u4F9B\u7684\u6240\u6709\u9875\u9762\u6A21\u677F\u53D8\u4F53").action(async () => {
|
|
2768
|
+
try {
|
|
2769
|
+
const result = await listTemplatesVariants();
|
|
2770
|
+
logger.info(`Available templates variants in ${result.packageName}:`);
|
|
2771
|
+
logger.info("");
|
|
2772
|
+
if (result.variants.length === 0) {
|
|
2773
|
+
logger.info(" (no variants yet)");
|
|
2774
|
+
return;
|
|
2775
|
+
}
|
|
2776
|
+
for (const v of result.variants) {
|
|
2777
|
+
logger.info(` ${v.name} (${v.displayName}) \u2014 v${v.version}`);
|
|
2778
|
+
if (v.description) logger.info(` ${v.description}`);
|
|
2779
|
+
logger.info("");
|
|
2780
|
+
}
|
|
2781
|
+
logger.info("Install from a variant: teamix-evo templates add <id> --variant <name>");
|
|
2782
|
+
} catch (err) {
|
|
2783
|
+
logger.error(`Failed: ${err.message}`);
|
|
2784
|
+
process.exitCode = 1;
|
|
2785
|
+
}
|
|
2786
|
+
});
|
|
2787
|
+
|
|
2788
|
+
// src/commands/templates/index.ts
|
|
2789
|
+
var templatesCommand = new Command23("templates").description(
|
|
2790
|
+
"\u7BA1\u7406\u9875\u9762\u6A21\u677F(\u53D8\u4F53\u611F\u77E5 \u2014 \u4E0E design / biz-ui \u540C\u53D8\u4F53\u540D\u7A7A\u95F4)"
|
|
2791
|
+
);
|
|
2792
|
+
templatesCommand.addCommand(addCommand4);
|
|
2793
|
+
templatesCommand.addCommand(listVariantsCommand3);
|
|
771
2794
|
|
|
772
2795
|
// src/index.ts
|
|
773
|
-
var
|
|
774
|
-
|
|
2796
|
+
var require6 = createRequire5(import.meta.url);
|
|
2797
|
+
var { version } = require6("../package.json");
|
|
2798
|
+
var program = new Command24();
|
|
2799
|
+
program.name("teamix-evo").description("Where ideas evolve. \u2014 AI Coding \u5957\u4EF6").version(version);
|
|
775
2800
|
program.addCommand(designCommand);
|
|
2801
|
+
program.addCommand(skillsCommand);
|
|
2802
|
+
program.addCommand(uiCommand);
|
|
2803
|
+
program.addCommand(bizUiCommand);
|
|
2804
|
+
program.addCommand(templatesCommand);
|
|
776
2805
|
program.parse();
|
|
777
2806
|
//# sourceMappingURL=index.js.map
|