llm-content-creator 0.1.0 → 0.1.1

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.
Files changed (91) hide show
  1. package/dist/application/workflow/SyncExecutor.d.ts.map +1 -1
  2. package/dist/application/workflow/SyncExecutor.js +1 -0
  3. package/dist/application/workflow/SyncExecutor.js.map +1 -1
  4. package/dist/config/index.d.ts +2 -2
  5. package/dist/config/index.js +3 -3
  6. package/dist/config/index.js.map +1 -1
  7. package/dist/domain/repositories/TaskRepository.d.ts +1 -0
  8. package/dist/domain/repositories/TaskRepository.d.ts.map +1 -1
  9. package/dist/infrastructure/database/MemoryTaskRepository.d.ts +17 -1
  10. package/dist/infrastructure/database/MemoryTaskRepository.d.ts.map +1 -1
  11. package/dist/infrastructure/database/MemoryTaskRepository.js +76 -1
  12. package/dist/infrastructure/database/MemoryTaskRepository.js.map +1 -1
  13. package/dist/infrastructure/database/SQLiteQualityCheckRepository.d.ts +27 -0
  14. package/dist/infrastructure/database/SQLiteQualityCheckRepository.d.ts.map +1 -0
  15. package/dist/infrastructure/database/SQLiteQualityCheckRepository.js +80 -0
  16. package/dist/infrastructure/database/SQLiteQualityCheckRepository.js.map +1 -0
  17. package/dist/infrastructure/database/SQLiteResultRepository.d.ts +31 -0
  18. package/dist/infrastructure/database/SQLiteResultRepository.d.ts.map +1 -0
  19. package/dist/infrastructure/database/SQLiteResultRepository.js +77 -0
  20. package/dist/infrastructure/database/SQLiteResultRepository.js.map +1 -0
  21. package/dist/infrastructure/database/SQLiteTaskRepository.d.ts +12 -0
  22. package/dist/infrastructure/database/SQLiteTaskRepository.d.ts.map +1 -1
  23. package/dist/infrastructure/database/SQLiteTaskRepository.js +69 -2
  24. package/dist/infrastructure/database/SQLiteTaskRepository.js.map +1 -1
  25. package/dist/infrastructure/database/index.d.ts +23 -2
  26. package/dist/infrastructure/database/index.d.ts.map +1 -1
  27. package/dist/infrastructure/database/index.js +33 -2
  28. package/dist/infrastructure/database/index.js.map +1 -1
  29. package/dist/infrastructure/redis/connection.d.ts.map +1 -1
  30. package/dist/infrastructure/redis/connection.js +14 -3
  31. package/dist/infrastructure/redis/connection.js.map +1 -1
  32. package/dist/presentation/cli/commands/cancel.d.ts.map +1 -1
  33. package/dist/presentation/cli/commands/cancel.js +1 -0
  34. package/dist/presentation/cli/commands/cancel.js.map +1 -1
  35. package/dist/presentation/cli/commands/create.d.ts.map +1 -1
  36. package/dist/presentation/cli/commands/create.js +29 -14
  37. package/dist/presentation/cli/commands/create.js.map +1 -1
  38. package/dist/presentation/cli/commands/list.d.ts +8 -0
  39. package/dist/presentation/cli/commands/list.d.ts.map +1 -0
  40. package/dist/presentation/cli/commands/list.js +154 -0
  41. package/dist/presentation/cli/commands/list.js.map +1 -0
  42. package/dist/presentation/cli/commands/result.d.ts.map +1 -1
  43. package/dist/presentation/cli/commands/result.js +33 -3
  44. package/dist/presentation/cli/commands/result.js.map +1 -1
  45. package/dist/presentation/cli/commands/retry.d.ts +8 -0
  46. package/dist/presentation/cli/commands/retry.d.ts.map +1 -0
  47. package/dist/presentation/cli/commands/retry.js +203 -0
  48. package/dist/presentation/cli/commands/retry.js.map +1 -0
  49. package/dist/presentation/cli/commands/status.d.ts.map +1 -1
  50. package/dist/presentation/cli/commands/status.js +3 -2
  51. package/dist/presentation/cli/commands/status.js.map +1 -1
  52. package/dist/presentation/cli/index.js +4 -0
  53. package/dist/presentation/cli/index.js.map +1 -1
  54. package/dist/presentation/cli/utils/cleanup.d.ts +1 -3
  55. package/dist/presentation/cli/utils/cleanup.d.ts.map +1 -1
  56. package/dist/presentation/cli/utils/cleanup.js.map +1 -1
  57. package/dist/presentation/monitor-cli.js +2 -4
  58. package/dist/presentation/monitor-cli.js.map +1 -1
  59. package/dist/presentation/worker-cli.js +8 -5
  60. package/dist/presentation/worker-cli.js.map +1 -1
  61. package/dist/schedulers/TaskScheduler.d.ts.map +1 -1
  62. package/dist/schedulers/TaskScheduler.js +13 -5
  63. package/dist/schedulers/TaskScheduler.js.map +1 -1
  64. package/dist/workers/TaskWorker.d.ts.map +1 -1
  65. package/dist/workers/TaskWorker.js +39 -21
  66. package/dist/workers/TaskWorker.js.map +1 -1
  67. package/docs/cli-reference.md +623 -0
  68. package/docs/quick-start.md +47 -0
  69. package/docs/user-guide.md +191 -24
  70. package/package.json +3 -1
  71. package/src/application/workflow/SyncExecutor.ts +1 -0
  72. package/src/config/index.ts +3 -3
  73. package/src/domain/repositories/TaskRepository.ts +1 -0
  74. package/src/infrastructure/database/MemoryTaskRepository.ts +89 -1
  75. package/src/infrastructure/database/SQLiteQualityCheckRepository.ts +102 -0
  76. package/src/infrastructure/database/SQLiteResultRepository.ts +95 -0
  77. package/src/infrastructure/database/SQLiteTaskRepository.ts +80 -2
  78. package/src/infrastructure/database/index.ts +39 -2
  79. package/src/infrastructure/redis/connection.ts +16 -3
  80. package/src/presentation/cli/commands/cancel.ts +1 -0
  81. package/src/presentation/cli/commands/create.ts +30 -14
  82. package/src/presentation/cli/commands/list.ts +172 -0
  83. package/src/presentation/cli/commands/result.ts +33 -3
  84. package/src/presentation/cli/commands/retry.ts +241 -0
  85. package/src/presentation/cli/commands/status.ts +3 -2
  86. package/src/presentation/cli/index.ts +4 -0
  87. package/src/presentation/cli/utils/cleanup.ts +1 -1
  88. package/src/presentation/monitor-cli.ts +2 -4
  89. package/src/presentation/worker-cli.ts +8 -5
  90. package/src/schedulers/TaskScheduler.ts +13 -5
  91. package/src/workers/TaskWorker.ts +47 -22
