jjb-cmd 2.5.3 → 2.5.4

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/src/ai-pull.js ADDED
@@ -0,0 +1,675 @@
1
+ const fs = require('fs');
2
+ const os = require('os');
3
+ const path = require('path');
4
+ const child_process = require('child_process');
5
+
6
+ // Cursor user rules SQLite 数据库路径
7
+ // Windows: C:\Users\<username>\AppData\Roaming\Cursor\User\globalStorage\state.vscdb
8
+ const CURSOR_DB_PATH = path.join(
9
+ os.homedir(),
10
+ 'AppData',
11
+ 'Roaming',
12
+ 'Cursor',
13
+ 'User',
14
+ 'globalStorage',
15
+ 'state.vscdb'
16
+ );
17
+
18
+ // Git 仓库地址
19
+ const AI_REPO_URL = 'http://192.168.1.242:10985/root/jjb-ai.git';
20
+ // Git 分支/标签
21
+ const AI_REPO_BRANCH = 'v_1.0.0';
22
+
23
+ /**
24
+ * 删除目录(回调方式,使用递归删除)
25
+ */
26
+ function deleteDir(dirPath, callback) {
27
+ if (!fs.existsSync(dirPath)) {
28
+ callback && callback();
29
+ return;
30
+ }
31
+
32
+ // 递归删除目录和文件
33
+ function deleteRecursive(dir) {
34
+ if (!fs.existsSync(dir)) {
35
+ return;
36
+ }
37
+
38
+ try {
39
+ const files = fs.readdirSync(dir);
40
+ for (let i = 0; i < files.length; i++) {
41
+ const file = files[i];
42
+ const curPath = path.join(dir, file);
43
+ const stat = fs.statSync(curPath);
44
+
45
+ if (stat.isDirectory()) {
46
+ deleteRecursive(curPath);
47
+ // 删除空目录
48
+ try {
49
+ fs.rmdirSync(curPath);
50
+ } catch (e) {
51
+ // 忽略错误,可能目录不为空
52
+ }
53
+ } else {
54
+ try {
55
+ fs.unlinkSync(curPath);
56
+ } catch (e) {
57
+ // 忽略删除文件错误
58
+ }
59
+ }
60
+ }
61
+
62
+ // 最后删除根目录
63
+ try {
64
+ fs.rmdirSync(dir);
65
+ } catch (e) {
66
+ // 忽略错误,可能目录不为空或正在使用
67
+ }
68
+ } catch (e) {
69
+ // 忽略错误
70
+ }
71
+ }
72
+
73
+ deleteRecursive(dirPath);
74
+
75
+ // 延迟回调,确保删除完成
76
+ setTimeout(() => {
77
+ callback && callback();
78
+ }, 300);
79
+ }
80
+
81
+ /**
82
+ * 复制文件夹(回调方式,改进错误处理)
83
+ */
84
+ function copyFolder(srcDir, tarDir, callback) {
85
+ if (!fs.existsSync(srcDir)) {
86
+ callback && callback(new Error(`源目录不存在: ${srcDir}`));
87
+ return;
88
+ }
89
+
90
+ // 确保目标目录存在
91
+ if (!fs.existsSync(tarDir)) {
92
+ try {
93
+ fs.mkdirSync(tarDir, { recursive: true });
94
+ } catch (e) {
95
+ callback && callback(e);
96
+ return;
97
+ }
98
+ }
99
+
100
+ // 使用改进的复制函数
101
+ copyFolderRecursive(srcDir, tarDir, callback);
102
+ }
103
+
104
+ /**
105
+ * 递归复制文件夹(带错误处理)
106
+ */
107
+ function copyFolderRecursive(srcDir, tarDir, callback) {
108
+ if (!fs.existsSync(srcDir)) {
109
+ callback && callback(new Error(`源目录不存在: ${srcDir}`));
110
+ return;
111
+ }
112
+
113
+ try {
114
+ const files = fs.readdirSync(srcDir);
115
+ let completed = 0;
116
+ let hasError = false;
117
+
118
+ if (files.length === 0) {
119
+ callback && callback();
120
+ return;
121
+ }
122
+
123
+ files.forEach((file) => {
124
+ const srcPath = path.join(srcDir, file);
125
+ const tarPath = path.join(tarDir, file);
126
+
127
+ try {
128
+ const stat = fs.statSync(srcPath);
129
+
130
+ if (stat.isDirectory()) {
131
+ // 确保目标目录存在
132
+ if (!fs.existsSync(tarPath)) {
133
+ fs.mkdirSync(tarPath, { recursive: true });
134
+ }
135
+
136
+ copyFolderRecursive(srcPath, tarPath, (err) => {
137
+ completed++;
138
+ if (err && !hasError) {
139
+ hasError = true;
140
+ console.log(`【Warning】:复制目录失败 ${srcPath}: ${err.message}`);
141
+ }
142
+ if (completed === files.length) {
143
+ callback && callback(hasError ? new Error('部分文件复制失败') : null);
144
+ }
145
+ });
146
+ } else {
147
+ // 复制文件,带重试机制
148
+ copyFileWithRetry(srcPath, tarPath, 3, (err) => {
149
+ completed++;
150
+ if (err && !hasError) {
151
+ hasError = true;
152
+ console.log(`【Warning】:复制文件失败 ${srcPath}: ${err.message}`);
153
+ }
154
+ if (completed === files.length) {
155
+ callback && callback(hasError ? new Error('部分文件复制失败') : null);
156
+ }
157
+ });
158
+ }
159
+ } catch (e) {
160
+ completed++;
161
+ if (!hasError) {
162
+ hasError = true;
163
+ console.log(`【Warning】:处理文件失败 ${srcPath}: ${e.message}`);
164
+ }
165
+ if (completed === files.length) {
166
+ callback && callback(hasError ? new Error('部分文件复制失败') : null);
167
+ }
168
+ }
169
+ });
170
+ } catch (e) {
171
+ callback && callback(e);
172
+ }
173
+ }
174
+
175
+ /**
176
+ * 复制文件(带重试机制)
177
+ */
178
+ function copyFileWithRetry(srcPath, tarPath, retries, callback) {
179
+ if (retries <= 0) {
180
+ callback && callback(new Error(`复制文件失败,已重试多次: ${srcPath}`));
181
+ return;
182
+ }
183
+
184
+ // 确保目标目录存在
185
+ const tarDir = path.dirname(tarPath);
186
+ if (!fs.existsSync(tarDir)) {
187
+ try {
188
+ fs.mkdirSync(tarDir, { recursive: true });
189
+ } catch (e) {
190
+ callback && callback(e);
191
+ return;
192
+ }
193
+ }
194
+
195
+ const rs = fs.createReadStream(srcPath);
196
+ const ws = fs.createWriteStream(tarPath);
197
+
198
+ rs.on('error', (err) => {
199
+ callback && callback(err);
200
+ });
201
+
202
+ ws.on('error', (err) => {
203
+ // 如果是文件被占用或其他临时错误,尝试重试
204
+ if (retries > 1 && (err.code === 'EBUSY' || err.code === 'EPERM' || err.code === 'EACCES')) {
205
+ setTimeout(() => {
206
+ copyFileWithRetry(srcPath, tarPath, retries - 1, callback);
207
+ }, 100);
208
+ } else {
209
+ callback && callback(err);
210
+ }
211
+ });
212
+
213
+ ws.on('close', () => {
214
+ callback && callback();
215
+ });
216
+
217
+ rs.pipe(ws);
218
+ }
219
+
220
+ module.exports = () => {
221
+ const root_path = path.resolve('./');
222
+ const tmpDir = os.tmpdir();
223
+ const cloneDir = path.join(tmpDir, 'jjb-ai-temp');
224
+
225
+ console.log('【jjb-cmd ai-pull】:开始执行...');
226
+
227
+ // 步骤1: 拉取或更新仓库代码
228
+ console.log('步骤1: 正在拉取 jjb-ai 仓库代码...');
229
+
230
+ // 如果临时目录已存在,先删除
231
+ deleteDir(cloneDir, () => {
232
+ try {
233
+ // 克隆仓库指定分支/标签
234
+ child_process.execSync(`git clone -b ${AI_REPO_BRANCH} ${AI_REPO_URL} "${cloneDir}"`, {
235
+ stdio: 'inherit',
236
+ cwd: tmpDir
237
+ });
238
+ console.log('✓ 仓库代码拉取成功');
239
+
240
+ // 步骤2: 复制 .ai 文件夹到当前项目
241
+ console.log('步骤2: 正在复制 .ai 文件夹...');
242
+ const aiSourcePath = path.join(cloneDir, '.ai');
243
+ const aiTargetPath = path.join(root_path, '.ai');
244
+
245
+ if (!fs.existsSync(aiSourcePath)) {
246
+ console.log('【Warning】:仓库中未找到 .ai 文件夹');
247
+ step3();
248
+ } else {
249
+ // 如果目标目录已存在,先删除
250
+ if (fs.existsSync(aiTargetPath)) {
251
+ deleteDir(aiTargetPath, () => {
252
+ // 复制 .ai 文件夹
253
+ copyFolder(aiSourcePath, aiTargetPath, (err) => {
254
+ if (err) {
255
+ console.error('【Error】:复制 .ai 文件夹失败', err.message);
256
+ cleanupAndExit(1);
257
+ return;
258
+ }
259
+ console.log('✓ .ai 文件夹复制成功');
260
+ step3();
261
+ });
262
+ });
263
+ } else {
264
+ // 复制 .ai 文件夹
265
+ copyFolder(aiSourcePath, aiTargetPath, (err) => {
266
+ if (err) {
267
+ console.error('【Error】:复制 .ai 文件夹失败', err.message);
268
+ cleanupAndExit(1);
269
+ return;
270
+ }
271
+ console.log('✓ .ai 文件夹复制成功');
272
+ step3();
273
+ });
274
+ }
275
+ }
276
+ } catch (error) {
277
+ console.error('【Error】:拉取仓库失败', error.message);
278
+ cleanupAndExit(1);
279
+ }
280
+ });
281
+
282
+ // 步骤3: 复制 prompt.md 到 Cursor user rules (SQLite 数据库)
283
+ function step3() {
284
+ console.log('步骤3: 正在更新 Cursor user rules (SQLite 数据库)...');
285
+ const promptSourcePath = path.join(cloneDir, 'prompt.md');
286
+
287
+ try {
288
+ if (!fs.existsSync(promptSourcePath)) {
289
+ console.log('【Warning】:仓库中未找到 prompt.md 文件');
290
+ step4();
291
+ return;
292
+ }
293
+
294
+ // 读取 prompt.md 内容
295
+ const promptContent = fs.readFileSync(promptSourcePath, 'utf8');
296
+
297
+ // 检查数据库文件是否存在
298
+ if (!fs.existsSync(CURSOR_DB_PATH)) {
299
+ console.log('【Warning】:Cursor 数据库文件不存在,可能 Cursor 未安装或路径不正确');
300
+ console.log(`【路径】:${CURSOR_DB_PATH}`);
301
+ console.log('【建议】:请确保已安装 Cursor 编辑器,或手动复制 prompt.md 内容');
302
+ step4();
303
+ return;
304
+ }
305
+
306
+ // 备份数据库文件
307
+ try {
308
+ const dbDir = path.dirname(CURSOR_DB_PATH);
309
+ const dbFileName = path.basename(CURSOR_DB_PATH);
310
+
311
+ // 查找所有备份文件
312
+ const files = fs.existsSync(dbDir) ? fs.readdirSync(dbDir) : [];
313
+ const backupFiles = files.filter(file =>
314
+ file.startsWith(dbFileName + '.backup.')
315
+ );
316
+
317
+ // 删除所有旧的备份文件
318
+ backupFiles.forEach(backupFile => {
319
+ try {
320
+ const backupFilePath = path.join(dbDir, backupFile);
321
+ fs.unlinkSync(backupFilePath);
322
+ console.log(`✓ 已删除旧备份文件: ${backupFile}`);
323
+ } catch (e) {
324
+ // 忽略删除错误
325
+ }
326
+ });
327
+
328
+ // 创建新的备份文件
329
+ const backupPath = CURSOR_DB_PATH + '.backup.' + Date.now();
330
+ fs.copyFileSync(CURSOR_DB_PATH, backupPath);
331
+ console.log(`✓ 已备份数据库文件到 ${path.basename(backupPath)}`);
332
+ } catch (e) {
333
+ console.log('【Warning】:备份数据库文件失败', e.message);
334
+ }
335
+
336
+ // 使用 better-sqlite3 或 sqlite3 更新数据库
337
+ try {
338
+ // 尝试使用 better-sqlite3 (同步 API,更简单)
339
+ let Database;
340
+ try {
341
+ Database = require('better-sqlite3');
342
+ } catch (e) {
343
+ // 如果 better-sqlite3 不存在,尝试使用 sqlite3
344
+ try {
345
+ const sqlite3 = require('sqlite3');
346
+ // sqlite3 是异步的,需要使用回调或 Promise
347
+ console.log(`【调试】:准备更新数据库,内容长度: ${promptContent.length} 字符`);
348
+
349
+ const db = new sqlite3.Database(CURSOR_DB_PATH, (err) => {
350
+ if (err) {
351
+ console.error('【Error】:无法打开数据库', err.message);
352
+ console.log('【建议】:请关闭 Cursor 编辑器后重试');
353
+ step4();
354
+ return;
355
+ }
356
+
357
+ // 先查询是否存在该键
358
+ db.get("SELECT value, length(value) as len FROM ItemTable WHERE key = ?", ['aicontext.personalContext'], (err, row) => {
359
+ if (err) {
360
+ console.error('【Error】:查询数据库失败', err.message);
361
+ db.close();
362
+ step4();
363
+ return;
364
+ }
365
+
366
+ // 更新或插入数据
367
+ if (row) {
368
+ console.log(`【调试】:找到现有记录,当前长度: ${row.len} 字节`);
369
+
370
+ // 检查内容是否已经相同
371
+ let oldContent = '';
372
+ if (Buffer.isBuffer(row.value)) {
373
+ oldContent = row.value.toString('utf8');
374
+ } else {
375
+ oldContent = String(row.value);
376
+ }
377
+
378
+ if (oldContent === promptContent) {
379
+ console.log('✓ prompt.md 内容未变化,无需更新');
380
+ console.log('【提示】:请重启 Cursor 编辑器以使规则生效');
381
+ db.close();
382
+ step4();
383
+ return;
384
+ }
385
+
386
+ // 更新现有记录
387
+ db.run("UPDATE ItemTable SET value = ? WHERE key = ?", [promptContent, 'aicontext.personalContext'], function(err) {
388
+ if (err) {
389
+ console.error('【Error】:更新数据库失败', err.message);
390
+ if (err.code === 'SQLITE_BUSY' || err.message.includes('locked')) {
391
+ console.error('【Error】:数据库被锁定,可能是 Cursor 正在使用');
392
+ }
393
+ console.log('【建议】:请关闭 Cursor 编辑器后重试');
394
+ db.close();
395
+ step4();
396
+ return;
397
+ }
398
+
399
+ console.log(`【调试】:更新影响行数: ${this.changes}`);
400
+
401
+ if (this.changes === 0) {
402
+ console.error('【Error】:更新失败,影响行数为 0,可能数据库被锁定');
403
+ console.log('【建议】:请关闭 Cursor 编辑器后重试');
404
+ db.close();
405
+ step4();
406
+ return;
407
+ }
408
+
409
+ // 验证更新是否成功
410
+ db.get("SELECT value, length(value) as len FROM ItemTable WHERE key = ?", ['aicontext.personalContext'], (err, verifyRow) => {
411
+ if (err) {
412
+ console.error('【Error】:验证更新失败', err.message);
413
+ } else if (verifyRow && verifyRow.value) {
414
+ let verifyContent = '';
415
+ if (Buffer.isBuffer(verifyRow.value)) {
416
+ verifyContent = verifyRow.value.toString('utf8');
417
+ } else {
418
+ verifyContent = String(verifyRow.value);
419
+ }
420
+
421
+ if (verifyContent === promptContent) {
422
+ console.log(`✓ prompt.md 已更新到 Cursor user rules (${verifyRow.len} 字节)`);
423
+ // 显示前几行内容确认
424
+ const previewLines = verifyContent.split('\n').slice(0, 3).join('\n');
425
+ console.log(`【预览】:内容前3行:\n${previewLines}${verifyContent.split('\n').length > 3 ? '...' : ''}`);
426
+ } else {
427
+ console.error(`【Error】:更新后内容不匹配!`);
428
+ console.error(` 期望长度: ${promptContent.length}, 实际长度: ${verifyContent.length}`);
429
+ console.error(` 期望前100字符: ${promptContent.substring(0, 100)}`);
430
+ console.error(` 实际前100字符: ${verifyContent.substring(0, 100)}`);
431
+ }
432
+ } else {
433
+ console.error('【Error】:更新后验证失败,值为空');
434
+ }
435
+
436
+ console.log('【提示】:请重启 Cursor 编辑器以使规则生效');
437
+ db.close();
438
+ step4();
439
+ });
440
+ });
441
+ } else {
442
+ console.log('【调试】:记录不存在,将插入新记录');
443
+ // 插入新记录
444
+ db.run("INSERT INTO ItemTable (key, value) VALUES (?, ?)", ['aicontext.personalContext', promptContent], function(err) {
445
+ if (err) {
446
+ console.error('【Error】:插入数据库失败', err.message);
447
+ if (err.code === 'SQLITE_BUSY' || err.message.includes('locked')) {
448
+ console.error('【Error】:数据库被锁定,可能是 Cursor 正在使用');
449
+ }
450
+ console.log('【建议】:请关闭 Cursor 编辑器后重试');
451
+ db.close();
452
+ step4();
453
+ return;
454
+ }
455
+
456
+ console.log(`【调试】:插入结果,最后插入ID: ${this.lastInsertRowid}`);
457
+
458
+ if (!this.lastInsertRowid) {
459
+ console.error('【Error】:插入失败,未返回插入ID');
460
+ db.close();
461
+ step4();
462
+ return;
463
+ }
464
+
465
+ // 验证插入是否成功
466
+ db.get("SELECT value, length(value) as len FROM ItemTable WHERE key = ?", ['aicontext.personalContext'], (err, verifyRow) => {
467
+ if (err) {
468
+ console.error('【Error】:验证插入失败', err.message);
469
+ } else if (verifyRow && verifyRow.value) {
470
+ let verifyContent = '';
471
+ if (Buffer.isBuffer(verifyRow.value)) {
472
+ verifyContent = verifyRow.value.toString('utf8');
473
+ } else {
474
+ verifyContent = String(verifyRow.value);
475
+ }
476
+
477
+ if (verifyContent === promptContent) {
478
+ console.log(`✓ prompt.md 已添加到 Cursor user rules (${verifyRow.len} 字节)`);
479
+ // 显示前几行内容确认
480
+ const previewLines = verifyContent.split('\n').slice(0, 3).join('\n');
481
+ console.log(`【预览】:内容前3行:\n${previewLines}${verifyContent.split('\n').length > 3 ? '...' : ''}`);
482
+ } else {
483
+ console.error(`【Error】:插入后内容不匹配!`);
484
+ console.error(` 期望长度: ${promptContent.length}, 实际长度: ${verifyContent.length}`);
485
+ }
486
+ } else {
487
+ console.error('【Error】:插入后验证失败,值为空');
488
+ }
489
+
490
+ console.log('【提示】:请重启 Cursor 编辑器以使规则生效');
491
+ db.close();
492
+ step4();
493
+ });
494
+ });
495
+ }
496
+ });
497
+ });
498
+ return; // 异步操作,提前返回
499
+ } catch (e2) {
500
+ console.error('【Error】:未找到 SQLite 模块 (better-sqlite3 或 sqlite3)');
501
+ console.log('【建议】:请运行 npm install better-sqlite3 或 npm install sqlite3');
502
+ console.log('【或者】:使用命令行工具手动更新数据库');
503
+ step4();
504
+ return;
505
+ }
506
+ }
507
+
508
+ // 使用 better-sqlite3 (同步 API)
509
+ const db = new Database(CURSOR_DB_PATH, { readonly: false });
510
+
511
+ try {
512
+ console.log(`【调试】:准备更新数据库,内容长度: ${promptContent.length} 字符`);
513
+
514
+ // 先查询是否存在该键
515
+ const row = db.prepare("SELECT value, length(value) as len FROM ItemTable WHERE key = ?").get('aicontext.personalContext');
516
+
517
+ if (row) {
518
+ console.log(`【调试】:找到现有记录,当前长度: ${row.len} 字节`);
519
+
520
+ // 检查内容是否已经相同
521
+ let oldContent = '';
522
+ if (Buffer.isBuffer(row.value)) {
523
+ oldContent = row.value.toString('utf8');
524
+ } else {
525
+ oldContent = String(row.value);
526
+ }
527
+
528
+ if (oldContent === promptContent) {
529
+ console.log('✓ prompt.md 内容未变化,无需更新');
530
+ console.log('【提示】:请重启 Cursor 编辑器以使规则生效');
531
+ db.close();
532
+ step4();
533
+ return;
534
+ }
535
+
536
+ // 更新现有记录
537
+ const updateStmt = db.prepare("UPDATE ItemTable SET value = ? WHERE key = ?");
538
+ const result = updateStmt.run(promptContent, 'aicontext.personalContext');
539
+ console.log(`【调试】:更新影响行数: ${result.changes}`);
540
+
541
+ if (result.changes === 0) {
542
+ console.error('【Error】:更新失败,影响行数为 0,可能数据库被锁定');
543
+ console.log('【建议】:请关闭 Cursor 编辑器后重试');
544
+ db.close();
545
+ step4();
546
+ return;
547
+ }
548
+
549
+ // 验证更新是否成功
550
+ const verifyRow = db.prepare("SELECT value, length(value) as len FROM ItemTable WHERE key = ?").get('aicontext.personalContext');
551
+ if (verifyRow && verifyRow.value) {
552
+ let verifyContent = '';
553
+ if (Buffer.isBuffer(verifyRow.value)) {
554
+ verifyContent = verifyRow.value.toString('utf8');
555
+ } else {
556
+ verifyContent = String(verifyRow.value);
557
+ }
558
+
559
+ if (verifyContent === promptContent) {
560
+ console.log(`✓ prompt.md 已更新到 Cursor user rules (${verifyRow.len} 字节)`);
561
+ // 显示前几行内容确认
562
+ const previewLines = verifyContent.split('\n').slice(0, 3).join('\n');
563
+ console.log(`【预览】:内容前3行:\n${previewLines}${verifyContent.split('\n').length > 3 ? '...' : ''}`);
564
+ } else {
565
+ console.error(`【Error】:更新后内容不匹配!`);
566
+ console.error(` 期望长度: ${promptContent.length}, 实际长度: ${verifyContent.length}`);
567
+ console.error(` 期望前100字符: ${promptContent.substring(0, 100)}`);
568
+ console.error(` 实际前100字符: ${verifyContent.substring(0, 100)}`);
569
+ }
570
+ } else {
571
+ console.error('【Error】:更新后验证失败,值为空');
572
+ }
573
+ } else {
574
+ console.log('【调试】:记录不存在,将插入新记录');
575
+ // 插入新记录
576
+ const insertStmt = db.prepare("INSERT INTO ItemTable (key, value) VALUES (?, ?)");
577
+ const result = insertStmt.run('aicontext.personalContext', promptContent);
578
+ console.log(`【调试】:插入结果,最后插入ID: ${result.lastInsertRowid}`);
579
+
580
+ if (!result.lastInsertRowid) {
581
+ console.error('【Error】:插入失败,未返回插入ID');
582
+ db.close();
583
+ step4();
584
+ return;
585
+ }
586
+
587
+ // 验证插入是否成功
588
+ const verifyRow = db.prepare("SELECT value, length(value) as len FROM ItemTable WHERE key = ?").get('aicontext.personalContext');
589
+ if (verifyRow && verifyRow.value) {
590
+ let verifyContent = '';
591
+ if (Buffer.isBuffer(verifyRow.value)) {
592
+ verifyContent = verifyRow.value.toString('utf8');
593
+ } else {
594
+ verifyContent = String(verifyRow.value);
595
+ }
596
+
597
+ if (verifyContent === promptContent) {
598
+ console.log(`✓ prompt.md 已添加到 Cursor user rules (${verifyRow.len} 字节)`);
599
+ // 显示前几行内容确认
600
+ const previewLines = verifyContent.split('\n').slice(0, 3).join('\n');
601
+ console.log(`【预览】:内容前3行:\n${previewLines}${verifyContent.split('\n').length > 3 ? '...' : ''}`);
602
+ } else {
603
+ console.error(`【Error】:插入后内容不匹配!`);
604
+ console.error(` 期望长度: ${promptContent.length}, 实际长度: ${verifyContent.length}`);
605
+ }
606
+ } else {
607
+ console.error('【Error】:插入后验证失败,值为空');
608
+ }
609
+ }
610
+
611
+ console.log('【提示】:请重启 Cursor 编辑器以使规则生效');
612
+ } catch (dbError) {
613
+ console.error('【Error】:操作数据库失败', dbError.message);
614
+ if (dbError.code === 'SQLITE_BUSY' || dbError.message.includes('locked')) {
615
+ console.error('【Error】:数据库被锁定,可能是 Cursor 正在使用');
616
+ }
617
+ console.error('【Error】:错误堆栈', dbError.stack);
618
+ console.log('【建议】:请关闭 Cursor 编辑器后重试');
619
+ } finally {
620
+ db.close();
621
+ }
622
+ } catch (dbError) {
623
+ console.error('【Error】:无法打开数据库', dbError.message);
624
+ console.log('【建议】:请关闭 Cursor 编辑器后重试');
625
+ }
626
+
627
+ step4();
628
+ } catch (error) {
629
+ console.error('【Error】:处理 prompt.md 失败', error.message);
630
+ console.log('【Warning】:可能 Cursor 未安装或路径不正确,请手动复制 prompt.md');
631
+ step4();
632
+ }
633
+ }
634
+
635
+ // 步骤4: 检查并更新 .gitignore
636
+ function step4() {
637
+ console.log('步骤4: 正在检查 .gitignore 文件...');
638
+ const gitignorePath = path.join(root_path, '.gitignore');
639
+
640
+ try {
641
+ if (fs.existsSync(gitignorePath)) {
642
+ let gitignoreContent = fs.readFileSync(gitignorePath, 'utf8');
643
+ const aiIgnorePattern = /^\.ai\s*$/m;
644
+
645
+ if (!aiIgnorePattern.test(gitignoreContent)) {
646
+ // 如果文件末尾没有换行,先添加换行
647
+ if (!gitignoreContent.endsWith('\n')) {
648
+ gitignoreContent += '\n';
649
+ }
650
+ gitignoreContent += '.ai\n';
651
+ fs.writeFileSync(gitignorePath, gitignoreContent, 'utf8');
652
+ console.log('✓ 已在 .gitignore 中添加 .ai 忽略规则');
653
+ } else {
654
+ console.log('✓ .gitignore 中已存在 .ai 忽略规则');
655
+ }
656
+ } else {
657
+ // 如果 .gitignore 不存在,创建它
658
+ fs.writeFileSync(gitignorePath, '.ai\n', 'utf8');
659
+ console.log('✓ 已创建 .gitignore 并添加 .ai 忽略规则');
660
+ }
661
+ } catch (error) {
662
+ console.error('【Error】:更新 .gitignore 失败', error.message);
663
+ }
664
+
665
+ cleanupAndExit(0);
666
+ }
667
+
668
+ // 清理临时目录并退出
669
+ function cleanupAndExit(exitCode) {
670
+ deleteDir(cloneDir, () => {
671
+ console.log('【jjb-cmd ai-pull】:执行完成!');
672
+ process.exit(exitCode);
673
+ });
674
+ }
675
+ };