cmyr-template-cli 1.45.5 → 1.46.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/dist/index.js CHANGED
@@ -23,13 +23,318 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
23
23
  ));
24
24
 
25
25
  // src/index.ts
26
- var import_path = __toESM(require("path"));
26
+ var import_path4 = __toESM(require("path"));
27
27
  var import_plop = require("plop");
28
28
  var import_commander = require("commander");
29
29
  var import_minimist = __toESM(require("minimist"));
30
+ var import_fs_extra3 = __toESM(require("fs-extra"));
31
+
32
+ // src/utils/config.ts
33
+ var import_path = __toESM(require("path"));
34
+ var import_os = __toESM(require("os"));
30
35
  var import_fs_extra = __toESM(require("fs-extra"));
36
+ var import_lodash = require("lodash");
37
+ async function loadTemplateCliConfig() {
38
+ const paths = [process.cwd(), import_os.default.homedir()].map((e) => import_path.default.join(e, ".ctrc"));
39
+ const [local, home] = (await Promise.all(paths.map(async (p) => {
40
+ try {
41
+ if (await import_fs_extra.default.pathExists(p)) {
42
+ return await import_fs_extra.default.readJSON(p);
43
+ }
44
+ return null;
45
+ } catch (error) {
46
+ console.error(error);
47
+ return null;
48
+ }
49
+ }))).filter(Boolean);
50
+ return (0, import_lodash.mergeWith)(home, local, (objValue, srcValue) => {
51
+ if (typeof objValue === "string" && srcValue === "") {
52
+ return objValue;
53
+ }
54
+ if (typeof srcValue !== "undefined" && srcValue !== null) {
55
+ return srcValue;
56
+ }
57
+ return objValue;
58
+ });
59
+ }
60
+
61
+ // src/commands/ai-update.ts
62
+ var import_path3 = __toESM(require("path"));
63
+
64
+ // src/utils/ai-scaffolding.ts
65
+ var import_path2 = __toESM(require("path"));
66
+ var import_os2 = __toESM(require("os"));
67
+ var import_crypto = __toESM(require("crypto"));
68
+ var import_ora = __toESM(require("ora"));
69
+ var import_axios = __toESM(require("axios"));
70
+ var import_adm_zip = __toESM(require("adm-zip"));
71
+ var import_fs_extra2 = __toESM(require("fs-extra"));
72
+
73
+ // src/utils/constants.ts
74
+ var GITHUB_API_URL = "https://api.github.com";
75
+ var REMOTES = [
76
+ "https://github.com",
77
+ "https://gh.flyinbug.top/gh/https://github.com",
78
+ "https://cors.isteed.cc/github.com",
79
+ "https://kgithub.com",
80
+ "https://ghproxy.1888866.xyz/https://github.com",
81
+ "https://hub.gitmirror.com/https://github.com",
82
+ "https://ghproxy.cfd/https://github.com",
83
+ "https://github.boki.moe/https://github.com",
84
+ "https://gh-proxy.net/https://github.com",
85
+ "https://gh.monlor.com/https://github.com",
86
+ "https://fastgit.cc/https://github.com",
87
+ "https://github.tbedu.top/https://github.com",
88
+ "https://ghfile.geekertao.top/https://github.com",
89
+ "https://ghp.keleyaa.com/https://github.com",
90
+ "https://ghpxy.hwinzniej.top/https://github.com",
91
+ "https://cdn.crashmc.com/https://github.com",
92
+ "https://git.yylx.win/https://github.com",
93
+ "https://gitproxy.mrhjx.cn/https://github.com",
94
+ "https://ghproxy.cxkpro.top/https://github.com",
95
+ "https://gh.xxooo.cf/https://github.com",
96
+ "https://gh.llkk.cc/https://github.com",
97
+ "https://raw.ihtw.moe/github.com",
98
+ "https://dgithub.xyz",
99
+ "https://gh.nxnow.top/https://github.com",
100
+ "https://gh.zwy.one/https://github.com",
101
+ "https://ghproxy.monkeyray.net/https://github.com",
102
+ "https://ghproxy.net/https://github.com",
103
+ "https://ghfast.top/https://github.com",
104
+ "https://wget.la/https://github.com",
105
+ "https://wget.la/https://github.com",
106
+ "https://hk.gh-proxy.com/https://github.com",
107
+ "https://ghfast.top/https://github.com",
108
+ "https://ghproxy.net/https://github.com"
109
+ ];
110
+
111
+ // src/utils/ai-scaffolding.ts
112
+ var DEFAULT_AI_SKILLS_REPOSITORY = "CaoMeiYouRen/cmyr-skills-agents";
113
+ var AI_MANIFEST_RELATIVE_PATH = ".ai/manifest.json";
114
+ function getAiSourceConfig(config) {
115
+ const localPath = config?.AI_SKILLS_LOCAL_PATH?.trim();
116
+ if (localPath) {
117
+ return {
118
+ type: "local",
119
+ localPath: import_path2.default.resolve(localPath)
120
+ };
121
+ }
122
+ return {
123
+ type: "github",
124
+ repository: config?.AI_SKILLS_REPOSITORY?.trim() || DEFAULT_AI_SKILLS_REPOSITORY
125
+ };
126
+ }
127
+ async function getGitHubLatestCommit(repository) {
128
+ try {
129
+ const response = await import_axios.default.get(`${GITHUB_API_URL}/repos/${repository}/commits/master`, {
130
+ headers: {
131
+ Accept: "application/vnd.github+json"
132
+ },
133
+ timeout: 15 * 1e3
134
+ });
135
+ return response.data?.sha || null;
136
+ } catch {
137
+ return null;
138
+ }
139
+ }
140
+ async function prepareAgentsSource(source) {
141
+ if (source.type === "local") {
142
+ const localPath = source.localPath || "";
143
+ if (!await import_fs_extra2.default.pathExists(localPath)) {
144
+ throw new Error(`AI 技能资产本地路径不存在: ${localPath}`);
145
+ }
146
+ return {
147
+ sourceDir: localPath,
148
+ source: { ...source },
149
+ cleanup: async () => void 0
150
+ };
151
+ }
152
+ const repository = source.repository || DEFAULT_AI_SKILLS_REPOSITORY;
153
+ const tempDir = import_path2.default.join(import_os2.default.tmpdir(), `cmyr-skills-agents-${(0, import_crypto.randomUUID)()}`);
154
+ try {
155
+ await downloadAgentsRepositoryZip(repository, tempDir);
156
+ const commit = await getGitHubLatestCommit(repository);
157
+ return {
158
+ sourceDir: tempDir,
159
+ source: { ...source, commit },
160
+ cleanup: async () => {
161
+ await import_fs_extra2.default.remove(tempDir);
162
+ }
163
+ };
164
+ } catch (error) {
165
+ await import_fs_extra2.default.remove(tempDir);
166
+ throw error;
167
+ }
168
+ }
169
+ async function downloadAgentsRepositoryZip(repository, destination) {
170
+ const loading = (0, import_ora.default)("正在下载 AI 技能资产仓库……").start();
171
+ try {
172
+ const candidates = REMOTES.map((remote) => `${remote}/${repository}/archive/refs/heads/master.zip`);
173
+ const response = await Promise.any(
174
+ candidates.map((url) => import_axios.default.get(url, {
175
+ responseType: "arraybuffer",
176
+ timeout: 30 * 1e3
177
+ }))
178
+ );
179
+ const zip = new import_adm_zip.default(Buffer.from(response.data));
180
+ const entries = zip.getEntries();
181
+ const topDir = entries[0]?.entryName.split("/")[0];
182
+ if (!topDir) {
183
+ throw new Error("无效的 zip 文件");
184
+ }
185
+ await import_fs_extra2.default.ensureDir(destination);
186
+ for (const entry of entries) {
187
+ if (entry.entryName.startsWith(`${topDir}/`)) {
188
+ const relativePath = entry.entryName.slice(topDir.length + 1);
189
+ if (!relativePath) {
190
+ continue;
191
+ }
192
+ const targetPath = import_path2.default.join(destination, relativePath);
193
+ const resolvedTarget = import_path2.default.resolve(targetPath);
194
+ const resolvedDestination = import_path2.default.resolve(destination);
195
+ const relative = import_path2.default.relative(resolvedDestination, resolvedTarget);
196
+ if (relative.startsWith("..") || import_path2.default.isAbsolute(relative)) {
197
+ throw new Error(`路径遍历攻击检测: ${relativePath}`);
198
+ }
199
+ if (entry.isDirectory) {
200
+ await import_fs_extra2.default.ensureDir(targetPath);
201
+ } else {
202
+ await import_fs_extra2.default.ensureDir(import_path2.default.dirname(targetPath));
203
+ await import_fs_extra2.default.writeFile(targetPath, entry.getData());
204
+ }
205
+ }
206
+ }
207
+ loading.succeed("AI 技能资产仓库下载成功!");
208
+ } catch (error) {
209
+ loading.fail("AI 技能资产仓库下载失败!");
210
+ throw error;
211
+ }
212
+ }
213
+ async function readL0Selection(sourceDir) {
214
+ const manifestPath = import_path2.default.join(sourceDir, "manifest.json");
215
+ const manifest = await import_fs_extra2.default.readJSON(manifestPath);
216
+ const l0 = manifest?.l0Selection;
217
+ if (!l0 || !Array.isArray(l0.skills) || !Array.isArray(l0.agents)) {
218
+ throw new Error("AI 技能资产仓库 manifest.json 缺少有效的 l0Selection");
219
+ }
220
+ return {
221
+ files: Array.isArray(l0.files) ? l0.files : [],
222
+ skills: l0.skills,
223
+ agents: l0.agents
224
+ };
225
+ }
226
+ async function computeFileHash(filePath) {
227
+ const content = await import_fs_extra2.default.readFile(filePath);
228
+ return import_crypto.default.createHash("sha256").update(content).digest("hex");
229
+ }
230
+ async function copyL0Selection(sourceDir, projectPath, l0) {
231
+ const hashes = {};
232
+ const githubSkillsDir = import_path2.default.join(projectPath, ".github/skills");
233
+ const githubAgentsDir = import_path2.default.join(projectPath, ".github/agents");
234
+ await import_fs_extra2.default.ensureDir(githubSkillsDir);
235
+ await import_fs_extra2.default.ensureDir(githubAgentsDir);
236
+ for (const skillName of l0.skills) {
237
+ const sourceSkillDir = import_path2.default.join(sourceDir, "skills", skillName);
238
+ if (!await import_fs_extra2.default.pathExists(sourceSkillDir)) {
239
+ console.warn(`警告: 技能 ${skillName} 在资产仓库中不存在,已跳过`);
240
+ continue;
241
+ }
242
+ const targetSkillDir = import_path2.default.join(githubSkillsDir, skillName);
243
+ await import_fs_extra2.default.copy(sourceSkillDir, targetSkillDir);
244
+ await collectHashes(targetSkillDir, import_path2.default.join(".github/skills", skillName), hashes);
245
+ }
246
+ for (const agentName of l0.agents) {
247
+ const sourceAgentFile = import_path2.default.join(sourceDir, "agents", `${agentName}.agent.md`);
248
+ if (!await import_fs_extra2.default.pathExists(sourceAgentFile)) {
249
+ console.warn(`警告: 代理 ${agentName} 在资产仓库中不存在,已跳过`);
250
+ continue;
251
+ }
252
+ const targetAgentFile = import_path2.default.join(githubAgentsDir, `${agentName}.agent.md`);
253
+ await import_fs_extra2.default.copyFile(sourceAgentFile, targetAgentFile);
254
+ const relativePath = `.github/agents/${agentName}.agent.md`;
255
+ hashes[relativePath] = await computeFileHash(targetAgentFile);
256
+ }
257
+ return hashes;
258
+ }
259
+ async function collectHashes(dir, dirRelativePath, hashes) {
260
+ const entries = await import_fs_extra2.default.readdir(dir, { withFileTypes: true });
261
+ for (const entry of entries) {
262
+ const fullPath = import_path2.default.join(dir, entry.name);
263
+ const relativePath = import_path2.default.join(dirRelativePath, entry.name).split(import_path2.default.sep).join("/");
264
+ if (entry.isDirectory()) {
265
+ await collectHashes(fullPath, relativePath, hashes);
266
+ } else if (entry.isFile()) {
267
+ hashes[relativePath] = await computeFileHash(fullPath);
268
+ }
269
+ }
270
+ }
271
+ async function writeAiManifest(projectPath, manifest) {
272
+ const manifestPath = import_path2.default.join(projectPath, AI_MANIFEST_RELATIVE_PATH);
273
+ await import_fs_extra2.default.ensureDir(import_path2.default.dirname(manifestPath));
274
+ await import_fs_extra2.default.writeJSON(manifestPath, manifest, { spaces: 4 });
275
+ }
276
+ async function readAiManifest(projectPath) {
277
+ const manifestPath = import_path2.default.join(projectPath, AI_MANIFEST_RELATIVE_PATH);
278
+ if (!await import_fs_extra2.default.pathExists(manifestPath)) {
279
+ return null;
280
+ }
281
+ try {
282
+ return await import_fs_extra2.default.readJSON(manifestPath);
283
+ } catch {
284
+ return null;
285
+ }
286
+ }
287
+ async function updateAiScaffolding(projectPath, config) {
288
+ const loading = (0, import_ora.default)("正在更新 AI 基建快照……").start();
289
+ const oldManifest = await readAiManifest(projectPath);
290
+ if (!oldManifest) {
291
+ loading.fail("未找到 .ai/manifest.json,请先通过脚手架初始化 AI 基建");
292
+ return;
293
+ }
294
+ try {
295
+ const source = getAiSourceConfig(config);
296
+ const prepared = await prepareAgentsSource(source);
297
+ try {
298
+ const l0 = await readL0Selection(prepared.sourceDir);
299
+ const oldSkills = oldManifest.l0Selection?.skills || [];
300
+ const oldAgents = oldManifest.l0Selection?.agents || [];
301
+ for (const skillName of oldSkills) {
302
+ if (!l0.skills.includes(skillName)) {
303
+ await import_fs_extra2.default.remove(import_path2.default.join(projectPath, ".github/skills", skillName));
304
+ }
305
+ }
306
+ for (const agentName of oldAgents) {
307
+ if (!l0.agents.includes(agentName)) {
308
+ await import_fs_extra2.default.remove(import_path2.default.join(projectPath, ".github/agents", `${agentName}.agent.md`));
309
+ }
310
+ }
311
+ const hashes = await copyL0Selection(prepared.sourceDir, projectPath, l0);
312
+ await writeAiManifest(projectPath, {
313
+ version: 1,
314
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
315
+ source: prepared.source,
316
+ l0Selection: l0,
317
+ links: oldManifest.links || [],
318
+ hashes
319
+ });
320
+ loading.succeed("AI 基建快照更新成功!");
321
+ } finally {
322
+ await prepared.cleanup();
323
+ }
324
+ } catch (error) {
325
+ loading.fail("AI 基建快照更新失败!");
326
+ throw error;
327
+ }
328
+ }
329
+
330
+ // src/commands/ai-update.ts
331
+ async function aiUpdateCommand(projectPath, config) {
332
+ await updateAiScaffolding(import_path3.default.resolve(projectPath), config);
333
+ }
334
+
335
+ // src/index.ts
31
336
  var program = new import_commander.Command("ct").description("草梅项目创建器");