@@ -83,11 +83,15 @@ export class SQLiteTaskRepository {
83
83
  CREATE TABLE IF NOT EXISTS quality_checks (
84
84
  id TEXT PRIMARY KEY,
85
85
  task_id TEXT NOT NULL,
86
- check_category TEXT NOT NULL,
86
+ check_type TEXT NOT NULL,
87
87
  score REAL,
88
88
  passed BOOLEAN NOT NULL DEFAULT 0,
89
+ hard_constraints_passed BOOLEAN NOT NULL DEFAULT 0,
89
90
  details TEXT,
90
91
  fix_suggestions TEXT,
92
+ rubric_version TEXT,
93
+ model_name TEXT,
94
+ prompt_hash TEXT,
91
95
  created_at TEXT NOT NULL DEFAULT (datetime('now')),
92
96
  FOREIGN KEY (task_id) REFERENCES tasks(id) ON DELETE CASCADE
93
97
  );
@@ -97,6 +101,7 @@ export class SQLiteTaskRepository {
97
101
  task_id TEXT NOT NULL,
98
102
  result_type TEXT NOT NULL,
99
103
  content TEXT,
104
+ file_path TEXT,
100
105
  metadata TEXT,
101
106
  quality_score REAL,
102
107
  created_at TEXT NOT NULL DEFAULT (datetime('now')),
@@ -415,7 +420,7 @@ export class SQLiteTaskRepository {
415
420
  WHERE id = ? AND version = ? AND status = ?
416
421
  `);
417
422
 
418
- const result = stmt.run(TaskStatus.RUNNING, workerId, now, taskId, version, TaskStatus.PENDING);
423
+ const result = stmt.run(TaskStatus.RUNNING, workerId, now, now, taskId, version, TaskStatus.PENDING);
419
424
  const claimed = result.changes > 0;
420
425
 
421
426
  if (claimed) {
@@ -611,6 +616,79 @@ export class SQLiteTaskRepository {
611
616
  }
612
617
  }
613
618
 
619
+ /**
620
+ * 查询任务列表(支持过滤和分页)
621
+ */
622
+ async findMany(filter?: any, pagination?: any): Promise<Task[]> {
623
+ const { limit = 50, offset = 0 } = pagination || {};
624
+
625
+ const conditions: string[] = [];
626
+ const values: any[] = [];
627
+
628
+ if (filter?.status) {
629
+ conditions.push('status = ?');
630
+ values.push(filter.status);
631
+ }
632
+ if (filter?.userId) {
633
+ conditions.push('id = ?'); // SQLite 没有 userId 字段,使用 id
634
+ values.push(filter.userId);
635
+ }
636
+ if (filter?.mode) {
637
+ conditions.push('mode = ?');
638
+ values.push(filter.mode);
639
+ }
640
+
641
+ const whereClause = conditions.length > 0
642
+ ? 'WHERE ' + conditions.join(' AND ')
643
+ : '';
644
+
645
+ const stmt = this.db.prepare(`
646
+ SELECT * FROM tasks
647
+ ${whereClause}
648
+ ORDER BY created_at DESC
649
+ LIMIT ? OFFSET ?
650
+ `);
651
+
652
+ const rows = stmt.all(...values, limit, offset) as any[];
653
+ return rows.map((row) => this.mapRowToTask(row));
654
+ }
655
+
656
+ /**
657
+ * 统计任务数量
658
+ */
659
+ async count(filter?: any): Promise<number> {
660
+ const conditions: string[] = [];
661
+ const values: any[] = [];
662
+
663
+ if (filter?.status) {
664
+ conditions.push('status = ?');
665
+ values.push(filter.status);
666
+ }
667
+ if (filter?.userId) {
668
+ conditions.push('id = ?');
669
+ values.push(filter.userId);
670
+ }
671
+ if (filter?.mode) {
672
+ conditions.push('mode = ?');
673
+ values.push(filter.mode);
674
+ }
675
+
676
+ const whereClause = conditions.length > 0
677
+ ? 'WHERE ' + conditions.join(' AND ')
678
+ : '';
679
+
680
+ const stmt = this.db.prepare(`SELECT COUNT(*) as count FROM tasks ${whereClause}`);
681
+ const result = stmt.get(...values) as { count: number };
682
+ return result.count;
683
+ }
684
+
685
+ /**
686
+ * 根据 userId 查询任务列表
687
+ */
688
+ async findByUserId(userId: string, pagination?: any): Promise<Task[]> {
689
+ return this.findMany({ userId }, pagination);
690
+ }
691
+
614
692
  /**
615
693
  * 关闭数据库连接
616
694
  */
@@ -7,6 +7,9 @@
7
7
  import { config } from '../../config/index.js';
8
8
  import { MemoryTaskRepository } from './MemoryTaskRepository.js';
9
9
  import { SQLiteTaskRepository } from './SQLiteTaskRepository.js';
10
+ import { SQLiteResultRepository } from './SQLiteResultRepository.js';
11
+ import { SQLiteQualityCheckRepository } from './SQLiteQualityCheckRepository.js';
12
+ import { PostgresTaskRepository } from './PostgresTaskRepository.js';
10
13
  import { createLogger } from '../logging/logger.js';
11
14
 
12
15
  const logger = createLogger('Database:Factory');
@@ -54,9 +57,7 @@ export function createTaskRepository(pool?: any, dbPath?: string) {
54
57
 
55
58
  if (dbType === 'postgres') {
56
59
  // PostgreSQL 版本(需要数据库连接)
57
- // 动态导入以避免在不需要时加载 pg
58
60
  try {
59
- const { PostgresTaskRepository } = require('./PostgresTaskRepository.js');
60
61
  // 如果提供了 pool,使用提供的;否则由 BaseRepository 自动创建
61
62
  logger.info('Using PostgresTaskRepository');
62
63
  return new PostgresTaskRepository(pool);
@@ -74,10 +75,46 @@ export function createTaskRepository(pool?: any, dbPath?: string) {
74
75
  return new SQLiteTaskRepository(dbPath);
75
76
  }
76
77
 
78
+ /**
79
+ * 创建 Result Repository
80
+ *
81
+ * 根据配置自动选择合适的实现
82
+ */
83
+ export function createResultRepository(_pool?: any, dbPath?: string) {
84
+ const dbType = config.database.type;
85
+
86
+ logger.info('Creating Result Repository', { databaseType: dbType });
87
+
88
+ // 注意:PostgreSQL 版本需要动态导入,但目前只支持 SQLite
89
+ // 默认使用 SQLiteResultRepository
90
+ logger.info('Using SQLiteResultRepository', { dbPath: dbPath || './data/content-creator.db' });
91
+ return new SQLiteResultRepository(dbPath);
92
+ }
93
+
94
+ /**
95
+ * 创建 Quality Check Repository
96
+ *
97
+ * 根据配置自动选择合适的实现
98
+ */
99
+ export function createQualityCheckRepository(_pool?: any, dbPath?: string) {
100
+ const dbType = config.database.type;
101
+
102
+ logger.info('Creating Quality Check Repository', { databaseType: dbType });
103
+
104
+ // 注意:PostgreSQL 版本需要动态导入,但目前只支持 SQLite
105
+ // 默认使用 SQLiteQualityCheckRepository
106
+ logger.info('Using SQLiteQualityCheckRepository', { dbPath: dbPath || './data/content-creator.db' });
107
+ return new SQLiteQualityCheckRepository(dbPath);
108
+ }
109
+
77
110
  // 导出具体的 Repository 类(可选使用)
78
111
  export { MemoryTaskRepository } from './MemoryTaskRepository.js';
79
112
  export { PostgresTaskRepository } from './PostgresTaskRepository.js';
80
113
  export { SQLiteTaskRepository } from './SQLiteTaskRepository.js';
114
+ export { PostgresResultRepository } from './ResultRepository.js';
115
+ export { SQLiteResultRepository } from './SQLiteResultRepository.js';
116
+ export { PostgresQualityCheckRepository } from './PostgresQualityCheckRepository.js';
117
+ export { SQLiteQualityCheckRepository } from './SQLiteQualityCheckRepository.js';
81
118
 
82
119
  // 导出单例(全局使用)
83
120
  export const taskRepository = createTaskRepository();
@@ -81,7 +81,7 @@ export class RedisClient {
81
81
  host: redisUrl.hostname,
82
82
  port: parseInt(redisUrl.port) || 6379,
83
83
  db: config.redis.db,
84
- maxRetriesPerRequest: config.redis.maxRetriesPerRequest,
84
+ maxRetriesPerRequest: null, // BullMQ Worker 要求必须设置为 null
85
85
  retryStrategy: (times: number) => {
86
86
  const delay = Math.min(times * 50, 2000);
87
87
  logger.debug(`Redis retry attempt ${times}, delay: ${delay}ms`);
@@ -131,9 +131,22 @@ export class RedisClient {
131
131
  });
132
132
 
133
133
  // 等待连接就绪
134
- await this.client.ready;
134
+ await new Promise((resolve, reject) => {
135
+ this.client.on('ready', () => {
136
+ logger.info('Redis connection established');
137
+ resolve(this.client);
138
+ });
139
+
140
+ this.client.on('error', (err: Error) => {
141
+ reject(err);
142
+ });
143
+
144
+ // 超时处理
145
+ setTimeout(() => {
146
+ reject(new Error('Redis connection timeout'));
147
+ }, config.redis.connectTimeout);
148
+ });
135
149
 
136
- logger.info('Redis connection established');
137
150
  return this.client;
138
151
  } catch (error) {
139
152
  this.isConnecting = false;
@@ -55,6 +55,7 @@ export const cancelCommand = new Command('cancel')
55
55
  success
56
56
  });
57
57
 
58
+ process.exit(0);
58
59
  } catch (error) {
59
60
  spinner.fail('操作失败');
60
61
  logger.error('Cancel command failed', error as Error);
@@ -11,6 +11,7 @@ import { v4 as uuidv4 } from 'uuid';
11
11
  import { createSyncExecutor } from '../../../application/workflow/SyncExecutor.js';
12
12
  import { MemoryTaskRepository } from '../../../infrastructure/database/MemoryTaskRepository.js';
13
13
  import { PostgresTaskRepository } from '../../../infrastructure/database/PostgresTaskRepository.js';
14
+ import { SQLiteTaskRepository } from '../../../infrastructure/database/SQLiteTaskRepository.js';
14
15
  import { PostgresResultRepository } from '../../../infrastructure/database/ResultRepository.js';
15
16
  import { PostgresQualityCheckRepository } from '../../../infrastructure/database/PostgresQualityCheckRepository.js';
16
17
  import { ExecutionMode, TaskPriority } from '../../../domain/entities/Task.js';
@@ -104,6 +105,10 @@ export const createCommand = new Command('create')
104
105
  qualityCheckRepo = new PostgresQualityCheckRepository(resources.pool);
105
106
 
106
107
  console.log('✅ 使用 PostgreSQL 持久化存储');
108
+ } else if (config.database.type === 'sqlite') {
109
+ // 使用 SQLite Task Repository,确保任务持久化
110
+ taskRepo = new SQLiteTaskRepository();
111
+ console.log('✅ 使用 SQLite 持久化存储');
107
112
  } else {
108
113
  // 使用内存数据库(仅用于测试)
109
114
  taskRepo = new MemoryTaskRepository();
@@ -149,11 +154,11 @@ export const createCommand = new Command('create')
149
154
 
150
155
  console.log(chalk.yellow.bold('\n💡 后续操作:'));
151
156
  console.log(chalk.white('1. 查询任务状态:'));
152
- console.log(chalk.gray(` pnpm cli result --task-id ${taskId}`));
157
+ console.log(chalk.gray(` pnpm run cli result --task-id ${taskId}`));
153
158
  console.log(chalk.white('\n2. 确保 Worker 正在运行:'));
154
159
  console.log(chalk.gray(' pnpm run worker'));
155
- console.log(chalk.white('\n3. 查看队列状态:'));
156
- console.log(chalk.gray(' pnpm cli worker:status'));
160
+ console.log(chalk.white('\n3. 查看监控面板:'));
161
+ console.log(chalk.gray(' pnpm run monitor'));
157
162
  printSeparator();
158
163
 
159
164
  logger.info('Task created via async mode', {
@@ -164,6 +169,14 @@ export const createCommand = new Command('create')
164
169
 
165
170
  } else {
166
171
  // ==================== 同步模式:使用 SyncExecutor ====================
172
+ // 为 SQLite 模式创建结果和质检仓储
173
+ if (config.database.type === 'sqlite') {
174
+ const { SQLiteResultRepository } = await import('../../../infrastructure/database/SQLiteResultRepository.js');
175
+ const { SQLiteQualityCheckRepository } = await import('../../../infrastructure/database/SQLiteQualityCheckRepository.js');
176
+ resultRepo = new SQLiteResultRepository();
177
+ qualityCheckRepo = new SQLiteQualityCheckRepository();
178
+ }
179
+
167
180
  const executor = createSyncExecutor(taskRepo, {
168
181
  databaseType: config.database.type,
169
182
  enableLogging: true,
@@ -311,6 +324,9 @@ export const createCommand = new Command('create')
311
324
  duration: result.duration
312
325
  });
313
326
  } // 结束同步模式的 else 块
327
+
328
+ // 任务完成后明确退出
329
+ process.exit(0);
314
330
  } catch (error) {
315
331
  logger.error('Create command failed', error as Error);
316
332
  console.error(chalk.red(`\n❌ 错误: ${error instanceof Error ? error.message : String(error)}`));
@@ -327,15 +343,7 @@ export const createCommand = new Command('create')
327
343
  if (resources.servicesInitialized) {
328
344
  logger.debug('Starting resource cleanup...');
329
345
 
330
- // 1. 关闭 Logger(必须在最后)
331
- try {
332
- await closeLogger();
333
- logger.debug('Logger closed');
334
- } catch (error) {
335
- console.log('Error closing logger (ignored):', error);
336
- }
337
-
338
- // 2. 停止 Metrics 服务定时器
346
+ // 1. 停止 Metrics 服务定时器
339
347
  try {
340
348
  metricsService.stop();
341
349
  console.log('Metrics service stopped');
@@ -343,7 +351,7 @@ export const createCommand = new Command('create')
343
351
  console.log('Error stopping metrics service (ignored):', error);
344
352
  }
345
353
 
346
- // 3. 关闭 Redis 客户端连接
354
+ // 2. 关闭 Redis 客户端连接
347
355
  try {
348
356
  await redisClient.disconnect();
349
357
  console.log('Redis client disconnected');
@@ -351,7 +359,7 @@ export const createCommand = new Command('create')
351
359
  console.log('Error disconnecting Redis (ignored):', error);
352
360
  }
353
361
 
354
- // 4. 关闭 PostgreSQL 连接池
362
+ // 3. 关闭 PostgreSQL 连接池
355
363
  try {
356
364
  if (resources.pool) {
357
365
  await resources.pool.end();
@@ -361,6 +369,14 @@ export const createCommand = new Command('create')
361
369
  console.log('Error closing PostgreSQL pool (ignored):', error);
362
370
  }
363
371
 
372
+ // 4. 关闭 Logger(必须在最后)
373
+ try {
374
+ await closeLogger();
375
+ logger.debug('Logger closed');
376
+ } catch (error) {
377
+ console.log('Error closing logger (ignored):', error);
378
+ }
379
+
364
380
  console.log('Resource cleanup completed');
365
381
  }
366
382
  }
@@ -0,0 +1,172 @@
1
+ /**
2
+ * CLI List Command
3
+ *
4
+ * 列出历史任务
5
+ */
6
+
7
+ import { Command } from 'commander';
8
+ import chalk from 'chalk';
9
+ import { createTaskRepository } from '../../../infrastructure/database/index.js';
10
+ import { createLogger } from '../../../infrastructure/logging/logger.js';
11
+
12
+ const logger = createLogger('CLI:List');
13
+
14
+ function printSeparator() {
15
+ console.log(chalk.gray('─'.repeat(100)));
16
+ }
17
+
18
+ /**
19
+ * 格式化任务状态显示
20
+ */
21
+ function formatTaskStatus(status: string): string {
22
+ const statusMap: Record<string, string> = {
23
+ pending: chalk.yellow('等待中'),
24
+ processing: chalk.blue('处理中'),
25
+ running: chalk.blue('运行中'),
26
+ completed: chalk.green('已完成'),
27
+ failed: chalk.red('失败'),
28
+ cancelled: chalk.gray('已取消'),
29
+ };
30
+ return statusMap[status] || status;
31
+ }
32
+
33
+ /**
34
+ * 格式化执行模式
35
+ */
36
+ function formatExecutionMode(mode: string): string {
37
+ const modeMap: Record<string, string> = {
38
+ sync: '同步',
39
+ async: '异步',
40
+ };
41
+ return modeMap[mode] || mode;
42
+ }
43
+
44
+ /**
45
+ * 格式化时间显示
46
+ */
47
+ function formatTime(dateStr: string | undefined): string {
48
+ if (!dateStr) return '-';
49
+ const date = new Date(dateStr);
50
+ const now = new Date();
51
+ const diff = now.getTime() - date.getTime();
52
+
53
+ // 小于1分钟
54
+ if (diff < 60000) {
55
+ return `${Math.floor(diff / 1000)}秒前`;
56
+ }
57
+ // 小于1小时
58
+ if (diff < 3600000) {
59
+ return `${Math.floor(diff / 60000)}分钟前`;
60
+ }
61
+ // 小于24小时
62
+ if (diff < 86400000) {
63
+ return `${Math.floor(diff / 3600000)}小时前`;
64
+ }
65
+ // 大于24小时,显示日期
66
+ return date.toLocaleDateString('zh-CN', {
67
+ month: '2-digit',
68
+ day: '2-digit',
69
+ hour: '2-digit',
70
+ minute: '2-digit',
71
+ });
72
+ }
73
+
74
+ /**
75
+ * 格式化持续时间
76
+ */
77
+ function formatDuration(startedAt: string | undefined, completedAt: string | undefined): string {
78
+ if (!startedAt) return '-';
79
+ if (!completedAt) return '进行中...';
80
+
81
+ const start = new Date(startedAt).getTime();
82
+ const end = new Date(completedAt).getTime();
83
+ const duration = end - start;
84
+
85
+ if (duration < 1000) return `${duration}ms`;
86
+ if (duration < 60000) return `${Math.floor(duration / 1000)}s`;
87
+ return `${Math.floor(duration / 60000)}m ${Math.floor((duration % 60000) / 1000)}s`;
88
+ }
89
+
90
+ export const listCommand = new Command('list')
91
+ .description('列出历史任务')
92
+ .option('-s, --status <status>', '筛选状态 (pending, running, completed, failed, cancelled)')
93
+ .option('-m, --mode <mode>', '筛选执行模式 (sync, async)')
94
+ .option('-l, --limit <number>', '显示数量', '20')
95
+ .option('-o, --offset <number>', '偏移量(用于分页)', '0')
96
+ .option('--json', '以 JSON 格式输出')
97
+ .action(async (options) => {
98
+ try {
99
+ const repository = createTaskRepository();
100
+
101
+ // 解析参数
102
+ const limit = parseInt(options.limit) || 20;
103
+ const offset = parseInt(options.offset) || 0;
104
+
105
+ // 构建过滤条件
106
+ const filters: any = {};
107
+
108
+ if (options.status) {
109
+ filters.status = options.status;
110
+ }
111
+
112
+ if (options.mode) {
113
+ filters.mode = options.mode;
114
+ }
115
+
116
+ // 使用 findMany 方法查询任务列表
117
+ const tasks = await repository.findMany(filters, { limit, offset });
118
+
119
+ // JSON 格式输出
120
+ if (options.json) {
121
+ console.log(JSON.stringify(tasks, null, 2));
122
+ process.exit(0);
123
+ }
124
+
125
+ // 文本格式输出
126
+ printSeparator();
127
+ console.log(chalk.bold.blue('📋 历史任务列表'));
128
+ printSeparator();
129
+
130
+ if (tasks.length === 0) {
131
+ console.log(chalk.yellow('暂无任务记录'));
132
+ console.log();
133
+ console.log(chalk.white('💡 提示:使用以下命令创建新任务'));
134
+ console.log(chalk.gray(' pnpm run cli:create --topic "文章主题" --requirements "创作要求"'));
135
+ console.log();
136
+ process.exit(0);
137
+ }
138
+
139
+ // 输出任务列表
140
+ tasks.forEach((task: any, index: number) => {
141
+ console.log(chalk.bold.white(`${index + 1}. ${task.topic}`));
142
+ console.log(chalk.gray(` ID: ${task.taskId}`));
143
+ console.log(chalk.gray(` 状态: ${formatTaskStatus(task.status)}`));
144
+ console.log(chalk.gray(` 模式: ${formatExecutionMode(task.mode)}`));
145
+ console.log(chalk.gray(` 创建时间: ${formatTime(task.createdAt?.toISOString())}`));
146
+ console.log(chalk.gray(` 耗时: ${formatDuration(task.startedAt?.toISOString(), task.completedAt?.toISOString())}`));
147
+
148
+ if (task.errorMessage) {
149
+ console.log(chalk.red(` 错误: ${task.errorMessage}`));
150
+ }
151
+
152
+ console.log();
153
+ });
154
+
155
+ // 详细信息提示
156
+ console.log(chalk.white('💡 查看任务详情:'));
157
+ console.log(chalk.gray(' pnpm run cli:status --task-id <任务ID>'));
158
+ console.log(chalk.gray(' pnpm run cli:result --task-id <任务ID>'));
159
+ console.log();
160
+
161
+ logger.info('Listed tasks', {
162
+ count: tasks.length,
163
+ filters,
164
+ });
165
+
166
+ process.exit(0);
167
+ } catch (error) {
168
+ logger.error('Failed to list tasks', error as Error);
169
+ console.error(chalk.red('❌ 查询任务列表失败:'), (error as Error).message);
170
+ process.exit(1);
171
+ }
172
+ });
@@ -44,14 +44,14 @@ export const resultCommand = new Command('result')
44
44
  if (task.status !== 'completed') {
45
45
  console.log(chalk.yellow(`⚠️ 任务尚未完成,当前状态: ${getStatusText(task.status)}`));
46
46
  await cleanupResources(taskRepo, resultRepo);
47
- return;
47
+ process.exit(0);
48
48
  }
49
49
 
50
50
  // JSON格式输出
51
51
  if (options.format === 'json') {
52
52
  console.log(JSON.stringify(task, null, 2));
53
53
  await cleanupResources(taskRepo, resultRepo);
54
- return;
54
+ process.exit(0);
55
55
  }
56
56
 
57
57
  // 文本格式输出
@@ -61,7 +61,7 @@ export const resultCommand = new Command('result')
61
61
  console.log(chalk.white(`状态: ${getStatusText(task.status)}`));
62
62
  printSeparator();
63
63
 
64
- // 从数据库查询结果(仅 PostgreSQL)
64
+ // 从数据库查询结果(支持 PostgreSQL 和 SQLite
65
65
  if (config.database.type === 'postgres') {
66
66
  const { Pool } = await import('pg');
67
67
  pool = new Pool({
@@ -101,6 +101,35 @@ export const resultCommand = new Command('result')
101
101
 
102
102
  // 关闭结果查询的连接池
103
103
  await pool.end();
104
+ } else if (config.database.type === 'sqlite') {
105
+ // SQLite 模式:使用 SQLiteResultRepository 查询结果
106
+ const { SQLiteResultRepository } = await import('../../../infrastructure/database/SQLiteResultRepository.js');
107
+ resultRepo = new SQLiteResultRepository();
108
+ const results = await resultRepo.findByTaskId(options.taskId);
109
+
110
+ if (results.length === 0) {
111
+ console.log(chalk.yellow('提示: 该任务未生成结果'));
112
+ } else {
113
+ console.log(chalk.blue.bold('\n📋 生成结果'));
114
+ printSeparator();
115
+
116
+ results.forEach((result: any, index: number) => {
117
+ console.log(chalk.white.bold(`${index + 1}. ${result.resultType.toUpperCase()}`));
118
+ printSeparator();
119
+
120
+ if (result.resultType === 'article') {
121
+ console.log(chalk.white('内容:'));
122
+ console.log(chalk.gray(result.content || '(无内容)'));
123
+ if (result.metadata?.wordCount) {
124
+ console.log(chalk.gray(`字数: ${result.metadata.wordCount}`));
125
+ }
126
+ } else if (result.resultType === 'image') {
127
+ console.log(chalk.white('图片 URL:'));
128
+ console.log(chalk.cyan(result.content || '(无 URL)'));
129
+ }
130
+ printSeparator();
131
+ });
132
+ }
104
133
  } else {
105
134
  // Memory 模式:提示结果仅实时返回
106
135
  console.log(chalk.yellow('\n💡 提示: 当前使用 Memory 模式'));
@@ -111,6 +140,7 @@ export const resultCommand = new Command('result')
111
140
 
112
141
  // 清理所有资源
113
142
  await cleanupResources(taskRepo, resultRepo);
143
+ process.exit(0);
114
144
 
115
145
  } catch (error) {
116
146
  console.error(chalk.red(`❌ 错误: ${error instanceof Error ? error.message : String(error)}`));