workplace-pua-cli 0.4.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.
Files changed (112) hide show
  1. package/.env.example +4 -0
  2. package/.eslintrc.json +21 -0
  3. package/.prettierrc.json +9 -0
  4. package/CHANGELOG.md +107 -0
  5. package/README.md +240 -0
  6. package/bin/pua +2 -0
  7. package/dist/commands/chat.d.ts +15 -0
  8. package/dist/commands/chat.d.ts.map +1 -0
  9. package/dist/commands/chat.js +262 -0
  10. package/dist/commands/chat.js.map +1 -0
  11. package/dist/commands/config.d.ts +15 -0
  12. package/dist/commands/config.d.ts.map +1 -0
  13. package/dist/commands/config.js +247 -0
  14. package/dist/commands/config.js.map +1 -0
  15. package/dist/commands/prompt.d.ts +14 -0
  16. package/dist/commands/prompt.d.ts.map +1 -0
  17. package/dist/commands/prompt.js +126 -0
  18. package/dist/commands/prompt.js.map +1 -0
  19. package/dist/config/providers.d.ts +37 -0
  20. package/dist/config/providers.d.ts.map +1 -0
  21. package/dist/config/providers.js +96 -0
  22. package/dist/config/providers.js.map +1 -0
  23. package/dist/config/session-storage.d.ts +29 -0
  24. package/dist/config/session-storage.d.ts.map +1 -0
  25. package/dist/config/session-storage.js +67 -0
  26. package/dist/config/session-storage.js.map +1 -0
  27. package/dist/config/settings.d.ts +55 -0
  28. package/dist/config/settings.d.ts.map +1 -0
  29. package/dist/config/settings.js +163 -0
  30. package/dist/config/settings.js.map +1 -0
  31. package/dist/config/storage.d.ts +69 -0
  32. package/dist/config/storage.d.ts.map +1 -0
  33. package/dist/config/storage.js +126 -0
  34. package/dist/config/storage.js.map +1 -0
  35. package/dist/history/session.d.ts +52 -0
  36. package/dist/history/session.d.ts.map +1 -0
  37. package/dist/history/session.js +122 -0
  38. package/dist/history/session.js.map +1 -0
  39. package/dist/index.d.ts +3 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +157 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/llm/base.d.ts +38 -0
  44. package/dist/llm/base.d.ts.map +1 -0
  45. package/dist/llm/base.js +22 -0
  46. package/dist/llm/base.js.map +1 -0
  47. package/dist/llm/factory.d.ts +12 -0
  48. package/dist/llm/factory.d.ts.map +1 -0
  49. package/dist/llm/factory.js +26 -0
  50. package/dist/llm/factory.js.map +1 -0
  51. package/dist/llm/openai.d.ts +10 -0
  52. package/dist/llm/openai.d.ts.map +1 -0
  53. package/dist/llm/openai.js +97 -0
  54. package/dist/llm/openai.js.map +1 -0
  55. package/dist/llm/zhipu.d.ts +10 -0
  56. package/dist/llm/zhipu.d.ts.map +1 -0
  57. package/dist/llm/zhipu.js +91 -0
  58. package/dist/llm/zhipu.js.map +1 -0
  59. package/dist/prompts/boss.d.ts +6 -0
  60. package/dist/prompts/boss.d.ts.map +1 -0
  61. package/dist/prompts/boss.js +41 -0
  62. package/dist/prompts/boss.js.map +1 -0
  63. package/dist/prompts/employee.d.ts +6 -0
  64. package/dist/prompts/employee.d.ts.map +1 -0
  65. package/dist/prompts/employee.js +41 -0
  66. package/dist/prompts/employee.js.map +1 -0
  67. package/dist/prompts/index.d.ts +4 -0
  68. package/dist/prompts/index.d.ts.map +1 -0
  69. package/dist/prompts/index.js +9 -0
  70. package/dist/prompts/index.js.map +1 -0
  71. package/dist/utils/formatter.d.ts +25 -0
  72. package/dist/utils/formatter.d.ts.map +1 -0
  73. package/dist/utils/formatter.js +83 -0
  74. package/dist/utils/formatter.js.map +1 -0
  75. package/dist/utils/logger.d.ts +10 -0
  76. package/dist/utils/logger.d.ts.map +1 -0
  77. package/dist/utils/logger.js +31 -0
  78. package/dist/utils/logger.js.map +1 -0
  79. package/dist/utils/stream.d.ts +36 -0
  80. package/dist/utils/stream.d.ts.map +1 -0
  81. package/dist/utils/stream.js +74 -0
  82. package/dist/utils/stream.js.map +1 -0
  83. package/docs/OPTIMIZATION.md +772 -0
  84. package/docs/TECHNICAL_PRINCIPLES.md +663 -0
  85. package/package.json +52 -0
  86. package/sample/1.png +0 -0
  87. package/sample/2.png +0 -0
  88. package/screenshots/chat-dialogue.png +0 -0
  89. package/screenshots/chat-mode.png +0 -0
  90. package/src/__tests__/config/settings.test.ts +48 -0
  91. package/src/__tests__/prompts/boss.test.ts +35 -0
  92. package/src/commands/chat.ts +328 -0
  93. package/src/commands/config.ts +283 -0
  94. package/src/commands/prompt.ts +154 -0
  95. package/src/config/providers.ts +109 -0
  96. package/src/config/session-storage.ts +94 -0
  97. package/src/config/settings.ts +194 -0
  98. package/src/config/storage.ts +150 -0
  99. package/src/history/session.ts +141 -0
  100. package/src/index.ts +164 -0
  101. package/src/llm/base.ts +55 -0
  102. package/src/llm/factory.ts +24 -0
  103. package/src/llm/openai.ts +113 -0
  104. package/src/llm/zhipu.ts +101 -0
  105. package/src/prompts/boss.ts +43 -0
  106. package/src/prompts/employee.ts +43 -0
  107. package/src/prompts/index.ts +3 -0
  108. package/src/utils/formatter.ts +104 -0
  109. package/src/utils/logger.ts +31 -0
  110. package/src/utils/stream.ts +76 -0
  111. package/tsconfig.json +20 -0
  112. package/vitest.config.ts +18 -0