32
- var pkg = import_fs_extra.default.readJSONSync(import_path.default.join(__dirname, "../package.json"));
337
+ var pkg = import_fs_extra3.default.readJSONSync(import_path4.default.join(__dirname, "../package.json"));
33
338
  program.version(pkg?.version || "1.0.0", "-v, --version");
34
339
  var args = process.argv.slice(2);
35
340
  if (args.length === 0) {
@@ -42,7 +347,7 @@ var create = new import_commander.Command("create").description("创建项目").
42
347
  import_plop.Plop.launch(
43
348
  {
44
349
  cwd: argv.cwd,
45
- configPath: import_path.default.resolve(__dirname, "./plopfile.js"),
350
+ configPath: import_path4.default.resolve(__dirname, "./plopfile.js"),
46
351
  require: argv.require,
47
352
  completion: argv.completion
48
353
  },
@@ -50,6 +355,17 @@ var create = new import_commander.Command("create").description("创建项目").
50
355
  );
51
356
  });
52
357
  program.addCommand(create);
358
+ var aiUpdate = new import_commander.Command("ai-update").description("更新 AI 基建快照(技能/代理/植入清单)").option("-p, --path <path>", "项目路径(默认当前目录)", process.cwd()).action(async (opts2) => {
359
+ try {
360
+ const config = await loadTemplateCliConfig();
361
+ await aiUpdateCommand(opts2.path, config);
362
+ process.exit(0);
363
+ } catch (error) {
364
+ console.error(error);
365
+ process.exit(1);
366
+ }
367
+ });
368
+ program.addCommand(aiUpdate);
53
369
  program.parse(process.argv);
54
370
  var opts = program.opts();
55
371
  if (opts.debug) {
package/dist/plopfile.js CHANGED
@@ -23,9 +23,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
23
23
  ));
24
24
 
25
25
  // src/plopfile.ts
26
- var import_path11 = __toESM(require("path"));
27
- var import_fs_extra12 = __toESM(require("fs-extra"));
28
- var import_ora11 = __toESM(require("ora"));
26
+ var import_path13 = __toESM(require("path"));
27
+ var import_fs_extra14 = __toESM(require("fs-extra"));
28
+ var import_ora12 = __toESM(require("ora"));
29
29
 
