v2er-insight 1.0.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 (205) hide show
  1. package/README.md +215 -0
  2. package/dist/cli/commands/ai.d.ts +13 -0
  3. package/dist/cli/commands/ai.js +153 -0
  4. package/dist/cli/commands/analyze.d.ts +13 -0
  5. package/dist/cli/commands/analyze.js +80 -0
  6. package/dist/cli/commands/config.d.ts +43 -0
  7. package/dist/cli/commands/config.js +267 -0
  8. package/dist/cli/commands/fetch.d.ts +13 -0
  9. package/dist/cli/commands/fetch.js +150 -0
  10. package/dist/cli/commands/index.d.ts +10 -0
  11. package/dist/cli/commands/index.js +22 -0
  12. package/dist/cli/commands/run.d.ts +23 -0
  13. package/dist/cli/commands/run.js +52 -0
  14. package/dist/cli/commands/show.d.ts +13 -0
  15. package/dist/cli/commands/show.js +154 -0
  16. package/dist/cli/index.d.ts +6 -0
  17. package/dist/cli/index.js +107 -0
  18. package/dist/cli/types.d.ts +58 -0
  19. package/dist/cli/types.js +6 -0
  20. package/dist/cli/utils/error.d.ts +6 -0
  21. package/dist/cli/utils/error.js +18 -0
  22. package/dist/cli/utils.d.ts +20 -0
  23. package/dist/cli/utils.js +48 -0
  24. package/dist/cli/workflow/orchestrator.d.ts +15 -0
  25. package/dist/cli/workflow/orchestrator.js +144 -0
  26. package/dist/cli/workflow/recovery.d.ts +10 -0
  27. package/dist/cli/workflow/recovery.js +134 -0
  28. package/dist/cli/workflow/state.d.ts +19 -0
  29. package/dist/cli/workflow/state.js +45 -0
  30. package/dist/cli/workflow/types.d.ts +60 -0
  31. package/dist/cli/workflow/types.js +3 -0
  32. package/dist/config/defaults.d.ts +48 -0
  33. package/dist/config/defaults.js +42 -0
  34. package/dist/config/index.d.ts +16 -0
  35. package/dist/config/index.js +21 -0
  36. package/dist/config/path.d.ts +11 -0
  37. package/dist/config/path.js +28 -0
  38. package/dist/config/proxy.d.ts +16 -0
  39. package/dist/config/proxy.js +39 -0
  40. package/dist/config/storage.d.ts +23 -0
  41. package/dist/config/storage.js +85 -0
  42. package/dist/config/types/ai.d.ts +31 -0
  43. package/dist/config/types/ai.js +13 -0
  44. package/dist/config/types/analyzer.d.ts +15 -0
  45. package/dist/config/types/analyzer.js +6 -0
  46. package/dist/config/types/data.d.ts +20 -0
  47. package/dist/config/types/data.js +6 -0
  48. package/dist/config/types/fetch.d.ts +9 -0
  49. package/dist/config/types/fetch.js +6 -0
  50. package/dist/config/types/index.d.ts +32 -0
  51. package/dist/config/types/index.js +11 -0
  52. package/dist/config/types/log.d.ts +11 -0
  53. package/dist/config/types/log.js +6 -0
  54. package/dist/core/ai/index.d.ts +11 -0
  55. package/dist/core/ai/index.js +18 -0
  56. package/dist/core/ai/parser/index.d.ts +12 -0
  57. package/dist/core/ai/parser/index.js +44 -0
  58. package/dist/core/ai/parser/validator.d.ts +18 -0
  59. package/dist/core/ai/parser/validator.js +179 -0
  60. package/dist/core/ai/prompt/index.d.ts +20 -0
  61. package/dist/core/ai/prompt/index.js +75 -0
  62. package/dist/core/ai/prompt/system-prompt.md +210 -0
  63. package/dist/core/ai/providers/gemini.d.ts +25 -0
  64. package/dist/core/ai/providers/gemini.js +74 -0
  65. package/dist/core/ai/providers/index.d.ts +6 -0
  66. package/dist/core/ai/providers/index.js +9 -0
  67. package/dist/core/ai/types/index.d.ts +7 -0
  68. package/dist/core/ai/types/index.js +6 -0
  69. package/dist/core/ai/types/options.d.ts +14 -0
  70. package/dist/core/ai/types/options.js +6 -0
  71. package/dist/core/ai/types/provider.d.ts +19 -0
  72. package/dist/core/ai/types/provider.js +6 -0
  73. package/dist/core/ai/types/result.d.ts +64 -0
  74. package/dist/core/ai/types/result.js +6 -0
  75. package/dist/core/ai/utils/api-key.d.ts +15 -0
  76. package/dist/core/ai/utils/api-key.js +36 -0
  77. package/dist/core/ai/utils/index.d.ts +6 -0
  78. package/dist/core/ai/utils/index.js +11 -0
  79. package/dist/core/ai/utils/retry.d.ts +15 -0
  80. package/dist/core/ai/utils/retry.js +37 -0
  81. package/dist/core/analyzer/builder.d.ts +23 -0
  82. package/dist/core/analyzer/builder.js +113 -0
  83. package/dist/core/analyzer/content/chunker.d.ts +18 -0
  84. package/dist/core/analyzer/content/chunker.js +74 -0
  85. package/dist/core/analyzer/content/index.d.ts +7 -0
  86. package/dist/core/analyzer/content/index.js +13 -0
  87. package/dist/core/analyzer/content/transformer.d.ts +19 -0
  88. package/dist/core/analyzer/content/transformer.js +33 -0
  89. package/dist/core/analyzer/index.d.ts +17 -0
  90. package/dist/core/analyzer/index.js +21 -0
  91. package/dist/core/analyzer/periods/detector.d.ts +17 -0
  92. package/dist/core/analyzer/periods/detector.js +36 -0
  93. package/dist/core/analyzer/periods/index.d.ts +6 -0
  94. package/dist/core/analyzer/periods/index.js +11 -0
  95. package/dist/core/analyzer/periods/splitter.d.ts +11 -0
  96. package/dist/core/analyzer/periods/splitter.js +35 -0
  97. package/dist/core/analyzer/stats/index.d.ts +7 -0
  98. package/dist/core/analyzer/stats/index.js +13 -0
  99. package/dist/core/analyzer/stats/reply-stats.d.ts +15 -0
  100. package/dist/core/analyzer/stats/reply-stats.js +45 -0
  101. package/dist/core/analyzer/stats/topic-stats.d.ts +16 -0
  102. package/dist/core/analyzer/stats/topic-stats.js +51 -0
  103. package/dist/core/analyzer/stats/user-overview.d.ts +9 -0
  104. package/dist/core/analyzer/stats/user-overview.js +52 -0
  105. package/dist/core/analyzer/types/index.d.ts +7 -0
  106. package/dist/core/analyzer/types/index.js +6 -0
  107. package/dist/core/analyzer/types/input.d.ts +13 -0
  108. package/dist/core/analyzer/types/input.js +6 -0
  109. package/dist/core/analyzer/types/internal.d.ts +28 -0
  110. package/dist/core/analyzer/types/internal.js +6 -0
  111. package/dist/core/analyzer/types/output.d.ts +68 -0
  112. package/dist/core/analyzer/types/output.js +6 -0
  113. package/dist/core/analyzer/utils/date-parser.d.ts +41 -0
  114. package/dist/core/analyzer/utils/date-parser.js +118 -0
  115. package/dist/core/analyzer/utils/index.d.ts +6 -0
  116. package/dist/core/analyzer/utils/index.js +18 -0
  117. package/dist/core/analyzer/utils/stats.d.ts +12 -0
  118. package/dist/core/analyzer/utils/stats.js +64 -0
  119. package/dist/core/v2ex/index.d.ts +10 -0
  120. package/dist/core/v2ex/index.js +27 -0
  121. package/dist/core/v2ex/parsers/index.d.ts +8 -0
  122. package/dist/core/v2ex/parsers/index.js +15 -0
  123. package/dist/core/v2ex/parsers/replies-page.d.ts +11 -0
  124. package/dist/core/v2ex/parsers/replies-page.js +114 -0
  125. package/dist/core/v2ex/parsers/selectors/index.d.ts +10 -0
  126. package/dist/core/v2ex/parsers/selectors/index.js +18 -0
  127. package/dist/core/v2ex/parsers/selectors/pagination.d.ts +11 -0
  128. package/dist/core/v2ex/parsers/selectors/pagination.js +14 -0
  129. package/dist/core/v2ex/parsers/selectors/replies-page.d.ts +21 -0
  130. package/dist/core/v2ex/parsers/selectors/replies-page.js +24 -0
  131. package/dist/core/v2ex/parsers/selectors/topic-detail.d.ts +19 -0
  132. package/dist/core/v2ex/parsers/selectors/topic-detail.js +22 -0
  133. package/dist/core/v2ex/parsers/selectors/topics-list-page.d.ts +11 -0
  134. package/dist/core/v2ex/parsers/selectors/topics-list-page.js +14 -0
  135. package/dist/core/v2ex/parsers/selectors/user-profile.d.ts +11 -0
  136. package/dist/core/v2ex/parsers/selectors/user-profile.js +14 -0
  137. package/dist/core/v2ex/parsers/topic-detail.d.ts +11 -0
  138. package/dist/core/v2ex/parsers/topic-detail.js +94 -0
  139. package/dist/core/v2ex/parsers/topics-list-page.d.ts +11 -0
  140. package/dist/core/v2ex/parsers/topics-list-page.js +90 -0
  141. package/dist/core/v2ex/parsers/user-profile.d.ts +11 -0
  142. package/dist/core/v2ex/parsers/user-profile.js +70 -0
  143. package/dist/core/v2ex/parsers/utils/index.d.ts +6 -0
  144. package/dist/core/v2ex/parsers/utils/index.js +9 -0
  145. package/dist/core/v2ex/parsers/utils/pagination.d.ts +19 -0
  146. package/dist/core/v2ex/parsers/utils/pagination.js +29 -0
  147. package/dist/core/v2ex/types/entities.d.ts +45 -0
  148. package/dist/core/v2ex/types/entities.js +7 -0
  149. package/dist/core/v2ex/types/index.d.ts +6 -0
  150. package/dist/core/v2ex/types/index.js +6 -0
  151. package/dist/core/v2ex/types/parse-result.d.ts +64 -0
  152. package/dist/core/v2ex/types/parse-result.js +7 -0
  153. package/dist/core/v2ex/urls/constants.d.ts +5 -0
  154. package/dist/core/v2ex/urls/constants.js +8 -0
  155. package/dist/core/v2ex/urls/index.d.ts +7 -0
  156. package/dist/core/v2ex/urls/index.js +16 -0
  157. package/dist/core/v2ex/urls/topic-urls.d.ts +19 -0
  158. package/dist/core/v2ex/urls/topic-urls.js +48 -0
  159. package/dist/core/v2ex/urls/user-urls.d.ts +24 -0
  160. package/dist/core/v2ex/urls/user-urls.js +36 -0
  161. package/dist/core/v2ex/use-cases/index.d.ts +8 -0
  162. package/dist/core/v2ex/use-cases/index.js +14 -0
  163. package/dist/core/v2ex/use-cases/types.d.ts +31 -0
  164. package/dist/core/v2ex/use-cases/types.js +7 -0
  165. package/dist/core/v2ex/use-cases/user/index.d.ts +10 -0
  166. package/dist/core/v2ex/use-cases/user/index.js +16 -0
  167. package/dist/core/v2ex/use-cases/user/profile.d.ts +14 -0
  168. package/dist/core/v2ex/use-cases/user/profile.js +51 -0
  169. package/dist/core/v2ex/use-cases/user/replies.d.ts +14 -0
  170. package/dist/core/v2ex/use-cases/user/replies.js +20 -0
  171. package/dist/core/v2ex/use-cases/user/topic-urls.d.ts +21 -0
  172. package/dist/core/v2ex/use-cases/user/topic-urls.js +29 -0
  173. package/dist/core/v2ex/use-cases/user/topics-detail.d.ts +30 -0
  174. package/dist/core/v2ex/use-cases/user/topics-detail.js +62 -0
  175. package/dist/core/v2ex/use-cases/utils/index.d.ts +6 -0
  176. package/dist/core/v2ex/use-cases/utils/index.js +9 -0
  177. package/dist/core/v2ex/use-cases/utils/page-orchestrator.d.ts +24 -0
  178. package/dist/core/v2ex/use-cases/utils/page-orchestrator.js +93 -0
  179. package/dist/infra/fetcher/agent.d.ts +10 -0
  180. package/dist/infra/fetcher/agent.js +17 -0
  181. package/dist/infra/fetcher/fetcher.d.ts +10 -0
  182. package/dist/infra/fetcher/fetcher.js +81 -0
  183. package/dist/infra/fetcher/index.d.ts +3 -0
  184. package/dist/infra/fetcher/index.js +19 -0
  185. package/dist/infra/fetcher/types.d.ts +29 -0
  186. package/dist/infra/fetcher/types.js +6 -0
  187. package/dist/infra/logger/colors.d.ts +15 -0
  188. package/dist/infra/logger/colors.js +18 -0
  189. package/dist/infra/logger/index.d.ts +16 -0
  190. package/dist/infra/logger/index.js +19 -0
  191. package/dist/infra/logger/logger.d.ts +34 -0
  192. package/dist/infra/logger/logger.js +101 -0
  193. package/dist/infra/storage/cleaner.d.ts +24 -0
  194. package/dist/infra/storage/cleaner.js +73 -0
  195. package/dist/infra/storage/index.d.ts +7 -0
  196. package/dist/infra/storage/index.js +15 -0
  197. package/dist/infra/storage/paths.d.ts +26 -0
  198. package/dist/infra/storage/paths.js +53 -0
  199. package/dist/infra/storage/reader.d.ts +15 -0
  200. package/dist/infra/storage/reader.js +34 -0
  201. package/dist/infra/storage/types.d.ts +21 -0
  202. package/dist/infra/storage/types.js +18 -0
  203. package/dist/infra/storage/writer.d.ts +16 -0
  204. package/dist/infra/storage/writer.js +31 -0
  205. package/package.json +89 -0