@@ -0,0 +1,772 @@
1
+ # PUA CLI 优化分析
2
+
3
+ ## 架构分析
4
+
5
+ ### 当前架构(用户提供的图)
6
+
7
+ ```
8
+ ┌─────────────────────────────────────────────────────────────┐
9
+ │ PUA CLI 架构 │
10
+ ├─────────────────────────────────────────────────────────────┤
11
+ │ │
12
+ │ ┌─────────────┐ ┌──────────┐ ┌──────────────┐ │
13
+ │ │ CLI Layer │────│ Config Layer│ │ Prompt Layer │ │
14
+ │ │ (Commander) │ │ (Settings) │ │ (Boss/Empl)│ │
15
+ │ └──────┬──────┘ └──────┬───────┘ │
16
+ │ │ │ │
17
+ │ ▼ ▼ ▼ │
18
+ │ ┌─────────────────────────────────────────────────┐ │
19
+ │ │ LLM Layer (Factory Pattern) │ │
20
+ │ │ │ │
21
+ │ │ ┌──────────────────────────────────────────┐│ │
22
+ │ │ │ Abstract Base (LLMBase) ││ │
23
+ │ │ │ - chat() 抽象方法 ││ │
24
+ │ │ │ - chatStream() 抽象方法 ││ │
25
+ │ │ │ - 流式处理 (SSE) ││ │
26
+ │ │ └──────────┬──────────────────────────┘│ │
27
+ │ │ │ │ │
28
+ │ │ ┌─────────┴────────┐ │ │
29
+ │ │ │ ZhipuLLM │ OpenAI │ │
30
+ │ │ └──────────┬─────────┘ │ │
31
+ │ │ │ │ │
32
+ └─────────────┼─────────────────┼──────────────┘ │
33
+ │ │
34
+ ┌─────────┴─────────┐ │
35
+ │ AI 服务提供商 │ │
36
+ │ - 智谱 AI (zhipu) │ │
37
+ │ - OpenAI │ │
38
+ └───────────────────────┘ │
39
+ └─────────────────────────────────────────────────────────────┘
40
+ ```
41
+
42
+ ### 架构优势分析
43
+
44
+ #### ✅ 当前优势
45
+ 1. **清晰的分层** - 4 层架构,职责分明
46
+ 2. **工厂模式** - 易于扩展新 Provider
47
+ 3. **抽象基类** - LLM 接口统一
48
+ 4. **会话管理** - 内存中的上下文维护
49
+ 5. **配置分层** - 多源配置合并
50
+ 6. **流式输出** - SSE 实时响应
51
+
52
+ #### ⚠️ 当前不足
53
+ 1. **无标准 CLI 框架** - 使用 Commander.js(非 OCLIF)
54
+ 2. **缺少插件系统** - 功能扩展能力有限
55
+ 3. **无缓存机制** - 重复请求无优化
56
+ 4. **会话无持久化** - 重启丢失上下文
57
+ 5. **输出格式单一** - 仅支持纯文本
58
+ 6. **无成本追踪** - 无法监控 token 使用
59
+ 7. **错误处理简单** - 缺少重试机制
60
+ 8. **测试覆盖不足** - 当前测试仅占源码 10%
61
+
62
+ ---
63
+
64
+ ## 优化方案
65
+
66
+ ### 方案 1: CLI 框架升级 (OCLIF)
67
+
68
+ #### 目标
69
+ 实现符合 [OCLIF](https://oclif.io/) 标准的命令行工具,提升专业性和互操作性。
70
+
71
+ #### 实施细节
72
+
73
+ ```typescript
74
+ // src/core/oclif-commands.ts
75
+ import { Args, Command, Flags } from '@oclif/core';
76
+
77
+ export class ChatCommand extends Command {
78
+ static flags = {
79
+ role: Flags.custom({
80
+ description: '角色模式',
81
+ options: ['boss', 'employee'],
82
+ default: 'boss',
83
+ helpGroup: 'AI',
84
+ }),
85
+ severity: Flags.string({
86
+ description: 'PUA 强度',
87
+ options: ['mild', 'medium', 'extreme'],
88
+ default: 'medium',
89
+ }),
90
+ };
91
+
92
+ static description = '启动交互式 PUA 聊天';
93
+
94
+ async run() {
95
+ const { flags } = this.parse(ChatCommand);
96
+ // OCLIF 自动生成帮助和使用说明
97
+ this.log('启动', flags.role, '模式');
98
+ }
99
+ }
100
+
101
+ // src/index.ts - OCLIF 风格
102
+ import { CLI } from '@oclif/core';
103
+
104
+ const cli = new CLI({
105
+ id: 'pua',
106
+ description: 'PUA CLI - 趣味 AI 职场角色扮演工具',
107
+ commands: [ChatCommand, PromptCommand, ConfigCommand],
108
+ // 全局选项
109
+ flags: {
110
+ version: Flags.boolean({ description: '显示版本号' }),
111
+ verbose: Flags.boolean({ description: '详细输出' }),
112
+ },
113
+ });
114
+
115
+ // 自动生成帮助、使用说明和文档
116
+ cli.parse().catch((error) => {
117
+ if (error instanceof Error) {
118
+ this.error(error.message);
119
+ process.exit(1);
120
+ }
121
+ });
122
+ ```
123
+
124
+ #### 收益
125
+ - ✅ 自动生成的标准帮助格式
126
+ - ✅ 更好的参数组织(支持嵌套子命令)
127
+ - ✅ 标准化的错误处理和退出码
128
+ - ✅ 更好的多语言支持基础
129
+
130
+ ---
131
+
132
+ ### 方案 2: 插件系统架构
133
+
134
+ #### 目标
135
+ 实现类似 VS Code 插件或 MCP (Model Context Protocol) 的插件系统,支持动态扩展功能。
136
+
137
+ #### 实施细节
138
+
139
+ ```typescript
140
+ // src/core/plugin-manager.ts
141
+ interface PUAPLugin {
142
+ name: string;
143
+ version: string;
144
+ author?: string;
145
+ description?: string;
146
+
147
+ // 命令扩展
148
+ commands?: PluginCommand[];
149
+
150
+ // 提示词模板
151
+ prompts?: PromptTemplate[];
152
+
153
+ // 钩子
154
+ hooks?: {
155
+ beforeChat?: (config: ChatConfig) => void;
156
+ afterResponse?: (response: string) => void;
157
+ onConfigChange?: (key: string, value: any) => void;
158
+ };
159
+ }
160
+
161
+ // 自定义角色插件
162
+ export class CustomRolePlugin implements PUAPLugin {
163
+ name = 'custom-dev-lead';
164
+ version = '1.0.0';
165
+ description = '开发主管角色 - 更严厉更专业';
166
+
167
+ commands: [
168
+ {
169
+ name: 'review',
170
+ handler: async (input: string) => {
171
+ return `# 代码评审\n\n你写的代码是什么垃圾?${input}\n\n重写!`;
172
+ }
173
+ }
174
+ ];
175
+ }
176
+
177
+ // 插件管理器
178
+ export class PluginManager {
179
+ private plugins: Map<string, PUAPLugin> = new Map();
180
+ private pluginDir: string;
181
+
182
+ async loadPlugin(pluginPath: string): Promise<void> {
183
+ const plugin = require(pluginPath);
184
+ this.plugins.set(plugin.name, plugin);
185
+ console.log(`✓ 已加载插件: ${plugin.name}`);
186
+ }
187
+
188
+ getCommands(): PluginCommand[] {
189
+ return Array.from(this.plugins.values())
190
+ .flatMap(plugin => plugin.commands || []);
191
+ }
192
+ }
193
+ ```
194
+
195
+ #### 收益
196
+ - ✅ 用户可创建自定义角色
197
+ - ✅ 社区可贡献角色模板
198
+ - ✅ 核心功能与扩展解耦
199
+ - ✅ 支持插件的热加载/卸载
200
+
201
+ ---
202
+
203
+ ### 方案 3: 配置验证系统
204
+
205
+ #### 目标
206
+ 使用 Zod 进行运行时配置验证,提供类型安全的配置管理。
207
+
208
+ #### 实施细节
209
+
210
+ ```typescript
211
+ // src/config/schema.ts
212
+ import { z } from 'zod';
213
+
214
+ export const ConfigSchema = z.object({
215
+ // Provider 验证
216
+ provider: z.enum(['zhipu', 'openai'], {
217
+ errorMap: {
218
+ invalid_type_error: '不支持的 AI 服务商',
219
+ },
220
+ }),
221
+
222
+ // API Key 验证
223
+ apiKey: z.string().min(10, {
224
+ errorMap: {
225
+ too_small: 'API Key 长度不能少于 10 个字符',
226
+ },
227
+ }),
228
+
229
+ // 模型验证
230
+ model: z.string().default('glm-4.7', {
231
+ errorMap: {
232
+ invalid_model: '模型名称无效',
233
+ },
234
+ }),
235
+
236
+ // 角色验证
237
+ role: z.enum(['boss', 'employee'], {
238
+ errorMap: {
239
+ invalid_role: '角色必须是 boss 或 employee',
240
+ },
241
+ }),
242
+
243
+ // 强度验证
244
+ severity: z.enum(['mild', 'medium', 'extreme']).default('medium'),
245
+
246
+ // 输出格式验证
247
+ format: z.enum(['text', 'markdown', 'json']).default('text'),
248
+ });
249
+
250
+ export type Config = z.infer<typeof ConfigSchema>;
251
+
252
+ // 运行时验证
253
+ export function loadAndValidateConfig(input: unknown): Result<Config, ZodError> {
254
+ try {
255
+ const config = ConfigSchema.parse(input);
256
+ return { success: true, data: config };
257
+ } catch (error) {
258
+ if (error instanceof z.ZodError) {
259
+ // 返回详细的错误信息
260
+ return {
261
+ success: false,
262
+ error: error.errors.map(e => ({
263
+ path: e.path.join('.'),
264
+ message: e.message,
265
+ code: e.code,
266
+ }))
267
+ };
268
+ }
269
+ return { success: false, error: { message: '未知错误' } };
270
+ }
271
+ }
272
+ ```
273
+
274
+ #### 收益
275
+ - ✅ 运行时类型检查
276
+ - ✅ 详细的错误提示(多语言支持)
277
+ - ✅ 配置迁移和版本管理
278
+ - ✅ 防止无效配置导致的问题
279
+
280
+ ---
281
+
282
+ ### 方案 4: 缓存系统
283
+
284
+ #### 目标
285
+ 实现多层缓存机制,减少 API 调用次数和响应时间。
286
+
287
+ #### 实施细节
288
+
289
+ ```typescript
290
+ // src/cache/cache-manager.ts
291
+ interface CacheEntry<T> {
292
+ key: string;
293
+ value: T;
294
+ timestamp: number;
295
+ ttl: number; // 存活时间(秒)
296
+ hits: number; // 命中次数
297
+ }
298
+
299
+ export class CacheManager {
300
+ private memoryCache: Map<string, CacheEntry<any>> = new Map();
301
+ private diskCacheDir: string;
302
+
303
+ constructor() {
304
+ this.diskCacheDir = path.join(os.homedir(), '.pua-cli', 'cache');
305
+ this.ensureCacheDir();
306
+ }
307
+
308
+ // 获取缓存(内存 > 磁盘)
309
+ async get<T>(key: string): Promise<T | null> {
310
+ // 先查内存
311
+ const memEntry = this.memoryCache.get(key);
312
+ if (memEntry && Date.now() - memEntry.timestamp < memEntry.ttl * 1000) {
313
+ memEntry.hits++;
314
+ return memEntry.value;
315
+ }
316
+
317
+ // 再查磁盘
318
+ return await this.getFromDisk<T>(key);
319
+ }
320
+
321
+ // 设置缓存
322
+ async set<T>(key: string, value: T, ttl: number = 3600): Promise<void> {
323
+ const entry: CacheEntry<T> = {
324
+ key,
325
+ value,
326
+ timestamp: Date.now(),
327
+ ttl,
328
+ hits: 0,
329
+ };
330
+
331
+ // 写入内存
332
+ this.memoryCache.set(key, entry);
333
+
334
+ // 异步写入磁盘
335
+ this.setToDisk(key, entry);
336
+ }
337
+
338
+ // 清理过期缓存
339
+ async cleanup(): Promise<void> {
340
+ const now = Date.now();
341
+ for (const [key, entry] of this.memoryCache) {
342
+ if (now - entry.timestamp > entry.ttl * 1000) {
343
+ this.memoryCache.delete(key);
344
+ await this.deleteFromDisk(key);
345
+ }
346
+ }
347
+ }
348
+ }
349
+ ```
350
+
351
+ #### 缓存策略
352
+
353
+ | 缓存类型 | TTL | 用途 |
354
+ |---------|-----|------|
355
+ | 提示词缓存 | 永久 | 减少重复生成系统提示词 |
356
+ | API 响应缓存 | 1 小时 | 相同问题快速返回 |
357
+ | 会话历史缓存 | 会话期 | 重启后快速恢复上下文 |
358
+ | 配置缓存 | 永久 | 加速配置加载 |
359
+
360
+ #### 收益
361
+ - ✅ 减少 API 调用成本
362
+ - ✅ 提升响应速度
363
+ - ✅ 降低延迟感知
364
+ - ✅ 支持离线模式(部分功能)
365
+
366
+ ---
367
+
368
+ ### 方案 5: 会话持久化增强
369
+
370
+ #### 目标
371
+ 实现完整的会话持久化系统,支持会话的保存、加载、搜索、导出、导入。
372
+
373
+ #### 实施细节
374
+
375
+ ```typescript
376
+ // src/session/persistent-session-manager.ts
377
+ export interface SessionMetadata {
378
+ id: string;
379
+ name: string;
380
+ description?: string;
381
+ tags: string[];
382
+ createdAt: string;
383
+ updatedAt: string;
384
+ messageCount: number;
385
+ role: string;
386
+ severity: string;
387
+ provider: string;
388
+ model: string;
389
+ tokensUsed: number;
390
+ }
391
+
392
+ export class PersistentSessionManager {
393
+ private sessionsDir: string;
394
+
395
+ constructor() {
396
+ this.sessionsDir = path.join(os.homedir(), '.pua-cli', 'sessions');
397
+ this.ensureDir();
398
+ }
399
+
400
+ // 保存会话(包含完整元数据)
401
+ async saveSession(options: {
402
+ name?: string;
403
+ description?: string;
404
+ tags?: string[];
405
+ autoSave?: boolean;
406
+ } = Promise<SessionMetadata> {
407
+ const session: SessionMetadata = {
408
+ id: generateId(),
409
+ name: options.name || '未命名会话',
410
+ description: options.description || '',
411
+ tags: options.tags || [],
412
+ createdAt: new Date().toISOString(),
413
+ updatedAt: new Date().toISOString(),
414
+ messageCount: sessionManager.getMessageCount(),
415
+ role: currentConfig.role,
416
+ severity: currentConfig.severity,
417
+ provider: currentConfig.provider,
418
+ model: currentConfig.model,
419
+ tokensUsed: sessionManager.getTokensUsed(),
420
+ };
421
+
422
+ const filePath = path.join(this.sessionsDir, `${session.id}.json`);
423
+ fs.writeFileSync(filePath, JSON.stringify(session, null, 2));
424
+
425
+ return session;
426
+ }
427
+
428
+ // 搜索会话
429
+ searchSessions(query: string): SessionMetadata[] {
430
+ const sessions = this.listSessions();
431
+
432
+ if (!query) return sessions;
433
+
434
+ const lowerQuery = query.toLowerCase();
435
+ return sessions.filter(s =>
436
+ s.name.toLowerCase().includes(lowerQuery) ||
437
+ s.description?.toLowerCase().includes(lowerQuery) ||
438
+ s.tags.some(tag => tag.toLowerCase().includes(lowerQuery))
439
+ );
440
+ }
441
+
442
+ // 导出会话
443
+ exportSessions(sessionIds: string[], format: 'json' | 'markdown'): void {
444
+ const sessions = sessionIds.map(id => this.loadSession(id))
445
+ .filter(Boolean);
446
+
447
+ const data = format === 'json'
448
+ ? JSON.stringify(sessions, null, 2)
449
+ : this.toMarkdown(sessions);
450
+
451
+ const exportPath = path.join(os.homedir(), 'pua-cli', `sessions-export.${Date.now()}.${format}`);
452
+ fs.writeFileSync(exportPath, data);
453
+
454
+ console.log(`✓ 已导出 ${sessions.length} 个会话到 ${exportPath}`);
455
+ }
456
+
457
+ // 导入会话
458
+ importSessions(importFile: string): void {
459
+ const content = fs.readFileSync(importFile, 'utf-8');
460
+ const sessions = JSON.parse(content) as SessionMetadata[];
461
+
462
+ for (const session of sessions) {
463
+ const filePath = path.join(this.sessionsDir, `${session.id}.json`);
464
+ fs.writeFileSync(filePath, JSON.stringify(session, null, 2));
465
+ }
466
+
467
+ console.log(`✓ 已导入 ${sessions.length} 个会话`);
468
+ }
469
+ }
470
+ ```
471
+
472
+ #### 命令扩展
473
+
474
+ ```bash
475
+ # 新增会话管理命令
476
+ /sessions # 列出所有会话
477
+ /save [名称] [描述...] # 保存当前会话
478
+ /load <ID> # 加载指定会话
479
+ /export <format> # 导出所有会话
480
+ /import <file> # 导入会话文件
481
+ /delete <ID> # 删除指定会话
482
+ /search <关键词> # 搜索会话
483
+ /tag <标签> # 为当前会话添加标签
484
+ ```
485
+
486
+ #### 收益
487
+ - ✅ 重启不丢失上下文
488
+ - ✅ 支持多会话管理
489
+ - ✅ 会话可搜索和分类
490
+ - ✅ 支持会话导出/导入
491
+
492
+ ---
493
+
494
+ ### 方案 6: 成本追踪系统
495
+
496
+ #### 目标
497
+ 实现完整的 token 使用追踪和成本计算功能,帮助用户控制使用成本。
498
+
499
+ #### 实施细节
500
+
501
+ ```typescript
502
+ // src/cost/token-tracker.ts
503
+ export interface TokenUsage {
504
+ provider: string;
505
+ model: string;
506
+ inputTokens: number;
507
+ outputTokens: number;
508
+ cacheHits: number;
509
+ timestamp: number;
510
+ }
511
+
512
+ export interface CostSummary {
513
+ totalTokens: number;
514
+ totalCost: number;
515
+ currency: string;
516
+ breakdownByProvider: Record<string, number>;
517
+ breakdownByModel: Record<string, number>;
518
+ }
519
+
520
+ export class TokenTracker {
521
+ private usageFile: string;
522
+
523
+ // 定价(示例)
524
+ private pricing = {
525
+ zhipu: {
526
+ 'glm-4.7': 0.0005, // 每千 tokens 价格
527
+ 'glm-4.7-flash': 0.0001,
528
+ },
529
+ openai: {
530
+ 'gpt-4o': 0.005,
531
+ 'gpt-4o-mini': 0.00015,
532
+ },
533
+ };
534
+
535
+ constructor() {
536
+ this.usageFile = path.join(os.homedir(), '.pua-cli', 'usage.json');
537
+ }
538
+
539
+ // 记录 token 使用
540
+ async recordUsage(usage: TokenUsage): Promise<void> {
541
+ const record: TokenUsage = {
542
+ ...usage,
543
+ timestamp: Date.now(),
544
+ };
545
+
546
+ const history = this.getUsageHistory();
547
+ history.push(record);
548
+ fs.writeFileSync(this.usageFile, JSON.stringify(history, null, 2));
549
+ }
550
+
551
+ // 获取今日统计
552
+ getTodayStats(): CostSummary {
553
+ const today = new Date().toDateString();
554
+ const history = this.getUsageHistory();
555
+
556
+ const todayUsage = history.filter(u =>
557
+ new Date(u.timestamp).toDateString() === today
558
+ );
559
+
560
+ const totalTokens = todayUsage.reduce((sum, u) => sum + u.outputTokens, 0);
561
+ const totalCost = this.calculateCost(todayUsage);
562
+
563
+ return {
564
+ totalTokens,
565
+ totalCost,
566
+ currency: 'CNY',
567
+ breakdownByProvider: this.groupByProvider(todayUsage),
568
+ breakdownByModel: this.groupByModel(todayUsage),
569
+ };
570
+ }
571
+
572
+ // 显示成本统计
573
+ async showStats(days: number = 7): Promise<void> {
574
+ const stats = this.getStats(days);
575
+
576
+ console.log(`\n📊 Token 使用统计(最近 ${days} 天)\n`);
577
+ console.log('─'.repeat(50));
578
+
579
+ console.log(`总 Token: ${stats.totalTokens.toLocaleString()}`);
580
+ console.log(`总成本: ¥${stats.totalCost.toFixed(2)}`);
581
+ console.log(`\n按 Provider:`);
582
+ for (const [provider, cost] of Object.entries(stats.breakdownByProvider)) {
583
+ console.log(` ${provider}: ¥${cost.toFixed(2)}`);
584
+ }
585
+
586
+ console.log(`\n按模型:`);
587
+ for (const [model, cost] of Object.entries(stats.breakdownByModel)) {
588
+ console.log(` ${model}: ¥${cost.toFixed(2)}`);
589
+ }
590
+ }
591
+ }
592
+ ```
593
+
594
+ #### 命令
595
+
596
+ ```bash
597
+ # 成本管理命令
598
+ /cost # 显示成本统计
599
+ /cost --days 7 # 显示指定天数统计
600
+ /cost --today # 只显示今日
601
+ /cost --export # 导出成本数据
602
+ ```
603
+
604
+ #### 收益
605
+ - ✅ 实时成本监控
606
+ - ✅ 历史趋势分析
607
+ - ✅ 预算提醒
608
+ - ✅ 按 Provider/模型分组统计
609
+
610
+ ---
611
+
612
+ ### 方案 7: 错误处理增强
613
+
614
+ #### 目标
615
+ 实现统一的错误处理机制,包括重试、回退、错误分类等。
616
+
617
+ #### 实施细节
618
+
619
+ ```typescript
620
+ // src/utils/error-handler.ts
621
+ export enum ErrorType {
622
+ NETWORK_ERROR = 'NETWORK_ERROR',
623
+ API_ERROR = 'API_ERROR',
624
+ CONFIG_ERROR = 'CONFIG_ERROR',
625
+ VALIDATION_ERROR = 'VALIDATION_ERROR',
626
+ RATE_LIMIT_ERROR = 'RATE_LIMIT_ERROR',
627
+ }
628
+
629
+ export class AppError extends Error {
630
+ type: ErrorType;
631
+ retryable: boolean;
632
+ userMessage: string;
633
+ originalError?: Error;
634
+
635
+ constructor(
636
+ type: ErrorType,
637
+ message: string,
638
+ retryable: boolean = true,
639
+ originalError?: Error
640
+ ) {
641
+ super(message);
642
+ this.type = type;
643
+ this.retryable = retryable;
644
+ this.originalError = originalError;
645
+ this.name = 'AppError';
646
+ }
647
+ }
648
+
649
+ export class ErrorHandler {
650
+ private maxRetries: number = 3;
651
+ private baseDelay: number = 1000; // 1 秒
652
+
653
+ async withRetry<T>(
654
+ operation: () => Promise<T>,
655
+ context: string,
656
+ options?: {
657
+ maxRetries?: number;
658
+ baseDelay?: number;
659
+ exponentialBackoff?: boolean;
660
+ }
661
+ ): Promise<T> {
662
+ const opts = {
663
+ maxRetries: options?.maxRetries || this.maxRetries,
664
+ baseDelay: options?.baseDelay || this.baseDelay,
665
+ exponentialBackoff: options?.exponentialBackoff ?? true,
666
+ };
667
+
668
+ let lastError: Error | null;
669
+
670
+ for (let attempt = 0; attempt <= opts.maxRetries; attempt++) {
671
+ try {
672
+ const result = await operation();
673
+ return result;
674
+ } catch (error) {
675
+ lastError = error as Error;
676
+
677
+ // 最后一次尝试不重试
678
+ if (attempt === opts.maxRetries) break;
679
+
680
+ // 指数退避
681
+ const delay = opts.exponentialBackoff
682
+ ? opts.baseDelay * Math.pow(2, attempt)
683
+ : opts.baseDelay;
684
+
685
+ await this.sleep(delay);
686
+ }
687
+ }
688
+
689
+ // 所有重试都失败
690
+ throw new AppError(
691
+ ErrorType.NETWORK_ERROR,
692
+ `${context} 失败: ${opts.maxRetries} 次重试`,
693
+ true,
694
+ lastError
695
+ );
696
+ }
697
+
698
+ private sleep(ms: number): Promise<void> {
699
+ return new Promise(resolve => setTimeout(resolve, ms));
700
+ }
701
+
702
+ // 分类错误处理
703
+ handle(error: unknown): void {
704
+ if (error instanceof AppError) {
705
+ switch (error.type) {
706
+ case ErrorType.RATE_LIMIT_ERROR:
707
+ console.warn('⚠️ 请求过于频繁,请稍后重试');
708
+ break;
709
+ case ErrorType.NETWORK_ERROR:
710
+ console.error('❌ 网络错误,请检查网络连接');
711
+ break;
712
+ case ErrorType.VALIDATION_ERROR:
713
+ console.error('❌ 配置错误:', error.message);
714
+ break;
715
+ default:
716
+ console.error('❌ 未知错误:', error.message);
717
+ }
718
+ } else {
719
+ console.error('❌ 未知错误:', String(error));
720
+ }
721
+ }
722
+ }
723
+ ```
724
+
725
+ #### 收益
726
+ - ✅ 网络错误自动重试
727
+ - ✅ 指数退避避免过载
728
+ - ✅ 用户友好的错误提示
729
+ - ✅ 错误分类和针对性处理
730
+
731
+ ---
732
+
733
+ ## 实施优先级
734
+
735
+ ### 第一阶段:基础巩固 (1-2 周)
736
+ - [x] 修复 TypeScript 类型问题
737
+ - [ ] 完善测试覆盖到 60%
738
+ - [ ] 优化错误处理
739
+
740
+ ### 第二阶段:架构升级 (2-4 周)
741
+ - [ ] 迁移到 OCLIF 框架
742
+ - [ ] 实现配置验证系统
743
+ - [ ] 添加缓存机制
744
+
745
+ ### 第三阶段:功能增强 (4-6 周)
746
+ - [ ] 实现插件系统基础
747
+ - [ ] 实现会话持久化增强
748
+ - [ ] 实现成本追踪系统
749
+ - [ ] 添加智能自动补全
750
+
751
+ ### 第四阶段:生态完善 (6-8 周)
752
+ - [ ] 多语言支持
753
+ - [ ] 社区贡献模板
754
+ - [ ] CI/CD 流水线
755
+ - [ ] 完善文档和示例
756
+
757
+ ---
758
+
759
+ ## 参考资源
760
+
761
+ - [OCLIF 规范](https://oclif.io/)
762
+ - [Ink - React for CLIs](https://github.com/vadimdemoneda/ink)
763
+ - [Zod - TypeScript Schema Validation](https://zod.dev/)
764
+ - [Bull - Redis for Node.js](https://github.com/OptimalBits/bull)
765
+ - [node-cache-manager](https://github.com/NodeRedis/node-cache-manager)
766
+ - [Commander.js vs OCLIF](https://stackoverflow.com/questions/38242272/commander-js-vs-oclif)
767
+ - [Pexels Terminal Icons](https://www.pexels.com/) - for CLI icons
768
+
769
+ ---
770
+
771
+ **文档版本**: 1.0.0
772
+ **最后更新**: 2025-02-12