30
30
  // src/config/env.ts
31
31
  var env = process.env;
@@ -33,12 +33,12 @@ var __DEV__ = env.NODE_ENV === "development";
33
33
  var PACKAGE_MANAGER = "pnpm";
34
34
 
35
35
  // src/utils/utils.ts
36
- var import_path10 = __toESM(require("path"));
36
+ var import_path12 = __toESM(require("path"));
37
37
  var import_colors3 = __toESM(require("@colors/colors"));
38
- var import_ora10 = __toESM(require("ora"));
39
- var import_axios2 = __toESM(require("axios"));
40
- var import_adm_zip = __toESM(require("adm-zip"));
41
- var import_fs_extra11 = __toESM(require("fs-extra"));
38
+ var import_ora11 = __toESM(require("ora"));
39
+ var import_axios3 = __toESM(require("axios"));
40
+ var import_adm_zip2 = __toESM(require("adm-zip"));
41
+ var import_fs_extra13 = __toESM(require("fs-extra"));
42
42
 
43
43
  // src/utils/constants.ts
44
44
  var GITHUB_API_URL = "https://api.github.com";
@@ -2157,15 +2157,372 @@ async function initTest(projectPath, answers) {
2157
2157
  }
2158
2158
 
2159
2159
  // src/core/ai.ts
2160
+ var import_path11 = __toESM(require("path"));
2161
+ var import_ora10 = __toESM(require("ora"));
2162
+ var import_fs_extra12 = __toESM(require("fs-extra"));
2163
+
2164
+ // src/utils/symlink.ts
2160
2165
  var import_path9 = __toESM(require("path"));
2161
- var import_ora9 = __toESM(require("ora"));
2162
2166
  var import_fs_extra10 = __toESM(require("fs-extra"));