@@ -0,0 +1,267 @@
1
+ "use strict";
2
+ /**
3
+ * config 命令
4
+ *
5
+ * 提供配置的查看、设置和代理管理功能。
6
+ * 所有配置持久化到 ~/.v2er-insight/config.json。
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.configProxy = configProxy;
10
+ exports.configShow = configShow;
11
+ exports.configSet = configSet;
12
+ exports.configReset = configReset;
13
+ const config_1 = require("../../config");
14
+ const logger_1 = require("../../infra/logger");
15
+ /**
16
+ * 合法配置路径白名单
17
+ *
18
+ * 作用:
19
+ * 1. 校验用户输入的 dotpath 是否合法
20
+ * 2. 自动推断值类型并做类型转换
21
+ * 3. 枚举路径做候选值校验
22
+ */
23
+ // NOTE: 枚举 values 与类型定义(ThinkingLevel、LogLevel 等)存在耦合,新增枚举值时需同步更新。
24
+ // 未来可考虑从类型定义中统一导出枚举值以减少维护负担。
25
+ const CONFIG_PATHS = {
26
+ // 顶层
27
+ proxy: { type: 'string' },
28
+ // AI
29
+ 'ai.provider': { type: 'enum', values: ['gemini'] },
30
+ 'ai.apiKey': { type: 'string' },
31
+ 'ai.model': { type: 'string' },
32
+ 'ai.thinkingLevel': { type: 'enum', values: ['minimal', 'low', 'medium', 'high'] },
33
+ 'ai.timeout': { type: 'number' },
34
+ 'ai.maxRetries': { type: 'number' },
35
+ 'ai.baseDelay': { type: 'number' },
36
+ 'ai.maxDelay': { type: 'number' },
37
+ // Fetch
38
+ 'fetch.timeout': { type: 'number' },
39
+ // Analyzer
40
+ 'analyzer.inactivityThreshold': { type: 'number' },
41
+ 'analyzer.chunkMaxTopics': { type: 'number' },
42
+ 'analyzer.chunkMaxReplies': { type: 'number' },
43
+ 'analyzer.nodeDistributionTopN': { type: 'number' },
44
+ // Data
45
+ 'data.keepRaw': { type: 'boolean' },
46
+ 'data.rawRetention': { type: 'number' },
47
+ // Log
48
+ 'log.level': { type: 'enum', values: ['error', 'warn', 'info', 'debug'] },
49
+ };
50
+ /** 所有合法的顶层分组名 */
51
+ const CONFIG_GROUPS = ['ai', 'fetch', 'analyzer', 'data', 'log'];
52
+ // -- 工具函数 -----------------------------------------------------------------
53
+ /**
54
+ * 掩码敏感字段(如 apiKey)
55
+ *
56
+ * 保留前 4 位和后 4 位,中间用 **** 替代。
57
+ * 长度不足 8 位时全部掩码。
58
+ */
59
+ function maskSensitive(value) {
60
+ if (value.length <= 8)
61
+ return '****';
62
+ return `${value.slice(0, 4)}****${value.slice(-4)}`;
63
+ }
64
+ /**
65
+ * 格式化配置值用于显示
66
+ *
67
+ * 对 apiKey 字段做掩码处理,其余原样输出。
68
+ */
69
+ // NOTE: 目前仅 ai.apiKey 需要掩码。若新增其他敏感字段,考虑用元数据驱动替代硬编码。
70
+ function formatConfigForDisplay(config) {
71
+ const display = JSON.parse(JSON.stringify(config));
72
+ if (display.ai?.apiKey) {
73
+ display.ai.apiKey = maskSensitive(display.ai.apiKey);
74
+ }
75
+ return display;
76
+ }
77
+ /**
78
+ * 将字符串值按目标类型转换
79
+ */
80
+ function coerceValue(raw, meta) {
81
+ switch (meta.type) {
82
+ case 'boolean':
83
+ if (raw === 'true')
84
+ return true;
85
+ if (raw === 'false')
86
+ return false;
87
+ throw new Error(`Invalid boolean value: "${raw}" (expected: true | false)`);
88
+ case 'number': {
89
+ const num = Number(raw);
90
+ if (raw.trim() === '' || !Number.isFinite(num) || num < 0) {
91
+ throw new Error(`Invalid number value: "${raw}" (expected non-negative finite number)`);
92
+ }
93
+ return num;
94
+ }
95
+ case 'enum':
96
+ if (!meta.values?.includes(raw)) {
97
+ throw new Error(`Invalid value: "${raw}" (expected: ${meta.values?.join(' | ')})`);
98
+ }
99
+ return raw;
100
+ case 'string':
101
+ default:
102
+ return raw;
103
+ }
104
+ }
105
+ /**
106
+ * 按点分路径设置深层值
107
+ *
108
+ * 例如 setByPath(obj, 'ai.model', 'gemini-2.5-flash')
109
+ * → obj.ai.model = 'gemini-2.5-flash'
110
+ */
111
+ function setByPath(obj, dotPath, value) {
112
+ const parts = dotPath.split('.');
113
+ // 顶层字段直接设置
114
+ if (parts.length === 1) {
115
+ obj[parts[0]] = value;
116
+ return;
117
+ }
118
+ // 嵌套字段:确保中间对象存在
119
+ let current = obj;
120
+ for (let i = 0; i < parts.length - 1; i++) {
121
+ const key = parts[i];
122
+ if (typeof current[key] !== 'object' || current[key] === null) {
123
+ current[key] = {};
124
+ }
125
+ current = current[key];
126
+ }
127
+ current[parts[parts.length - 1]] = value;
128
+ }
129
+ /**
130
+ * 代理配置命令
131
+ */
132
+ function configProxy(url, options) {
133
+ const config = (0, config_1.readConfig)();
134
+ // 清除代理
135
+ if (options?.clear) {
136
+ delete config.proxy;
137
+ (0, config_1.writeConfig)(config);
138
+ logger_1.logger.info('Proxy cleared');
139
+ return;
140
+ }
141
+ // 设置代理
142
+ if (url) {
143
+ config.proxy = url;
144
+ (0, config_1.writeConfig)(config);
145
+ logger_1.logger.info(`Proxy set to: ${url}`);
146
+ logger_1.logger.detail(`Config file: ${(0, config_1.getConfigPath)()}`);
147
+ return;
148
+ }
149
+ // 查看代理
150
+ if (config.proxy) {
151
+ logger_1.logger.info(`Current proxy: ${config.proxy}`);
152
+ }
153
+ else {
154
+ logger_1.logger.info('No proxy configured');
155
+ logger_1.logger.detail('Use: v2er config proxy <url>');
156
+ }
157
+ }
158
+ /**
159
+ * 查看配置
160
+ *
161
+ * 无参数显示完整配置,传 group 显示指定分组。
162
+ */
163
+ function configShow(group) {
164
+ const config = (0, config_1.getConfig)();
165
+ const display = formatConfigForDisplay(config);
166
+ // 指定分组
167
+ if (group) {
168
+ // proxy 作为特殊的顶层字段
169
+ if (group === 'proxy') {
170
+ if (display.proxy) {
171
+ logger_1.logger.info(`[proxy] ${display.proxy}`);
172
+ }
173
+ else {
174
+ logger_1.logger.info('[proxy] (not set)');
175
+ }
176
+ return;
177
+ }
178
+ if (!CONFIG_GROUPS.includes(group)) {
179
+ logger_1.logger.error(`Unknown config group: "${group}"`);
180
+ logger_1.logger.detail(`Available groups: proxy, ${CONFIG_GROUPS.join(', ')}`);
181
+ return;
182
+ }
183
+ const groupConfig = display[group];
184
+ logger_1.logger.info(`[${group}]`);
185
+ console.log(JSON.stringify(groupConfig, null, 2));
186
+ return;
187
+ }
188
+ // 全量显示
189
+ console.log(JSON.stringify(display, null, 2));
190
+ logger_1.logger.detail(`\nConfig file: ${(0, config_1.getConfigPath)()}`);
191
+ }
192
+ /**
193
+ * 设置配置项
194
+ *
195
+ * 支持点分路径,自动类型转换和枚举校验。
196
+ *
197
+ * 示例:
198
+ * configSet('ai.model', 'gemini-2.5-flash')
199
+ * configSet('data.keepRaw', 'true')
200
+ * configSet('log.level', 'debug')
201
+ */
202
+ function configSet(dotPath, rawValue) {
203
+ // 路径白名单校验
204
+ const meta = CONFIG_PATHS[dotPath];
205
+ if (!meta) {
206
+ logger_1.logger.error(`Unknown config path: "${dotPath}"`);
207
+ logger_1.logger.detail('Available paths:');
208
+ for (const path of Object.keys(CONFIG_PATHS)) {
209
+ logger_1.logger.detail(` ${path}`);
210
+ }
211
+ return;
212
+ }
213
+ // 类型转换 + 枚举校验
214
+ let value;
215
+ try {
216
+ value = coerceValue(rawValue, meta);
217
+ }
218
+ catch (error) {
219
+ const message = error instanceof Error ? error.message : String(error);
220
+ logger_1.logger.error(message);
221
+ return;
222
+ }
223
+ // 读取 → 设置 → 写入
224
+ const config = (0, config_1.readConfig)();
225
+ setByPath(config, dotPath, value);
226
+ (0, config_1.writeConfig)(config);
227
+ // 敏感值掩码显示
228
+ const displayValue = dotPath.endsWith('apiKey') ? maskSensitive(String(value)) : String(value);
229
+ logger_1.logger.info(`Set ${dotPath} = ${displayValue}`);
230
+ }
231
+ /**
232
+ * 重置配置
233
+ *
234
+ * 无参数清空整个配置文件(恢复全部默认值)。
235
+ * 传 group 只清除指定分组。
236
+ *
237
+ * 示例:
238
+ * configReset() → 清空所有用户配置
239
+ * configReset('ai') → 仅清除 ai 分组
240
+ */
241
+ function configReset(group) {
242
+ // 指定分组
243
+ if (group) {
244
+ // 支持 proxy 作为特殊的顶层字段
245
+ if (group === 'proxy') {
246
+ const config = (0, config_1.readConfig)();
247
+ delete config.proxy;
248
+ (0, config_1.writeConfig)(config);
249
+ logger_1.logger.info('Reset: proxy');
250
+ return;
251
+ }
252
+ if (!CONFIG_GROUPS.includes(group)) {
253
+ logger_1.logger.error(`Unknown config group: "${group}"`);
254
+ logger_1.logger.detail(`Available groups: proxy, ${CONFIG_GROUPS.join(', ')}`);
255
+ return;
256
+ }
257
+ const config = (0, config_1.readConfig)();
258
+ delete config[group];
259
+ (0, config_1.writeConfig)(config);
260
+ logger_1.logger.info(`Reset: ${group}`);
261
+ return;
262
+ }
263
+ // 全部清空
264
+ (0, config_1.writeConfig)({});
265
+ logger_1.logger.info('All configuration reset to defaults');
266
+ }
267
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1,13 @@
1
+ /**
2
+ * fetch 命令 — 抓取 V2EX 用户数据并持久化
3
+ *
4
+ * 调用 V2EX use-cases 获取 profile + topics detail + replies,
5
+ * 组装为 RawUserData 结构后写入 raw.json。
6
+ */
7
+ import type { StepRunResult } from '../workflow/types';
8
+ import type { FetchCommandOptions } from '../types';
9
+ /**
10
+ * 执行 fetch 命令
11
+ */
12
+ export declare function runFetch(username: string, options: FetchCommandOptions): Promise<StepRunResult>;
13
+ //# sourceMappingURL=fetch.d.ts.map
@@ -0,0 +1,150 @@
1
+ "use strict";
2
+ /**
3
+ * fetch 命令 — 抓取 V2EX 用户数据并持久化
4
+ *
5
+ * 调用 V2EX use-cases 获取 profile + topics detail + replies,
6
+ * 组装为 RawUserData 结构后写入 raw.json。
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.runFetch = runFetch;
10
+ const v2ex_1 = require("../../core/v2ex");
11
+ const storage_1 = require("../../infra/storage");
12
+ const logger_1 = require("../../infra/logger");
13
+ const recovery_1 = require("../workflow/recovery");
14
+ const utils_1 = require("../utils");
15
+ // -- 内部工具 ----------------------------------------------------------------
16
+ /**
17
+ * 打印抓取摘要
18
+ */
19
+ function printSummary(profileOk, topicsResult, replies) {
20
+ logger_1.logger.section('=== 抓取摘要 ===');
21
+ logger_1.logger.detail(`Profile: ${profileOk ? 'OK' : 'Failed'}`);
22
+ if (topicsResult) {
23
+ if (topicsResult.isHidden) {
24
+ logger_1.logger.detail('Topics: Hidden');
25
+ }
26
+ else {
27
+ const failed = topicsResult.failedTopics > 0 ? ` (${topicsResult.failedTopics} failed)` : '';
28
+ logger_1.logger.detail(`Topics: ${topicsResult.fetchedTopics}/${topicsResult.totalTopics}${failed}`);
29
+ }
30
+ }
31
+ if (replies) {
32
+ const failed = replies.failedPages > 0 ? ` (${replies.failedPages} failed)` : '';
33
+ logger_1.logger.detail(`Replies: ${replies.data.length}${failed}`);
34
+ }
35
+ }
36
+ // -- 命令入口 ----------------------------------------------------------------
37
+ /**
38
+ * 执行 fetch 命令
39
+ */
40
+ async function runFetch(username, options) {
41
+ // 缓存检查
42
+ if (!options.force) {
43
+ const existing = (0, storage_1.readDataFile)(username, 'raw');
44
+ if (existing) {
45
+ logger_1.logger.info(`已存在 ${username} 的抓取数据,使用 --force 强制重新抓取`);
46
+ return {
47
+ step: 'fetch',
48
+ status: 'skipped',
49
+ message: 'raw.json 已存在,跳过抓取',
50
+ };
51
+ }
52
+ }
53
+ logger_1.logger.info(`\n抓取用户数据: ${username}`);
54
+ // 默认两者都抓取,除非显式指定了其中之一
55
+ const fetchTopics = options.topics || !options.replies;
56
+ const fetchReplies = options.replies || !options.topics;
57
+ let profile = null;
58
+ let topicsResult = null;
59
+ let replies = null;
60
+ // 1. 获取用户资料
61
+ logger_1.logger.section('获取用户资料...');
62
+ profile = await (0, v2ex_1.getUserProfile)(username, {
63
+ events: (0, utils_1.createFetchEvents)('获取资料'),
64
+ });
65
+ if (profile) {
66
+ logger_1.logger.success(`注册于 ${profile.joinDate}`);
67
+ if (profile.dailyRanking) {
68
+ logger_1.logger.detail(`活跃排名: #${profile.dailyRanking}`);
69
+ }
70
+ }
71
+ else {
72
+ logger_1.logger.error('获取用户资料失败');
73
+ return {
74
+ step: 'fetch',
75
+ status: 'failed',
76
+ reasonCode: 'FETCH_PROFILE_FAILED',
77
+ message: '获取用户资料失败',
78
+ recoverable: true,
79
+ recoverActions: (0, recovery_1.getRecoveryActions)('FETCH_PROFILE_FAILED', { username }),
80
+ };
81
+ }
82
+ // 2. 获取帖子详情
83
+ if (fetchTopics) {
84
+ logger_1.logger.section('获取帖子详情...');
85
+ topicsResult = await (0, v2ex_1.getAllUserTopicsDetail)(username, {
86
+ events: (0, utils_1.createFetchEvents)('获取帖子'),
87
+ });
88
+ if (topicsResult.isHidden) {
89
+ logger_1.logger.detail('帖子列表被用户隐藏');
90
+ }
91
+ else {
92
+ logger_1.logger.success(`${topicsResult.fetchedTopics} 篇帖子`);
93
+ if (topicsResult.failedTopics > 0) {
94
+ logger_1.logger.detail(`(${topicsResult.failedTopics} 篇获取失败)`);
95
+ }
96
+ }
97
+ }
98
+ // 3. 获取回复
99
+ if (fetchReplies) {
100
+ logger_1.logger.section('获取回复...');
101
+ replies = await (0, v2ex_1.getAllUserReplies)(username, {
102
+ events: (0, utils_1.createFetchEvents)('获取回复'),
103
+ });
104
+ logger_1.logger.success(`${replies.data.length} 条回复`);
105
+ if (replies.failedPages > 0) {
106
+ logger_1.logger.detail(`(${replies.failedPages} 页获取失败)`);
107
+ }
108
+ }
109
+ // 4. 组装 RawUserData 并持久化
110
+ const rawData = {
111
+ profile,
112
+ topics: topicsResult?.topics ?? [],
113
+ replies: replies?.data ?? [],
114
+ isTopicsHidden: topicsResult?.isHidden ?? false,
115
+ };
116
+ (0, storage_1.writeDataFile)(username, 'raw', rawData);
117
+ if (!options.pipeline) {
118
+ logger_1.logger.success('数据已保存');
119
+ printSummary(!!profile, topicsResult, replies);
120
+ }
121
+ const failedTopics = topicsResult?.failedTopics ?? 0;
122
+ const failedPages = replies?.failedPages ?? 0;
123
+ const isPartial = failedTopics > 0 || failedPages > 0;
124
+ if (isPartial) {
125
+ return {
126
+ step: 'fetch',
127
+ status: 'partial',
128
+ reasonCode: 'FETCH_PARTIAL_FAILED',
129
+ message: '抓取已完成,但存在部分页面失败',
130
+ recoverable: true,
131
+ recoverActions: (0, recovery_1.getRecoveryActions)('FETCH_PARTIAL_FAILED', { username }),
132
+ meta: {
133
+ failedTopics,
134
+ failedPages,
135
+ // TODO(ia319): 在实现 --retry 后补充真实失败页索引 [2026-02-14]
136
+ failedPageIndices: [],
137
+ },
138
+ };
139
+ }
140
+ return {
141
+ step: 'fetch',
142
+ status: 'success',
143
+ message: '抓取完成',
144
+ meta: {
145
+ fetchedTopics: topicsResult?.fetchedTopics ?? 0,
146
+ fetchedReplies: replies?.data.length ?? 0,
147
+ },
148
+ };
149
+ }
150
+ //# sourceMappingURL=fetch.js.map
@@ -0,0 +1,10 @@
1
+ /**
2
+ * CLI 命令导出
3
+ */
4
+ export { runFetch } from './fetch';
5
+ export { runAnalyze } from './analyze';
6
+ export { runAi } from './ai';
7
+ export { runShow } from './show';
8
+ export { runPipeline } from './run';
9
+ export { configProxy, configShow, configSet, configReset } from './config';
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ /**
3
+ * CLI 命令导出
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.configReset = exports.configSet = exports.configShow = exports.configProxy = exports.runPipeline = exports.runShow = exports.runAi = exports.runAnalyze = exports.runFetch = void 0;
7
+ var fetch_1 = require("./fetch");
8
+ Object.defineProperty(exports, "runFetch", { enumerable: true, get: function () { return fetch_1.runFetch; } });
9
+ var analyze_1 = require("./analyze");
10
+ Object.defineProperty(exports, "runAnalyze", { enumerable: true, get: function () { return analyze_1.runAnalyze; } });
11
+ var ai_1 = require("./ai");
12
+ Object.defineProperty(exports, "runAi", { enumerable: true, get: function () { return ai_1.runAi; } });
13
+ var show_1 = require("./show");
14
+ Object.defineProperty(exports, "runShow", { enumerable: true, get: function () { return show_1.runShow; } });
15
+ var run_1 = require("./run");
16
+ Object.defineProperty(exports, "runPipeline", { enumerable: true, get: function () { return run_1.runPipeline; } });
17
+ var config_1 = require("./config");
18
+ Object.defineProperty(exports, "configProxy", { enumerable: true, get: function () { return config_1.configProxy; } });
19
+ Object.defineProperty(exports, "configShow", { enumerable: true, get: function () { return config_1.configShow; } });
20
+ Object.defineProperty(exports, "configSet", { enumerable: true, get: function () { return config_1.configSet; } });
21
+ Object.defineProperty(exports, "configReset", { enumerable: true, get: function () { return config_1.configReset; } });
22
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,23 @@
1
+ /**
2
+ * 主命令 `v2er <username>` 入口
3
+ *
4
+ * 职责:
5
+ * 1. 将 Commander 解析的选项转换为 RunWorkflowOptions
6
+ * 2. 调用 orchestrator 执行一键工作流
7
+ * 3. 根据执行结果设置 process.exitCode
8
+ */
9
+ import type { RunCommandOptions } from '../types';
10
+ import type { RunWorkflowOptions } from '../workflow/types';
11
+ /**
12
+ * 将 Commander 解析的 CLI 选项转化为 orchestrator 所需的类型安全选项。
13
+ *
14
+ * 处理 Commander optional value 的特殊行为:
15
+ * - `--model gemini-2.5-pro` → 直接透传字符串
16
+ * - `--model`(无值)→ Commander 设为 true,此处暂按 undefined 处理(Task 7 接入交互选择)
17
+ */
18
+ export declare function resolveWorkflowOptions(username: string, options: RunCommandOptions): RunWorkflowOptions;
19
+ /**
20
+ * `v2er <username>` 主命令执行入口,由 cli/index.ts 注册调用。
21
+ */
22
+ export declare function runPipeline(username: string, options: RunCommandOptions): Promise<void>;
23
+ //# sourceMappingURL=run.d.ts.map
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ /**
3
+ * 主命令 `v2er <username>` 入口
4
+ *
5
+ * 职责:
6
+ * 1. 将 Commander 解析的选项转换为 RunWorkflowOptions
7
+ * 2. 调用 orchestrator 执行一键工作流
8
+ * 3. 根据执行结果设置 process.exitCode
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.resolveWorkflowOptions = resolveWorkflowOptions;
12
+ exports.runPipeline = runPipeline;
13
+ const logger_1 = require("../../infra/logger");
14
+ const error_1 = require("../utils/error");
15
+ const orchestrator_1 = require("../workflow/orchestrator");
16
+ /**
17
+ * 将 Commander 解析的 CLI 选项转化为 orchestrator 所需的类型安全选项。
18
+ *
19
+ * 处理 Commander optional value 的特殊行为:
20
+ * - `--model gemini-2.5-pro` → 直接透传字符串
21
+ * - `--model`(无值)→ Commander 设为 true,此处暂按 undefined 处理(Task 7 接入交互选择)
22
+ */
23
+ function resolveWorkflowOptions(username, options) {
24
+ const model = typeof options.model === 'string' ? options.model : undefined;
25
+ const thinkingLevel = typeof options.thinkingLevel === 'string' ? options.thinkingLevel : undefined;
26
+ return {
27
+ username,
28
+ force: options.force,
29
+ model,
30
+ thinkingLevel,
31
+ verbose: options.verbose,
32
+ };
33
+ }
34
+ /**
35
+ * `v2er <username>` 主命令执行入口,由 cli/index.ts 注册调用。
36
+ */
37
+ async function runPipeline(username, options) {
38
+ try {
39
+ if (options.verbose) {
40
+ logger_1.logger.setLevel('debug');
41
+ }
42
+ const workflowOptions = resolveWorkflowOptions(username, options);
43
+ const outcome = await (0, orchestrator_1.runWorkflow)(workflowOptions);
44
+ process.exitCode = outcome.exitCode;
45
+ }
46
+ catch (error) {
47
+ const { message } = (0, error_1.extractErrorDetails)(error);
48
+ logger_1.logger.error(`工作流执行失败: ${message}`);
49
+ process.exitCode = 1;
50
+ }
51
+ }
52
+ //# sourceMappingURL=run.js.map
@@ -0,0 +1,13 @@
1
+ /**
2
+ * show 命令 — 展示 AI 分析结果
3
+ *
4
+ * 读取 result.json,以结构化格式输出到终端。
5
+ * 支持 --json 原始输出和 --brief 简略版。
6
+ */
7
+ import type { ShowCommandOptions } from '../types';
8
+ import type { StepRunResult } from '../workflow/types';
9
+ /**
10
+ * 执行 show 命令
11
+ */
12
+ export declare function runShow(username: string, options: ShowCommandOptions): Promise<StepRunResult>;
13
+ //# sourceMappingURL=show.d.ts.map