2163
- async function initAIScaffolding(projectPath, projectInfo) {
2164
- const loading = (0, import_ora9.default)("正在初始化 AI 开发配置……").start();
2167
+ function toSymlinkTarget(linkPath, targetPath, method) {
2168
+ if (process.platform === "win32" && method === "junction") {
2169
+ return import_path9.default.win32.resolve(targetPath);
2170
+ }
2171
+ return import_path9.default.relative(import_path9.default.dirname(linkPath), targetPath);
2172
+ }
2173
+ async function isSameLinkTarget(linkPath, targetPath) {
2174
+ try {
2175
+ const resolvedLink = await import_fs_extra10.default.realpath(linkPath);
2176
+ const resolvedTarget = await import_fs_extra10.default.realpath(targetPath);
2177
+ return resolvedLink === resolvedTarget;
2178
+ } catch {
2179
+ return false;
2180
+ }
2181
+ }
2182
+ async function createDirSymlink(linkPath, targetPath) {
2183
+ await import_fs_extra10.default.ensureDir(import_path9.default.dirname(linkPath));
2184
+ try {
2185
+ if (await isSameLinkTarget(linkPath, targetPath)) {
2186
+ return { linkPath, targetPath, method: "symlink", status: "existing" };
2187
+ }
2188
+ const existing = await import_fs_extra10.default.lstat(linkPath);
2189
+ if (existing.isDirectory() || existing.isFile()) {
2190
+ return { linkPath, targetPath, method: "symlink", status: "skipped" };
2191
+ }
2192
+ } catch (error) {
2193
+ if (error?.code !== "ENOENT") {
2194
+ throw error;
2195
+ }
2196
+ }
2197
+ let method = "symlink";
2198
+ if (process.platform === "win32") {
2199
+ try {
2200
+ await import_fs_extra10.default.symlink(import_path9.default.relative(import_path9.default.dirname(linkPath), targetPath), linkPath, "dir");
2201
+ return { linkPath, targetPath, method, status: "created" };
2202
+ } catch {
2203
+ method = "junction";
2204
+ await import_fs_extra10.default.symlink(toSymlinkTarget(linkPath, targetPath, method), linkPath, "junction");
2205
+ return { linkPath, targetPath, method, status: "created" };
2206
+ }
2207
+ }
2208
+ await import_fs_extra10.default.symlink(toSymlinkTarget(linkPath, targetPath, method), linkPath, "dir");
2209
+ return { linkPath, targetPath, method, status: "created" };
2210
+ }
2211
+ async function createFileSymlink(linkPath, targetPath) {
2212
+ await import_fs_extra10.default.ensureDir(import_path9.default.dirname(linkPath));
2213
+ try {
2214
+ if (await isSameLinkTarget(linkPath, targetPath)) {
2215
+ return { linkPath, targetPath, method: "symlink", status: "existing" };
2216
+ }
2217
+ const existing = await import_fs_extra10.default.lstat(linkPath);
2218
+ if (existing.isDirectory() || existing.isFile()) {
2219
+ return { linkPath, targetPath, method: "symlink", status: "skipped" };
2220
+ }
2221
+ } catch (error) {
2222
+ if (error?.code !== "ENOENT") {
2223
+ throw error;
2224
+ }
2225
+ }
2226
+ if (process.platform === "win32") {
2227
+ try {
2228
+ await import_fs_extra10.default.symlink(toSymlinkTarget(linkPath, targetPath, "symlink"), linkPath, "file");
2229
+ return { linkPath, targetPath, method: "symlink", status: "created" };
2230
+ } catch {
2231
+ await import_fs_extra10.default.copyFile(targetPath, linkPath);
2232
+ return { linkPath, targetPath, method: "copy", status: "created" };
2233
+ }
2234
+ }
2235
+ await import_fs_extra10.default.symlink(toSymlinkTarget(linkPath, targetPath, "symlink"), linkPath, "file");
2236
+ return { linkPath, targetPath, method: "symlink", status: "created" };
2237
+ }
2238
+
2239
+ // src/utils/ai-scaffolding.ts
2240
+ var import_path10 = __toESM(require("path"));
2241
+ var import_os2 = __toESM(require("os"));
2242
+ var import_crypto = __toESM(require("crypto"));
2243
+ var import_ora9 = __toESM(require("ora"));
2244
+ var import_axios2 = __toESM(require("axios"));
2245
+ var import_adm_zip = __toESM(require("adm-zip"));
2246
+ var import_fs_extra11 = __toESM(require("fs-extra"));
2247
+ var DEFAULT_AI_SKILLS_REPOSITORY = "CaoMeiYouRen/cmyr-skills-agents";
2248
+ var AI_MANIFEST_RELATIVE_PATH = ".ai/manifest.json";
2249
+ function getAiSourceConfig(config) {
2250
+ const localPath = config?.AI_SKILLS_LOCAL_PATH?.trim();
2251
+ if (localPath) {
2252
+ return {
2253
+ type: "local",
2254
+ localPath: import_path10.default.resolve(localPath)
2255
+ };
2256
+ }
2257
+ return {
2258
+ type: "github",
2259
+ repository: config?.AI_SKILLS_REPOSITORY?.trim() || DEFAULT_AI_SKILLS_REPOSITORY
2260
+ };
2261
+ }
2262
+ async function getGitHubLatestCommit(repository) {
2263
+ try {
2264
+ const response = await import_axios2.default.get(`${GITHUB_API_URL}/repos/${repository}/commits/master`, {
2265
+ headers: {
2266
+ Accept: "application/vnd.github+json"
2267
+ },
2268
+ timeout: 15 * 1e3
2269
+ });
2270
+ return response.data?.sha || null;
2271
+ } catch {
2272
+ return null;
2273
+ }
2274
+ }
2275
+ async function prepareAgentsSource(source) {
2276
+ if (source.type === "local") {
2277
+ const localPath = source.localPath || "";
2278
+ if (!await import_fs_extra11.default.pathExists(localPath)) {
2279
+ throw new Error(`AI 技能资产本地路径不存在: ${localPath}`);
2280
+ }
2281
+ return {
2282
+ sourceDir: localPath,
2283
+ source: { ...source },
2284
+ cleanup: async () => void 0
2285
+ };
2286
+ }
2287
+ const repository = source.repository || DEFAULT_AI_SKILLS_REPOSITORY;
2288
+ const tempDir = import_path10.default.join(import_os2.default.tmpdir(), `cmyr-skills-agents-${(0, import_crypto.randomUUID)()}`);
2289
+ try {
2290
+ await downloadAgentsRepositoryZip(repository, tempDir);
2291
+ const commit = await getGitHubLatestCommit(repository);
2292
+ return {
2293
+ sourceDir: tempDir,
2294
+ source: { ...source, commit },
2295
+ cleanup: async () => {
2296
+ await import_fs_extra11.default.remove(tempDir);
2297
+ }
2298
+ };
2299
+ } catch (error) {
2300
+ await import_fs_extra11.default.remove(tempDir);
2301
+ throw error;
2302
+ }
2303
+ }
2304
+ async function downloadAgentsRepositoryZip(repository, destination) {
2305
+ const loading = (0, import_ora9.default)("正在下载 AI 技能资产仓库……").start();
2306
+ try {
2307
+ const candidates = REMOTES.map((remote) => `${remote}/${repository}/archive/refs/heads/master.zip`);
2308
+ const response = await Promise.any(
2309
+ candidates.map((url) => import_axios2.default.get(url, {
2310
+ responseType: "arraybuffer",
2311
+ timeout: 30 * 1e3
2312
+ }))
2313
+ );
2314
+ const zip = new import_adm_zip.default(Buffer.from(response.data));
2315
+ const entries = zip.getEntries();
2316
+ const topDir = entries[0]?.entryName.split("/")[0];
2317
+ if (!topDir) {
2318
+ throw new Error("无效的 zip 文件");
2319
+ }
2320
+ await import_fs_extra11.default.ensureDir(destination);
2321
+ for (const entry of entries) {
2322
+ if (entry.entryName.startsWith(`${topDir}/`)) {
2323
+ const relativePath = entry.entryName.slice(topDir.length + 1);
2324
+ if (!relativePath) {
2325
+ continue;
2326
+ }
2327
+ const targetPath = import_path10.default.join(destination, relativePath);
2328
+ const resolvedTarget = import_path10.default.resolve(targetPath);
2329
+ const resolvedDestination = import_path10.default.resolve(destination);
2330
+ const relative = import_path10.default.relative(resolvedDestination, resolvedTarget);
2331
+ if (relative.startsWith("..") || import_path10.default.isAbsolute(relative)) {
2332
+ throw new Error(`路径遍历攻击检测: ${relativePath}`);
2333
+ }
2334
+ if (entry.isDirectory) {
2335
+ await import_fs_extra11.default.ensureDir(targetPath);
2336
+ } else {
2337
+ await import_fs_extra11.default.ensureDir(import_path10.default.dirname(targetPath));
2338
+ await import_fs_extra11.default.writeFile(targetPath, entry.getData());
2339
+ }
2340
+ }
2341
+ }
2342
+ loading.succeed("AI 技能资产仓库下载成功!");
2343
+ } catch (error) {
2344
+ loading.fail("AI 技能资产仓库下载失败!");
2345
+ throw error;
2346
+ }
2347
+ }
2348
+ async function readL0Selection(sourceDir) {
2349
+ const manifestPath = import_path10.default.join(sourceDir, "manifest.json");
2350
+ const manifest = await import_fs_extra11.default.readJSON(manifestPath);
2351
+ const l0 = manifest?.l0Selection;
2352
+ if (!l0 || !Array.isArray(l0.skills) || !Array.isArray(l0.agents)) {
2353
+ throw new Error("AI 技能资产仓库 manifest.json 缺少有效的 l0Selection");
2354
+ }
2355
+ return {
2356
+ files: Array.isArray(l0.files) ? l0.files : [],
2357
+ skills: l0.skills,
2358
+ agents: l0.agents
2359
+ };
2360
+ }
2361
+ async function readAgentsTemplate(sourceDir) {
2362
+ const templatePath = import_path10.default.join(sourceDir, "global/AGENTS.template.md");
2363
+ if (!await import_fs_extra11.default.pathExists(templatePath)) {
2364
+ return null;
2365
+ }
2366
+ return (await import_fs_extra11.default.readFile(templatePath, "utf8")).toString();
2367
+ }
2368
+ async function computeFileHash(filePath) {
2369
+ const content = await import_fs_extra11.default.readFile(filePath);
2370
+ return import_crypto.default.createHash("sha256").update(content).digest("hex");
2371
+ }
2372
+ async function copyL0Selection(sourceDir, projectPath, l0) {
2373
+ const hashes = {};
2374
+ const githubSkillsDir = import_path10.default.join(projectPath, ".github/skills");
2375
+ const githubAgentsDir = import_path10.default.join(projectPath, ".github/agents");
2376
+ await import_fs_extra11.default.ensureDir(githubSkillsDir);
2377
+ await import_fs_extra11.default.ensureDir(githubAgentsDir);
2378
+ for (const skillName of l0.skills) {
2379
+ const sourceSkillDir = import_path10.default.join(sourceDir, "skills", skillName);
2380
+ if (!await import_fs_extra11.default.pathExists(sourceSkillDir)) {
2381
+ console.warn(`警告: 技能 ${skillName} 在资产仓库中不存在,已跳过`);
2382
+ continue;
2383
+ }
2384
+ const targetSkillDir = import_path10.default.join(githubSkillsDir, skillName);
2385
+ await import_fs_extra11.default.copy(sourceSkillDir, targetSkillDir);
2386
+ await collectHashes(targetSkillDir, import_path10.default.join(".github/skills", skillName), hashes);
2387
+ }
2388
+ for (const agentName of l0.agents) {
2389
+ const sourceAgentFile = import_path10.default.join(sourceDir, "agents", `${agentName}.agent.md`);
2390
+ if (!await import_fs_extra11.default.pathExists(sourceAgentFile)) {
2391
+ console.warn(`警告: 代理 ${agentName} 在资产仓库中不存在,已跳过`);
2392
+ continue;
2393
+ }
2394
+ const targetAgentFile = import_path10.default.join(githubAgentsDir, `${agentName}.agent.md`);
2395
+ await import_fs_extra11.default.copyFile(sourceAgentFile, targetAgentFile);
2396
+ const relativePath = `.github/agents/${agentName}.agent.md`;
2397
+ hashes[relativePath] = await computeFileHash(targetAgentFile);
2398
+ }
2399
+ return hashes;
2400
+ }
2401
+ async function collectHashes(dir, dirRelativePath, hashes) {
2402
+ const entries = await import_fs_extra11.default.readdir(dir, { withFileTypes: true });
2403
+ for (const entry of entries) {
2404
+ const fullPath = import_path10.default.join(dir, entry.name);
2405
+ const relativePath = import_path10.default.join(dirRelativePath, entry.name).split(import_path10.default.sep).join("/");
2406
+ if (entry.isDirectory()) {
2407
+ await collectHashes(fullPath, relativePath, hashes);
2408
+ } else if (entry.isFile()) {
2409
+ hashes[relativePath] = await computeFileHash(fullPath);
2410
+ }
2411
+ }
2412
+ }
2413
+ async function writeAiManifest(projectPath, manifest) {
2414
+ const manifestPath = import_path10.default.join(projectPath, AI_MANIFEST_RELATIVE_PATH);
2415
+ await import_fs_extra11.default.ensureDir(import_path10.default.dirname(manifestPath));
2416
+ await import_fs_extra11.default.writeJSON(manifestPath, manifest, { spaces: 4 });
2417
+ }
2418
+
2419
+ // src/pure/agents-md.ts
2420
+ function replaceAgentsTemplateTodos(content, projectInfo) {
2421
+ let result = content;
2422
+ const description = projectInfo.projectDescription || projectInfo.description || "";
2423
+ if (description) {
2424
+ result = result.replace("<!-- TODO: 一句话描述项目目的 -->", description);
2425
+ }
2426
+ const testFramework = projectInfo.isInitTest;
2427
+ if (testFramework === "vitest" || testFramework === "jest") {
2428
+ result = result.replace("<!-- TODO: 指定最低覆盖率阈值,如 80% -->", "80%");
2429
+ }
2430
+ return result;
2431
+ }
2432
+ function buildAgentsMdL1Section(projectInfo, l0Skills = [], projectSummary) {
2433
+ const {
2434
+ language,
2435
+ runtime,
2436
+ vueVersion
2437
+ } = projectInfo.templateMeta || {};
2438
+ const packageManager = projectInfo.packageManager || "npm";
2439
+ const devCommand = projectInfo.devCommand;
2440
+ const testCommand = projectInfo.testCommand;
2441
+ const buildCommand = projectInfo.buildCommand;
2442
+ const lintCommand = projectInfo.lintCommand;
2443
+ const startCommand = projectInfo.startCommand;
2444
+ const commitCommand = projectInfo.commitCommand;
2445
+ const techStackLines = [
2446
+ `- 主要语言: ${language || "typescript"}`,
2447
+ `- 运行时: ${runtime || "nodejs"}`
2448
+ ];
2449
+ if (vueVersion === 2) {
2450
+ techStackLines.push("- 框架: Vue 2");
2451
+ } else if (vueVersion === 3) {
2452
+ techStackLines.push("- 框架: Vue 3");
2453
+ }
2454
+ techStackLines.push(`- 包管理器: ${packageManager}`);
2455
+ const commandLines = [
2456
+ `- 安装依赖: \`${packageManager} install\``
2457
+ ];
2458
+ if (devCommand) {
2459
+ commandLines.push(`- 启动开发环境: \`${devCommand}\``);
2460
+ }
2461
+ if (testCommand) {
2462
+ commandLines.push(`- 运行测试: \`${testCommand}\``);
2463
+ }
2464
+ if (buildCommand) {
2465
+ commandLines.push(`- 构建项目: \`${buildCommand}\``);
2466
+ }
2467
+ if (lintCommand) {
2468
+ commandLines.push(`- 代码检查: \`${lintCommand}\``);
2469
+ }
2470
+ if (startCommand) {
2471
+ commandLines.push(`- 启动生产或本地预览: \`${startCommand}\``);
2472
+ }
2473
+ if (commitCommand) {
2474
+ commandLines.push(`- 生成提交: \`${commitCommand}\``);
2475
+ }
2476
+ const skillIndexLines = l0Skills.length > 0 ? l0Skills.map((skill) => `- ${skill}`) : ["- 未植入技能(L0 精选清单为空)"];
2477
+ const summary = projectSummary?.summary || projectInfo.projectDescription || projectInfo.description || "";
2478
+ const featureLines = projectSummary?.features?.length ? projectSummary.features.map((feature) => `- ${feature}`) : [];
2479
+ const overviewLines = summary ? [summary, ...featureLines.length > 0 ? ["", "主要特性:", ...featureLines] : []] : ["(项目概述待补充)"];
2480
+ return [
2481
+ "---",
2482
+ "",
2483
+ "## 项目信息(由 cmyr-template-cli 生成)",
2484
+ "",
2485
+ "> 本节内容由脚手架基于 ProjectInfo 参数注入,如需调整请直接修改。",
2486
+ "",
2487
+ "### 项目概述",
2488
+ ...overviewLines,
2489
+ "",
2490
+ "### 技术栈",
2491
+ ...techStackLines,
2492
+ "",
2493
+ "### 常用命令",
2494
+ ...commandLines,
2495
+ "",
2496
+ "### AI 技能索引",
2497
+ "",
2498
+ "> 技能与代理已快照植入 `.github/skills/`、`.github/agents/`,并被链接到各 AI 工具目录(.claude/.opencode/.agents 等)。",
2499
+ ...skillIndexLines,
2500
+ "",
2501
+ "### 植入来源",
2502
+ "",
2503
+ "> 快照来源与校验记录见 `.ai/manifest.json`;更新命令:`ct ai-update`。",
2504
+ ""
2505
+ ].join("\n");
2506
+ }
2507
+
2508
+ // src/core/ai.ts
2509
+ var AI_DIR_LINK_MAPPINGS = [
2510
+ { linkRelPath: ".claude/skills", targetRelPath: ".github/skills" },
2511
+ { linkRelPath: ".claude/agents", targetRelPath: ".github/agents" },
2512
+ { linkRelPath: ".opencode/skills", targetRelPath: ".github/skills" },
2513
+ { linkRelPath: ".opencode/agents", targetRelPath: ".github/agents" },
2514
+ { linkRelPath: ".agents/skills", targetRelPath: ".github/skills" },
2515
+ { linkRelPath: ".agents/agents", targetRelPath: ".github/agents" }
2516
+ ];
2517
+ var AI_FILE_LINK_MAPPINGS = [
2518
+ { linkRelPath: "CLAUDE.md", targetRelPath: "AGENTS.md" }
2519
+ ];
2520
+ async function initAIScaffolding(projectPath, projectInfo, config) {
2521
+ const loading = (0, import_ora10.default)("正在初始化 AI 开发配置……").start();
2165
2522
  try {
2166
2523
  const aiTools = projectInfo.aiTools ?? ["claude", "copilot"];
2167
2524
  if (aiTools.includes("claude")) {
2168
- await initAgentsMd(projectPath, projectInfo);
2525
+ await initAgentsMdScaffolding(projectPath, projectInfo, config);
2169
2526
  await initClaudeDirectory(projectPath);
2170
2527
  }
2171
2528
  if (aiTools.includes("copilot")) {
@@ -2184,59 +2541,118 @@ async function initAIScaffolding(projectPath, projectInfo) {
2184
2541
  console.error(error);
2185
2542
  }
2186
2543
  }
2187
- async function initAgentsMd(projectPath, projectInfo) {
2188
- const loading = (0, import_ora9.default)("正在生成 AGENTS.md……").start();
2544
+ async function initAgentsMdScaffolding(projectPath, projectInfo, config) {
2545
+ let prepared = null;
2189
2546
  try {
2190
- const outputPath = import_path9.default.join(projectPath, "AGENTS.md");
2191
- if (await import_fs_extra10.default.pathExists(outputPath)) {
2547
+ prepared = await prepareAgentsSource(getAiSourceConfig(config));
2548
+ const l0 = await readL0Selection(prepared.sourceDir);
2549
+ await initAgentsMd(projectPath, projectInfo, prepared.sourceDir, l0);
2550
+ const hashes = await initSkillsSnapshot(projectPath, prepared.sourceDir, l0);
2551
+ const links = await initAgentLinkDirs(projectPath, projectInfo);
2552
+ await writeAiManifest(projectPath, {
2553
+ version: 1,
2554
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
2555
+ source: prepared.source,
2556
+ l0Selection: l0,
2557
+ links,
2558
+ hashes
2559
+ });
2560
+ } finally {
2561
+ if (prepared) {
2562
+ await prepared.cleanup();
2563
+ }
2564
+ }
2565
+ }
2566
+ async function initAgentsMd(projectPath, projectInfo, sourceDir, l0) {
2567
+ const loading = (0, import_ora10.default)("正在生成 AGENTS.md……").start();
2568
+ try {
2569
+ const outputPath = import_path11.default.join(projectPath, "AGENTS.md");
2570
+ if (await import_fs_extra12.default.pathExists(outputPath)) {
2192
2571
  loading.stopAndPersist({
2193
2572
  text: "AGENTS.md 已存在,跳过生成",
2194
2573
  symbol: "⊙"
2195
2574
  });
2196
2575
  return;
2197
2576
  }
2198
- const templatePath = import_path9.default.join(__dirname, "../templates/AGENTS.md.ejs");
2199
- const templateData = {
2200
- projectDescription: projectInfo.projectDescription || projectInfo.description || "",
2201
- language: projectInfo.templateMeta?.language || "typescript",
2202
- runtime: projectInfo.templateMeta?.runtime || "nodejs",
2203
- vueVersion: projectInfo.templateMeta?.vueVersion || 0,
2204
- packageManager: projectInfo.packageManager || "npm",
2205
- repositoryUrl: projectInfo.repositoryUrl || "",
2206
- documentationUrl: projectInfo.documentationUrl || "",
2207
- issuesUrl: projectInfo.issuesUrl || "",
2208
- contributingUrl: projectInfo.contributingUrl || "",
2209
- isInitTest: projectInfo.isInitTest || "none",
2210
- devCommand: projectInfo.devCommand,
2211
- testCommand: projectInfo.testCommand,
2212
- buildCommand: projectInfo.buildCommand,
2213
- lintCommand: projectInfo.lintCommand,
2214
- startCommand: projectInfo.startCommand,
2215
- commitCommand: projectInfo.commitCommand
2216
- };
2217
- await ejsRender(templatePath, templateData, outputPath);
2577
+ if (!sourceDir) {
2578
+ throw new Error("未提供 AI 技能资产源,无法生成 AGENTS.md");
2579
+ }
2580
+ const templateContent = await readAgentsTemplate(sourceDir);
2581
+ if (!templateContent) {
2582
+ throw new Error("资产仓库缺少 global/AGENTS.template.md,无法生成 AGENTS.md");
2583
+ }
2584
+ const l0Content = replaceAgentsTemplateTodos(templateContent, projectInfo);
2585
+ const l1Section = buildAgentsMdL1Section(projectInfo, l0?.skills || [], projectInfo.aiGeneratedSummary);
2586
+ await import_fs_extra12.default.writeFile(outputPath, `${l0Content.trimEnd()}
2587
+
2588
+ ${l1Section}`);
2218
2589
  loading.succeed("AGENTS.md 生成成功!");
2219
2590
  } catch (error) {
2220
2591
  loading.fail("AGENTS.md 生成失败!");
2221
2592
  throw error;
2222
2593
  }
2223
2594
  }
2595
+ async function initSkillsSnapshot(projectPath, sourceDir, l0) {
2596
+ const loading = (0, import_ora10.default)("正在快照植入 AI 技能与代理……").start();
2597
+ try {
2598
+ const hashes = await copyL0Selection(sourceDir, projectPath, l0);
2599
+ loading.succeed("AI 技能与代理快照植入成功!");
2600
+ return hashes;
2601
+ } catch (error) {
2602
+ loading.fail("AI 技能与代理快照植入失败!");
2603
+ throw error;
2604
+ }
2605
+ }
2606
+ async function initAgentLinkDirs(projectPath, projectInfo) {
2607
+ const loading = (0, import_ora10.default)("正在创建跨 agent 链接……").start();
2608
+ try {
2609
+ const links = [];
2610
+ for (const mapping of AI_DIR_LINK_MAPPINGS) {
2611
+ const linkPath = import_path11.default.join(projectPath, mapping.linkRelPath);
2612
+ const targetPath = import_path11.default.join(projectPath, mapping.targetRelPath);
2613
+ const result = await createDirSymlink(linkPath, targetPath);
2614
+ links.push({
2615
+ linkRelPath: mapping.linkRelPath,
2616
+ targetRelPath: mapping.targetRelPath,
2617
+ method: result.method
2618
+ });
2619
+ }
2620
+ const isClaude = projectInfo.aiTools?.includes("claude");
2621
+ if (isClaude) {
2622
+ for (const mapping of AI_FILE_LINK_MAPPINGS) {
2623
+ const linkPath = import_path11.default.join(projectPath, mapping.linkRelPath);
2624
+ const targetPath = import_path11.default.join(projectPath, mapping.targetRelPath);
2625
+ const result = await createFileSymlink(linkPath, targetPath);
2626
+ links.push({
2627
+ linkRelPath: mapping.linkRelPath,
2628
+ targetRelPath: mapping.targetRelPath,
2629
+ method: result.method
2630
+ });
2631
+ }
2632
+ }
2633
+ loading.succeed("跨 agent 链接创建成功!");
2634
+ return links;
2635
+ } catch (error) {
2636
+ loading.fail("跨 agent 链接创建失败!");
2637
+ throw error;
2638
+ }
2639
+ }
2224
2640
  async function initCopilotInstructions(projectPath) {
2225
- const loading = (0, import_ora9.default)("正在生成 .github/copilot-instructions.md……").start();
2641
+ const loading = (0, import_ora10.default)("正在生成 .github/copilot-instructions.md……").start();
2226
2642
  try {
2227
- const outputPath = import_path9.default.join(projectPath, ".github/copilot-instructions.md");
2228
- if (await import_fs_extra10.default.pathExists(outputPath)) {
2643
+ const outputPath = import_path11.default.join(projectPath, ".github/copilot-instructions.md");
2644
+ if (await import_fs_extra12.default.pathExists(outputPath)) {
2229
2645
  loading.stopAndPersist({
2230
2646
  text: ".github/copilot-instructions.md 已存在,跳过生成",
2231
2647
  symbol: "⊙"
2232
2648
  });
2233
2649
  return;
2234
2650
  }
2235
- const githubDir = import_path9.default.join(projectPath, ".github");
2236
- if (!await import_fs_extra10.default.pathExists(githubDir)) {
2237
- await import_fs_extra10.default.mkdirp(githubDir);
2651
+ const githubDir = import_path11.default.join(projectPath, ".github");
2652
+ if (!await import_fs_extra12.default.pathExists(githubDir)) {
2653
+ await import_fs_extra12.default.mkdirp(githubDir);
2238
2654
  }
2239
- const templatePath = import_path9.default.join(__dirname, "../templates/.github/copilot-instructions.md.ejs");
2655
+ const templatePath = import_path11.default.join(__dirname, "../templates/.github/copilot-instructions.md.ejs");
2240
2656
  await ejsRender(templatePath, {}, outputPath);
2241
2657
  loading.succeed(".github/copilot-instructions.md 生成成功!");
2242
2658
  } catch (error) {
@@ -2245,17 +2661,17 @@ async function initCopilotInstructions(projectPath) {
2245
2661
  }
2246
2662
  }
2247
2663
  async function initCursorRules(projectPath) {
2248
- const loading = (0, import_ora9.default)("正在生成 .cursorrules……").start();
2664
+ const loading = (0, import_ora10.default)("正在生成 .cursorrules……").start();
2249
2665
  try {
2250
- const outputPath = import_path9.default.join(projectPath, ".cursorrules");
2251
- if (await import_fs_extra10.default.pathExists(outputPath)) {
2666
+ const outputPath = import_path11.default.join(projectPath, ".cursorrules");
2667
+ if (await import_fs_extra12.default.pathExists(outputPath)) {
2252
2668
  loading.stopAndPersist({
2253
2669
  text: ".cursorrules 已存在,跳过生成",
2254
2670
  symbol: "⊙"
2255
2671
  });
2256
2672
  return;
2257
2673
  }
2258
- const templatePath = import_path9.default.join(__dirname, "../templates/.cursorrules.ejs");
2674
+ const templatePath = import_path11.default.join(__dirname, "../templates/.cursorrules.ejs");
2259
2675
  await ejsRender(templatePath, {}, outputPath);
2260
2676
  loading.succeed(".cursorrules 生成成功!");
2261
2677
  } catch (error) {
@@ -2264,17 +2680,17 @@ async function initCursorRules(projectPath) {
2264
2680
  }
2265
2681
  }
2266
2682
  async function initWindsurfRules(projectPath) {
2267
- const loading = (0, import_ora9.default)("正在生成 .windsurfrules……").start();
2683
+ const loading = (0, import_ora10.default)("正在生成 .windsurfrules……").start();
2268
2684
  try {
2269
- const outputPath = import_path9.default.join(projectPath, ".windsurfrules");
2270
- if (await import_fs_extra10.default.pathExists(outputPath)) {
2685
+ const outputPath = import_path11.default.join(projectPath, ".windsurfrules");
2686
+ if (await import_fs_extra12.default.pathExists(outputPath)) {
2271
2687
  loading.stopAndPersist({
2272
2688
  text: ".windsurfrules 已存在,跳过生成",
2273
2689
  symbol: "⊙"
2274
2690
  });
2275
2691
  return;
2276
2692
  }
2277
- const templatePath = import_path9.default.join(__dirname, "../templates/.windsurfrules.ejs");
2693
+ const templatePath = import_path11.default.join(__dirname, "../templates/.windsurfrules.ejs");
2278
2694
  await ejsRender(templatePath, {}, outputPath);
2279
2695
  loading.succeed(".windsurfrules 生成成功!");
2280
2696
  } catch (error) {
@@ -2283,18 +2699,17 @@ async function initWindsurfRules(projectPath) {
2283
2699
  }
2284
2700
  }
2285
2701
  async function initClaudeDirectory(projectPath) {
2286
- const loading = (0, import_ora9.default)("正在初始化 .claude/ 目录……").start();
2702
+ const loading = (0, import_ora10.default)("正在初始化 .claude/ 目录……").start();
2287
2703
  try {
2288
- const claudeDir = import_path9.default.join(projectPath, ".claude");
2289
- if (await import_fs_extra10.default.pathExists(claudeDir)) {
2704
+ const settingsPath = import_path11.default.join(projectPath, ".claude/settings.json");
2705
+ if (await import_fs_extra12.default.pathExists(settingsPath)) {
2290
2706
  loading.stopAndPersist({
2291
- text: ".claude/ 目录已存在,跳过初始化",
2707
+ text: ".claude/settings.json 已存在,跳过初始化",
2292
2708
  symbol: "⊙"
2293
2709
  });
2294
2710
  return;
2295
2711
  }
2296
- await import_fs_extra10.default.mkdirp(import_path9.default.join(claudeDir, "skills"));
2297
- await import_fs_extra10.default.mkdirp(import_path9.default.join(claudeDir, "agents"));
2712
+ await import_fs_extra12.default.mkdirp(import_path11.default.join(projectPath, ".claude"));
2298
2713
  const files = [".claude/settings.json"];
2299
2714
  await copyFilesFromTemplates(projectPath, files, true);
2300
2715
  loading.succeed(".claude/ 目录初始化成功!");
@@ -2304,17 +2719,17 @@ async function initClaudeDirectory(projectPath) {
2304
2719
  }
2305
2720
  }
2306
2721
  async function initCursorDirectory(projectPath) {
2307
- const loading = (0, import_ora9.default)("正在初始化 .cursor/ 目录……").start();
2722
+ const loading = (0, import_ora10.default)("正在初始化 .cursor/ 目录……").start();
2308
2723
  try {
2309
- const cursorDir = import_path9.default.join(projectPath, ".cursor", "rules");
2310
- if (await import_fs_extra10.default.pathExists(cursorDir)) {
2724
+ const cursorDir = import_path11.default.join(projectPath, ".cursor", "rules");
2725
+ if (await import_fs_extra12.default.pathExists(cursorDir)) {
2311
2726
  loading.stopAndPersist({
2312
2727
  text: ".cursor/ 目录已存在,跳过初始化",
2313
2728
  symbol: "⊙"
2314
2729
  });
2315
2730
  return;
2316
2731
  }
2317
- await import_fs_extra10.default.mkdirp(cursorDir);
2732
+ await import_fs_extra12.default.mkdirp(cursorDir);
2318
2733
  loading.succeed(".cursor/ 目录初始化成功!");
2319
2734
  } catch (error) {
2320
2735
  loading.fail(".cursor/ 目录初始化失败!");
@@ -2325,7 +2740,7 @@ async function initCursorDirectory(projectPath) {
2325
2740
  // src/utils/utils.ts
2326
2741
  async function downloadGitRepo(repository, destination) {
2327
2742
  const fastRepo = await getFastGitRepo(repository);
2328
- const loading = (0, import_ora10.default)(`正在下载模板 - ${repository}`);
2743
+ const loading = (0, import_ora11.default)(`正在下载模板 - ${repository}`);
2329
2744
  loading.start();
2330
2745
  return Promise.any([
2331
2746
  downloadAndExtractZip(fastRepo, destination, loading, repository),
@@ -2334,31 +2749,31 @@ async function downloadGitRepo(repository, destination) {
2334
2749
  }
2335
2750
  async function downloadAndExtractZip(url, destination, loading, repository) {
2336
2751
  try {
2337
- const response = await import_axios2.default.get(url, { responseType: "arraybuffer" });
2752
+ const response = await import_axios3.default.get(url, { responseType: "arraybuffer" });
2338
2753
  const buffer = Buffer.from(response.data);
2339
- const zip = new import_adm_zip.default(buffer);
2754
+ const zip = new import_adm_zip2.default(buffer);
2340
2755
  const entries = zip.getEntries();
2341
2756
  const topDir = entries[0]?.entryName.split("/")[0];
2342
2757
  if (!topDir) {
2343
2758
  throw new Error("无效的 zip 文件");
2344
2759
  }
2345
- await import_fs_extra11.default.ensureDir(destination);
2760
+ await import_fs_extra13.default.ensureDir(destination);
2346
2761
  for (const entry of entries) {
2347
2762
  if (entry.entryName.startsWith(`${topDir}/`)) {
2348
2763
  const relativePath = entry.entryName.slice(topDir.length + 1);
2349
2764
  if (!relativePath) {
2350
2765
  continue;
2351
2766
  }
2352
- const targetPath = import_path10.default.join(destination, relativePath);
2353
- const resolvedPath = import_path10.default.resolve(targetPath);
2354
- if (!resolvedPath.startsWith(import_path10.default.resolve(destination))) {
2767
+ const targetPath = import_path12.default.join(destination, relativePath);
2768
+ const resolvedPath = import_path12.default.resolve(targetPath);
2769
+ if (!resolvedPath.startsWith(import_path12.default.resolve(destination))) {
2355
2770
  throw new Error(`路径遍历攻击检测: ${relativePath}`);
2356
2771
  }
2357
2772
  if (entry.isDirectory) {
2358
- await import_fs_extra11.default.ensureDir(targetPath);
2773
+ await import_fs_extra13.default.ensureDir(targetPath);
2359
2774
  } else {
2360
- await import_fs_extra11.default.ensureDir(import_path10.default.dirname(targetPath));
2361
- await import_fs_extra11.default.writeFile(targetPath, entry.getData());
2775
+ await import_fs_extra13.default.ensureDir(import_path12.default.dirname(targetPath));
2776
+ await import_fs_extra13.default.writeFile(targetPath, entry.getData());
2362
2777
  }
2363
2778
  }
2364
2779
  }
@@ -2370,7 +2785,7 @@ async function downloadAndExtractZip(url, destination, loading, repository) {
2370
2785
  }
2371
2786
  }
2372
2787
  async function getFastGitRepo(repository) {
2373
- const loading = (0, import_ora10.default)(`正在选择镜像源 - ${repository}`);
2788
+ const loading = (0, import_ora11.default)(`正在选择镜像源 - ${repository}`);
2374
2789
  loading.start();
2375
2790
  try {
2376
2791
  const fastUrl = await getFastUrl(REMOTES.map((remote) => `${remote}/${repository}/archive/refs/heads/master.zip`));
@@ -2385,7 +2800,7 @@ async function getFastGitRepo(repository) {
2385
2800
  async function initProject(answers) {
2386
2801
  const typedAnswers = answers;
2387
2802
  const { name, template } = typedAnswers;
2388
- const projectPath = import_path10.default.join(process.cwd(), name);
2803
+ const projectPath = import_path12.default.join(process.cwd(), name);
2389
2804
  await downloadGitRepo(`CaoMeiYouRen/${template}`, projectPath);
2390
2805
  await init(projectPath, typedAnswers);
2391
2806
  return "- 下载项目模板成功!";
@@ -2537,7 +2952,7 @@ async function getGitUserName() {
2537
2952
  }
2538
2953
 
2539
2954
  // src/utils/ai-api.ts
2540
- var import_axios3 = __toESM(require("axios"));
2955
+ var import_axios4 = __toESM(require("axios"));
2541
2956
 
2542
2957
  // src/pure/ai.ts
2543
2958
  var MAX_USER_INPUT_LENGTH = 500;
@@ -2596,6 +3011,49 @@ function parseAIResponse(response) {
2596
3011
  return null;
2597
3012
  }
2598
3013
  }
3014
+ function buildProjectSummaryPrompt(projectInfo) {
3015
+ if (typeof projectInfo.description !== "string" || !projectInfo.description.trim()) {
3016
+ throw new Error("buildProjectSummaryPrompt: projectInfo.description is required");
3017
+ }
3018
+ return `你是一个项目文档撰写助手。基于以下项目信息,生成 README 项目简介:
3019
+
3020
+ 项目名称: ${projectInfo.name}
3021
+ 项目描述: ${projectInfo.description}
3022
+ 关键词: ${projectInfo.keywords.join(", ") || "无"}
3023
+
3024
+ 要求:
3025
+ 1. **summary**:一段精炼的中文项目简介(80-150 字),突出项目价值,适合 README 引言
3026
+ 2. **features**:3-5 个主要特性,每条不超过 15 字
3027
+
3028
+ 请以 JSON 格式返回(不要包含 markdown 代码块标记):
3029
+ {
3030
+ "summary": "项目简介",
3031
+ "features": ["特性1", "特性2"]
3032
+ }`;
3033
+ }
3034
+ function parseProjectSummary(response) {
3035
+ if (typeof response !== "string") {
3036
+ return null;
3037
+ }
3038
+ try {
3039
+ let jsonStr = response.trim();
3040
+ const codeBlockRegex = /```(?:json)?\s*([\s\S]*?)\s*```/;
3041
+ const match = jsonStr.match(codeBlockRegex);
3042
+ if (match && match[1]) {
3043
+ jsonStr = match[1].trim();
3044
+ }
3045
+ const parsed = JSON.parse(jsonStr);
3046
+ if (!parsed || typeof parsed !== "object" || typeof parsed.summary !== "string" || parsed.summary.trim().length === 0 || !Array.isArray(parsed.features) || parsed.features.length === 0) {
3047
+ return null;
3048
+ }
3049
+ return {
3050
+ summary: parsed.summary,
3051
+ features: parsed.features.filter((feature) => typeof feature === "string").slice(0, 5)
3052
+ };
3053
+ } catch {
3054
+ return null;
3055
+ }
3056
+ }
2599
3057
 
2600
3058
  // src/utils/ai-api.ts
2601
3059
  var AI_TIMEOUT = 30 * 1e3;
@@ -2620,7 +3078,7 @@ async function chatCompletion(request) {
2620
3078
  throw new Error("AI_API_BASE must use HTTPS for security (except for localhost)");
2621
3079
  }
2622
3080
  try {
2623
- const response = await import_axios3.default.post(
3081
+ const response = await import_axios4.default.post(
2624
3082
  endpoint,
2625
3083
  {
2626
3084
  model,
@@ -2646,7 +3104,7 @@ async function chatCompletion(request) {
2646
3104
  }
2647
3105
  return content;
2648
3106
  } catch (error) {
2649
- if (import_axios3.default.isAxiosError(error)) {
3107
+ if (import_axios4.default.isAxiosError(error)) {
2650
3108
  const axiosError = error;
2651
3109
  if (axiosError.code === "ECONNABORTED" || axiosError.message.includes("timeout")) {
2652
3110
  throw new Error(`AI API request timed out after ${AI_TIMEOUT / 1e3} seconds. Please try again.`);
@@ -2691,6 +3149,29 @@ async function getAIProjectSuggestion(userInput, config) {
2691
3149
  }
2692
3150
  return suggestion;
2693
3151
  }
3152
+ async function getAIProjectSummary(projectInfo, config) {
3153
+ const { AI_API_BASE, AI_API_KEY, AI_MODEL } = config;
3154
+ if (!AI_API_KEY) {
3155
+ throw new Error(
3156
+ 'AI_API_KEY is not configured. Please add it to your .ctrc file:\n "AI_API_KEY": "your-api-key-here"'
3157
+ );
3158
+ }
3159
+ const prompt = buildProjectSummaryPrompt(projectInfo);
3160
+ const response = await chatCompletion({
3161
+ prompt,
3162
+ apiKey: AI_API_KEY,
3163
+ apiBase: AI_API_BASE,
3164
+ model: AI_MODEL,
3165
+ temperature: 0.7
3166
+ });
3167
+ const summary = parseProjectSummary(response);
3168
+ if (!summary) {
3169
+ throw new Error(
3170
+ "Failed to parse AI project summary. The AI may have returned an invalid format. Please try again or contact support if the issue persists."
3171
+ );
3172
+ }
3173
+ return summary;
3174
+ }
2694
3175
 
2695
3176
  // src/plopfile.ts
2696
3177
  module.exports = function(plop) {
@@ -2700,6 +3181,7 @@ module.exports = function(plop) {
2700
3181
  async prompts(inquirer) {
2701
3182
  const config = await loadTemplateCliConfig();
2702
3183
  let aiSuggestion = null;
3184
+ let aiSummary = null;
2703
3185
  const questions = [
2704
3186
  // ===== AI 引导模式(必须最先询问) =====
2705
3187
  {
@@ -2724,7 +3206,7 @@ module.exports = function(plop) {
2724
3206
  message: "",
2725
3207
  async when(answers2) {
2726
3208
  if (answers2.isAIAssisted && answers2.aiUserInput) {
2727
- const spinner = (0, import_ora11.default)("AI 正在生成项目建议...").start();
3209
+ const spinner = (0, import_ora12.default)("AI 正在生成项目建议...").start();
2728
3210
  try {
2729
3211
  aiSuggestion = await getAIProjectSuggestion(answers2.aiUserInput, config);
2730
3212
  spinner.succeed("AI 已生成项目建议");
@@ -2735,6 +3217,16 @@ module.exports = function(plop) {
2735
3217
  console.log(` 关键词: ${aiSuggestion.keywords.join(", ")}`);
2736
3218
  console.log(` 推荐模板: ${aiSuggestion.template}`);
2737
3219
  console.log("");
3220
+ try {
3221
+ const summary = await getAIProjectSummary({
3222
+ name: aiSuggestion.names[0],
3223
+ description: aiSuggestion.description,
3224
+ keywords: aiSuggestion.keywords
3225
+ }, config);
3226
+ aiSummary = summary;
3227
+ spinner.succeed("AI 已生成项目简介");
3228
+ } catch {
3229
+ }
2738
3230
  } catch (error) {
2739
3231
  spinner.fail(`AI 引导失败: ${error instanceof Error ? error.message : String(error)}`);
2740
3232
  console.log("回退到标准问答流程\n");
@@ -2899,7 +3391,7 @@ module.exports = function(plop) {
2899
3391
  name: "license",
2900
3392
  message: "请选择开源协议",
2901
3393
  async choices() {
2902
- return import_fs_extra12.default.readdir(import_path11.default.join(__dirname, "../templates/licenses/"));
3394
+ return import_fs_extra14.default.readdir(import_path13.default.join(__dirname, "../templates/licenses/"));
2903
3395
  },
2904
3396
  default: "MIT",
2905
3397
  when(answers2) {
@@ -3076,6 +3568,9 @@ module.exports = function(plop) {
3076
3568
  answers.aiGeneratedKeywords = suggestion.keywords;
3077
3569
  answers.aiRecommendedTemplate = suggestion.template;
3078
3570
  }
3571
+ if (aiSummary) {
3572
+ answers.aiGeneratedSummary = aiSummary;
3573
+ }
3079
3574
  delete answers._aiTrigger;
3080
3575
  return answers;
3081
3576
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cmyr-template-cli",
3
- "version": "1.45.5",
3
+ "version": "1.46.0",
4
4
  "description": "草梅友仁自制的项目模板创建器",
5
5
  "author": "CaoMeiYouRen",
6
6
  "license": "MIT",
@@ -49,7 +49,7 @@
49
49
  "@types/node": "^26.0.1",
50
50
  "@vitest/coverage-v8": "^4.1.6",
51
51
  "commitizen": "^4.3.1",
52
- "commitlint-config-cmyr": "1.0.0",
52
+ "commitlint-config-cmyr": "1.0.1",
53
53
  "conventional-changelog-cmyr-config": "^3.0.0",
54
54
  "conventional-changelog-writer": "^9.1.0",
55
55
  "cross-env": "^10.1.0",
@@ -60,7 +60,7 @@
60
60
  "husky": "^9.0.5",
61
61
  "lint-staged": "^17.0.4",
62
62
  "rimraf": "^6.1.3",
63
- "semantic-release": "25.0.3",
63
+ "semantic-release": "25.0.8",
64
64
  "semantic-release-cmyr-config": "1.0.2",
65
65
  "tsup": "^8.5.1",
66
66
  "tsx": "^4.22.0",
@@ -1,73 +0,0 @@
1
- # AGENTS.md
2
-
3
- ## 项目概述
4
- <%= projectDescription %>
5
-
6
- ## 角色与目标
7
- - 本文件用于约束 AI 代理与协作者在当前项目中的默认行为。
8
- - 任何更具体的项目规范、目录说明、设计决策或工作流要求,应以项目内的 README、开发规范、设计文档和配置文件为准。
9
- - 当下文内容与项目实际实现冲突时,以代码、配置和项目规范文档中的事实为准。
10
-
11
- ## 技术栈
12
- - 主要语言: <%= language %>
13
- - 运行时: <%= runtime %>
14
- <% if (vueVersion === 3) { %>- 框架: Vue 3<% } %>
15
- <% if (vueVersion === 2) { %>- 框架: Vue 2<% } %>
16
- - 包管理器: <%= packageManager %>
17
-
18
- <% if (repositoryUrl || documentationUrl || issuesUrl || contributingUrl) { %>## 仓库信息
19
- <% if (repositoryUrl) { %>- 仓库地址: <%= repositoryUrl %><% } %>
20
- <% if (documentationUrl) { %>- 项目文档: <%= documentationUrl %><% } %>
21
- <% if (issuesUrl) { %>- Issue 地址: <%= issuesUrl %><% } %>
22
- <% if (contributingUrl) { %>- 贡献指南: <%= contributingUrl %><% } %>
23
- <% } %>
24
-
25
- ## 项目结构
26
- > 以下目录为模板默认结构;具体项目可在生成后按实际情况增删。
27
-
28
- ```
29
- src/ # 源代码
30
- <% if (isInitTest !== 'none') { %>tests/ # 测试文件
31
- <% } %><% if (devCommand) { %>playground/ # 本地调试或演示代码(如有)
32
- <% } %>docs/ # 文档与规范(如有)
33
- ```
34
-
35
- ## 常用命令
36
- - 安装依赖: `<%= packageManager %> install`
37
- <% if (devCommand) { %>- 启动开发环境: `<%= devCommand %>`<% } %>
38
- <% if (testCommand) { %>- 运行测试: `<%= testCommand %>`<% } %>
39
- <% if (buildCommand) { %>- 构建项目: `<%= buildCommand %>`<% } %>
40
- <% if (lintCommand) { %>- 代码检查: `<%= lintCommand %>`<% } %>
41
- <% if (startCommand) { %>- 启动生产或本地预览: `<%= startCommand %>`<% } %>
42
- <% if (commitCommand) { %>- 生成提交: `<%= commitCommand %>`<% } %>
43
-
44
- ## 编码与协作约定
45
- - 优先遵循项目现有代码风格、目录约定和命名规范,不要擅自引入新的体系。
46
- - 使用与项目一致的语言特性和类型策略;如果项目启用了 TypeScript,则保持类型检查可通过。
47
- - 变更应尽量保持最小范围,避免顺手重构无关模块。
48
- - 需要新增或调整依赖时,优先确认项目已有方案是否已覆盖。
49
- - 提交信息遵循 Conventional Commits 规范。
50
-
51
- ## 质量门禁
52
- <% if (isInitTest === 'vitest') { %>
53
- - 测试框架: Vitest
54
- - 目标覆盖率: >= 80%
55
- <% } else if (isInitTest === 'jest') { %>
56
- - 测试框架: Jest
57
- - 目标覆盖率: >= 80%
58
- <% } else { %>
59
- - 当前未配置测试框架;如后续补充测试,应同步更新本文件。
60
- <% } %>
61
- - 代码变更后,按项目实际可用命令完成 lint、typecheck、test 等必要校验。
62
- - 如果某项校验在当前项目中不存在,应在说明中明确标注,而不是假定其存在。
63
-
64
- ## 安全与避免事项
65
- - 不要硬编码 API Key、Token、密码或其他敏感信息。
66
- - 不要直接修改构建产物、发布产物或生成目录中的文件。
67
- - 不要跳过 TypeScript 类型检查或项目规定的静态检查。
68
- - 不要使用 `var` 声明变量,优先使用 `const` 和 `let`。
69
- - 对环境变量、密钥文件和部署配置的修改应格外谨慎。
70
-
71
- ## 可选补充
72
- - 如果项目存在更细的 AI 规则、目录约定或角色分工,可在此处继续补充,但不要与项目事实相冲突。
73
- - 如果项目已经定义了更强的安全、测试或发布流程,本文件应只保留入口级约束。