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
package/README.md ADDED
@@ -0,0 +1,215 @@
1
+ # V2ER Insight
2
+
3
+ V2EX 用户画像深度分析工具。通过自动化抓取数据、统计解析及 AI 语言模型建模,构建多维度的用户行为与心理画像。
4
+
5
+ ## 核心流程 (Pipe Flow)
6
+
7
+ 本项目采用管道化设计,目前通过以下步骤逐步生成深度报告:
8
+ **Fetch** (抓取) → **Analyze** (统计) → **AI** (建模) → **Show** (展示)
9
+
10
+ ## 快速开始
11
+
12
+ 按顺序完成以下步骤:
13
+
14
+ 1. 安装 CLI 包
15
+
16
+ ```bash
17
+ npm install -g v2er-insight
18
+ # 或
19
+ pnpm add -g v2er-insight
20
+ ```
21
+
22
+ 2. 配置 Gemini API Key
23
+
24
+ ```bash
25
+ v2er config set ai.apiKey <your_gemini_api_key>
26
+ ```
27
+
28
+ 3. 设置代理(可选,建议在受限网络下启用)
29
+
30
+ ```bash
31
+ v2er config proxy http://127.0.0.1:7890
32
+ ```
33
+
34
+ 4. 执行一键分析
35
+
36
+ ```bash
37
+ v2er <username>
38
+ ```
39
+
40
+ ## CLI 命令
41
+
42
+ ### 一键分析(推荐)
43
+
44
+ 从零到报告,一条命令完成全流程:
45
+
46
+ ```bash
47
+ v2er <username>
48
+ ```
49
+
50
+ | 选项 | 说明 |
51
+ | -------------------------- | ------------------------------------------------------------------ |
52
+ | `--force` | 强制重新抓取(忽略本地缓存) |
53
+ | `--model [name]` | 指定 AI 模型(默认: `gemini-3-pro-preview`) |
54
+ | `--thinking-level [level]` | 指定思考等级(默认: `high`,可选 `minimal`/`low`/`medium`/`high`) |
55
+ | `-v, --verbose` | 显示调试输出 |
56
+
57
+ 智能跳过:管道执行时,若 `raw.json` 已存在则跳过抓取步骤(analyze 和 ai 每次重新执行)。`--force` 忽略缓存从头开始。
58
+
59
+ ---
60
+
61
+ ### 分步执行
62
+
63
+ ### 1. 数据抓取 (Fetch)
64
+
65
+ 抓取指定用户的个人资料、帖子内容及所有回复。
66
+
67
+ ```bash
68
+ v2er fetch <username> [选项]
69
+ ```
70
+
71
+ | 选项 | 说明 |
72
+ | ----------- | ------------------------------------- |
73
+ | `--topics` | 仅抓取话题 |
74
+ | `--replies` | 仅抓取回复 |
75
+ | `--force` | 强制重新抓取(忽略本地 `.json` 缓存) |
76
+
77
+ ### 2. 统计分析 (Analyze)
78
+
79
+ 对抓取的原始数据进行加工,计算活跃周期、发帖频率、节点分布等指标。
80
+
81
+ - 分析结果文档定义:[docs/analyzer-output/output-schema.md](docs/analyzer-output/output-schema.md)
82
+
83
+ ```bash
84
+ v2er analyze <username>
85
+ ```
86
+
87
+ ### 3. AI 画像建模 (AI)
88
+
89
+ 目前仅支持 **Google Gemini** 服务。
90
+ 调用 AI 模型,基于统计结果进行多维度心理、行为及社交建模,生成分析报告。
91
+
92
+ - 核心提示词所在位置:[docs/prompt.md](docs/prompt.md)
93
+ - 分析维度详细说明:[docs/ai-result/result-schema.md](docs/ai-result/result-schema.md)
94
+
95
+ ```bash
96
+ v2er ai <username> [选项]
97
+ ```
98
+
99
+ | 选项 | 说明 |
100
+ | -------------------------- | ------------------------------------------------------------------ |
101
+ | `--model [name]` | 指定 Gemini 模型(默认: `gemini-3-pro-preview`) |
102
+ | `--thinking-level [level]` | 指定思考等级(默认: `high`,可选 `minimal`/`low`/`medium`/`high`) |
103
+
104
+ ### 4. 报告展示 (Show)
105
+
106
+ 以结构化的格式展示最终的分析报告,包含 OCEAN 五维性格雷达图(字符模拟)。
107
+
108
+ ```bash
109
+ v2er show <username> [选项]
110
+ ```
111
+
112
+ | 选项 | 说明 |
113
+ | --------- | ------------------------------ |
114
+ | `--brief` | 简略版输出(仅摘要及核心指标) |
115
+ | `--json` | 输出 AI 返回的原始 JSON 数据 |
116
+
117
+ ### 5. 配置管理 (Config)
118
+
119
+ - **group**: 配置分组名,可选 `ai`、`fetch`、`analyzer`、`data`、`log`、`proxy`
120
+ - **path**: 点分路径,如 `ai.model`、`log.level`、`data.keepRaw`
121
+ - **value**: 配置值,自动进行类型转换(字符串/数字/布尔)和枚举校验
122
+
123
+ ```bash
124
+ # 查看
125
+ v2er config show # 查看全部配置(apiKey 自动掩码)
126
+ v2er config show ai # 查看 ai 分组
127
+
128
+ # 设置
129
+ v2er config set ai.model gemini-2.5-flash # 切换模型
130
+ v2er config set ai.thinkingLevel medium # 设置思考等级
131
+ v2er config set log.level debug # 开启调试日志
132
+ v2er config set data.keepRaw true # 保留原始数据
133
+ v2er config set ai.timeout 120000 # AI 请求超时 120s
134
+
135
+ # 重置
136
+ v2er config reset # 重置全部为默认值
137
+ v2er config reset ai # 仅重置 ai 分组
138
+
139
+ # 代理快捷方式
140
+ v2er config proxy http://127.0.0.1:7890 # 设置代理
141
+ v2er config proxy # 查看代理
142
+ v2er config proxy --clear # 清除代理
143
+ ```
144
+
145
+ ---
146
+
147
+ ## 详细配置说明
148
+
149
+ 配置文件位于 `~/.v2er-insight/config.json`,可通过 `v2er config set` 或手动编辑。
150
+
151
+ ### 1. API Key 解析顺序
152
+
153
+ AI 模块通过以下优先级依次尝试读取 Gemini API Key:
154
+
155
+ - `~/.v2er-insight/config.json` 中的 `ai.apiKey` 字段
156
+ - 环境变量 `GOOGLE_API_KEY`
157
+ - 环境变量 `GEMINI_API_KEY`
158
+
159
+ ### 2. 代理读取逻辑 (Proxy)
160
+
161
+ 程序按以下优先级确定请求使用的代理(Fetcher 和 AI 模块共用同一优先级):
162
+
163
+ 1. 配置文件 (`~/.v2er-insight/config.json`) 中的 `proxy` 字段
164
+ 2. 系统环境变量 `HTTPS_PROXY`
165
+ 3. 系统环境变量 `HTTP_PROXY`
166
+
167
+ 若以上均未配置,则尝试直接连接。
168
+
169
+ ### 3. 参数优先级与默认值来源
170
+
171
+ 配置解析关系:显式参数 > `~/.v2er-insight/config.json` > [src/config/defaults.ts](src/config/defaults.ts)(特例:`proxy` / `apiKey` 还会读取环境变量)。
172
+
173
+ ### 4. 技术实现细节
174
+
175
+ - 日志系统:采用级别过滤(Error/Warn/Info/Debug),支持带进度的章节式输出。
176
+ - 代理驱动(双通道):
177
+ - **Fetcher**(V2EX 数据抓取):`https-proxy-agent` + Axios `httpsAgent`
178
+ - **AI**(Gemini API 调用):`undici` `ProxyAgent` + `setGlobalDispatcher`(原生 `fetch()` 代理)
179
+ - 数据本地化:数据存储于 `~/.v2er-insight/data/{username}/` 下。
180
+ - 环境要求:Node.js >= 20.18.1(undici 7.x 要求)。
181
+
182
+ ---
183
+
184
+ ## 开发
185
+
186
+ ### 安装与构建
187
+
188
+ ```bash
189
+ pnpm install
190
+ pnpm run build
191
+ ```
192
+
193
+ ### 开发模式(直接运行源码)
194
+
195
+ ```bash
196
+ npx ts-node -r tsconfig-paths/register src/cli/index.ts <command> <username>
197
+ ```
198
+
199
+ ### 质量检查
200
+
201
+ ```bash
202
+ pnpm run check:types # TypeScript 类型检查
203
+ pnpm run lint # ESLint 代码规范
204
+ pnpm run test # Vitest 单元测试(单次)
205
+ pnpm run dev # Vitest 监听模式
206
+ pnpm run ci # 完整 CI(类型 + lint + 格式 + 测试)
207
+ ```
208
+
209
+ ---
210
+
211
+ ### 安全与隐私
212
+
213
+ - 文件权限:在 Linux/Mac 系统上,程序创建的配置文件权限为 `0600`(仅当前用户读写)。
214
+ - 隐私保护:建议避免在配置文件中直接存储包含明文凭据的代理 URL。
215
+ - Windows 用户建议:手动检查 `~/.v2er-insight/config.json` 的访问控制列表 (ACL),确保其安全性。
@@ -0,0 +1,13 @@
1
+ /**
2
+ * ai 命令 — 调用 AI 分析用户数据
3
+ *
4
+ * 读取 analyzed.json,构建多轮对话消息序列,
5
+ * 通过 GeminiProvider 发送至 AI,解析响应后写入 result.json。
6
+ */
7
+ import type { AiCommandOptions } from '../types';
8
+ import type { StepRunResult } from '../workflow/types';
9
+ /**
10
+ * 执行 ai 命令
11
+ */
12
+ export declare function runAi(username: string, options: AiCommandOptions): Promise<StepRunResult>;
13
+ //# sourceMappingURL=ai.d.ts.map
@@ -0,0 +1,153 @@
1
+ "use strict";
2
+ /**
3
+ * ai 命令 — 调用 AI 分析用户数据
4
+ *
5
+ * 读取 analyzed.json,构建多轮对话消息序列,
6
+ * 通过 GeminiProvider 发送至 AI,解析响应后写入 result.json。
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.runAi = runAi;
10
+ const ai_1 = require("../../core/ai");
11
+ const config_1 = require("../../config");
12
+ const storage_1 = require("../../infra/storage");
13
+ const logger_1 = require("../../infra/logger");
14
+ const recovery_1 = require("../workflow/recovery");
15
+ const error_1 = require("../utils/error");
16
+ /**
17
+ * 执行 ai 命令
18
+ */
19
+ async function runAi(username, options) {
20
+ // 读取分析数据
21
+ const analyzed = (0, storage_1.readDataFile)(username, 'analyzed');
22
+ if (!analyzed) {
23
+ logger_1.logger.error(`未找到 ${username} 的分析数据`);
24
+ logger_1.logger.info('请先运行: v2er analyze <username>');
25
+ return {
26
+ step: 'ai',
27
+ status: 'failed',
28
+ reasonCode: 'AI_INPUT_MISSING',
29
+ message: '缺少 analyzed.json,无法执行 AI 分析',
30
+ recoverable: true,
31
+ recoverActions: (0, recovery_1.getRecoveryActions)('AI_INPUT_MISSING', { username }),
32
+ };
33
+ }
34
+ // 解析 API Key
35
+ const apiKey = (0, ai_1.resolveApiKey)();
36
+ if (!apiKey) {
37
+ logger_1.logger.error('未找到 API Key');
38
+ logger_1.logger.info('请通过以下方式之一配置:');
39
+ logger_1.logger.detail('1. 环境变量: GOOGLE_API_KEY 或 GEMINI_API_KEY');
40
+ logger_1.logger.detail('2. 配置文件: v2er config ai.apiKey <key>');
41
+ return {
42
+ step: 'ai',
43
+ status: 'failed',
44
+ reasonCode: 'AI_API_KEY_MISSING',
45
+ message: '缺少 API Key,无法发起 AI 请求',
46
+ recoverable: true,
47
+ recoverActions: (0, recovery_1.getRecoveryActions)('AI_API_KEY_MISSING', { username }),
48
+ };
49
+ }
50
+ const config = (0, config_1.getConfig)();
51
+ // Commander 将 --model [name] 无值时解析为 true;
52
+ // 字符串时直接使用,否则回退到配置/默认值(后续支持交互选择时替换此逻辑)
53
+ const model = typeof options.model === 'string'
54
+ ? options.model
55
+ : (config.ai?.model ?? 'gemini-3-pro-preview');
56
+ // 同上:--thinking-level [level] 无值时为 true,字符串时直接使用
57
+ const rawThinkingLevel = typeof options.thinkingLevel === 'string' ? options.thinkingLevel : config.ai?.thinkingLevel;
58
+ // 校验 thinkingLevel 合法性
59
+ if (rawThinkingLevel && !config_1.THINKING_LEVELS.includes(rawThinkingLevel)) {
60
+ logger_1.logger.error(`无效的思考等级: "${rawThinkingLevel}"`);
61
+ logger_1.logger.info(`可选值: ${config_1.THINKING_LEVELS.join(' | ')}`);
62
+ return {
63
+ step: 'ai',
64
+ status: 'failed',
65
+ reasonCode: 'AI_INVALID_THINKING_LEVEL',
66
+ message: `无效的 thinkingLevel: ${rawThinkingLevel}`,
67
+ recoverable: false,
68
+ recoverActions: (0, recovery_1.getRecoveryActions)('AI_INVALID_THINKING_LEVEL', { username }),
69
+ };
70
+ }
71
+ // 注意:先完成上方白名单校验,再使用该类型断言。
72
+ // TODO: 使用 type guard 替换断言式收窄 [2026-02-18]
73
+ const thinkingLevel = rawThinkingLevel;
74
+ logger_1.logger.info(`\nAI 分析: ${username} (模型: ${model})`);
75
+ if (thinkingLevel) {
76
+ logger_1.logger.detail(`思考等级: ${thinkingLevel}`);
77
+ }
78
+ // 构建消息序列
79
+ const sequence = (0, ai_1.buildMessageSequence)(analyzed);
80
+ const totalMessages = sequence.messages.length + 1; // +1 for finalPrompt
81
+ // 创建 Provider
82
+ const provider = new ai_1.GeminiProvider(apiKey, model);
83
+ const retryOptions = {
84
+ maxRetries: config.ai?.maxRetries,
85
+ baseDelay: config.ai?.baseDelay,
86
+ maxDelay: config.ai?.maxDelay,
87
+ };
88
+ try {
89
+ // 初始化会话
90
+ await provider.createSession(sequence.systemPrompt, {
91
+ thinkingLevel,
92
+ timeout: config.ai?.timeout,
93
+ });
94
+ // 逐条发送数据消息
95
+ logger_1.logger.section('发送数据至 AI...');
96
+ let messageIndex = 0;
97
+ for (const message of sequence.messages) {
98
+ logger_1.logger.progress(messageIndex, totalMessages, '发送消息');
99
+ await (0, ai_1.withRetry)(() => provider.sendMessage(message), retryOptions);
100
+ messageIndex++;
101
+ }
102
+ // 发送最终分析请求
103
+ logger_1.logger.progress(sequence.messages.length, totalMessages, '请求分析');
104
+ const rawResponse = await (0, ai_1.withRetry)(() => provider.sendMessage(sequence.finalPrompt), retryOptions);
105
+ // 解析响应
106
+ const { data: result, warnings } = (0, ai_1.parseResponse)(rawResponse);
107
+ if (warnings.length > 0) {
108
+ logger_1.logger.section('AI 响应警告:');
109
+ for (const warning of warnings) {
110
+ logger_1.logger.warn(` ${warning}`);
111
+ }
112
+ }
113
+ // 持久化结果
114
+ (0, storage_1.writeDataFile)(username, 'result', result);
115
+ // 清理过期中间数据
116
+ const cleaned = (0, storage_1.cleanExpiredData)(username);
117
+ if (!options.pipeline) {
118
+ logger_1.logger.success('分析结果已保存');
119
+ if (cleaned.length > 0) {
120
+ logger_1.logger.detail(`已清理中间数据: ${cleaned.join(', ')}`);
121
+ }
122
+ }
123
+ return {
124
+ step: 'ai',
125
+ status: 'success',
126
+ message: 'AI 分析完成',
127
+ meta: {
128
+ model,
129
+ warningCount: warnings.length,
130
+ cleanedFiles: cleaned,
131
+ },
132
+ };
133
+ }
134
+ catch (error) {
135
+ const { message, raw } = (0, error_1.extractErrorDetails)(error);
136
+ if (!options.pipeline) {
137
+ logger_1.logger.error(`AI 分析失败: ${message}`);
138
+ logger_1.logger.debug(raw);
139
+ }
140
+ return {
141
+ step: 'ai',
142
+ status: 'failed',
143
+ reasonCode: 'AI_PROVIDER_FAILED',
144
+ message: `AI 分析失败: ${message}`,
145
+ recoverable: true,
146
+ recoverActions: (0, recovery_1.getRecoveryActions)('AI_PROVIDER_FAILED', { username }),
147
+ meta: {
148
+ rawError: raw,
149
+ },
150
+ };
151
+ }
152
+ }
153
+ //# sourceMappingURL=ai.js.map
@@ -0,0 +1,13 @@
1
+ /**
2
+ * analyze 命令 — 分析已抓取的用户数据
3
+ *
4
+ * 读取 raw.json,执行 Analyzer,输出 analyzed.json。
5
+ */
6
+ import type { StepRunResult } from '../workflow/types';
7
+ /**
8
+ * 执行 analyze 命令
9
+ */
10
+ export declare function runAnalyze(username: string, options?: {
11
+ pipeline?: boolean;
12
+ }): Promise<StepRunResult>;
13
+ //# sourceMappingURL=analyze.d.ts.map
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ /**
3
+ * analyze 命令 — 分析已抓取的用户数据
4
+ *
5
+ * 读取 raw.json,执行 Analyzer,输出 analyzed.json。
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.runAnalyze = runAnalyze;
9
+ const analyzer_1 = require("../../core/analyzer");
10
+ const storage_1 = require("../../infra/storage");
11
+ const logger_1 = require("../../infra/logger");
12
+ const recovery_1 = require("../workflow/recovery");
13
+ /**
14
+ * 打印分析统计摘要
15
+ */
16
+ function printStats(output) {
17
+ const { userOverview, summary } = output;
18
+ logger_1.logger.section('=== 分析摘要 ===');
19
+ logger_1.logger.detail(`注册时间: ${userOverview.joinDate}`);
20
+ logger_1.logger.detail(`最后活跃: ${userOverview.lastActiveTime}`);
21
+ if (userOverview.totalTopics !== null) {
22
+ logger_1.logger.detail(`帖子总数: ${userOverview.totalTopics}`);
23
+ }
24
+ logger_1.logger.detail(`回复总数: ${userOverview.totalReplies}`);
25
+ if (userOverview.topicReplyRatio !== null) {
26
+ logger_1.logger.detail(`帖/回比: ${userOverview.topicReplyRatio.toFixed(2)}`);
27
+ }
28
+ logger_1.logger.detail(`活跃期数: ${summary.totalPeriods}`);
29
+ logger_1.logger.detail(`内容分片: ${output.contents.length}`);
30
+ }
31
+ /**
32
+ * 执行 analyze 命令
33
+ */
34
+ async function runAnalyze(username, options = {}) {
35
+ // 读取原始数据
36
+ const rawData = (0, storage_1.readDataFile)(username, 'raw');
37
+ if (!rawData) {
38
+ logger_1.logger.error(`未找到 ${username} 的抓取数据`);
39
+ logger_1.logger.info('请先运行: v2er fetch <username>');
40
+ return {
41
+ step: 'analyze',
42
+ status: 'failed',
43
+ reasonCode: 'ANALYZE_INPUT_MISSING',
44
+ message: '缺少 raw.json,无法执行分析',
45
+ recoverable: true,
46
+ recoverActions: (0, recovery_1.getRecoveryActions)('ANALYZE_INPUT_MISSING', { username }),
47
+ };
48
+ }
49
+ logger_1.logger.info(`\n分析用户数据: ${username}`);
50
+ try {
51
+ const output = (0, analyzer_1.buildAnalyzerOutput)(rawData);
52
+ (0, storage_1.writeDataFile)(username, 'analyzed', output);
53
+ if (!options.pipeline) {
54
+ logger_1.logger.success('分析结果已保存');
55
+ printStats(output);
56
+ }
57
+ return {
58
+ step: 'analyze',
59
+ status: 'success',
60
+ message: '分析完成',
61
+ meta: {
62
+ totalPeriods: output.summary.totalPeriods,
63
+ contentChunks: output.contents.length,
64
+ },
65
+ };
66
+ }
67
+ catch (error) {
68
+ const message = error instanceof Error ? error.message : String(error);
69
+ logger_1.logger.error(`分析失败: ${message}`);
70
+ return {
71
+ step: 'analyze',
72
+ status: 'failed',
73
+ reasonCode: 'ANALYZE_FAILED',
74
+ message: `分析失败: ${message}`,
75
+ recoverable: true,
76
+ recoverActions: (0, recovery_1.getRecoveryActions)('ANALYZE_FAILED', { username }),
77
+ };
78
+ }
79
+ }
80
+ //# sourceMappingURL=analyze.js.map
@@ -0,0 +1,43 @@
1
+ /**
2
+ * config 命令
3
+ *
4
+ * 提供配置的查看、设置和代理管理功能。
5
+ * 所有配置持久化到 ~/.v2er-insight/config.json。
6
+ */
7
+ interface ConfigProxyOptions {
8
+ clear?: boolean;
9
+ }
10
+ /**
11
+ * 代理配置命令
12
+ */
13
+ export declare function configProxy(url?: string, options?: ConfigProxyOptions): void;
14
+ /**
15
+ * 查看配置
16
+ *
17
+ * 无参数显示完整配置,传 group 显示指定分组。
18
+ */
19
+ export declare function configShow(group?: string): void;
20
+ /**
21
+ * 设置配置项
22
+ *
23
+ * 支持点分路径,自动类型转换和枚举校验。
24
+ *
25
+ * 示例:
26
+ * configSet('ai.model', 'gemini-2.5-flash')
27
+ * configSet('data.keepRaw', 'true')
28
+ * configSet('log.level', 'debug')
29
+ */
30
+ export declare function configSet(dotPath: string, rawValue: string): void;
31
+ /**
32
+ * 重置配置
33
+ *
34
+ * 无参数清空整个配置文件(恢复全部默认值)。
35
+ * 传 group 只清除指定分组。
36
+ *
37
+ * 示例:
38
+ * configReset() → 清空所有用户配置
39
+ * configReset('ai') → 仅清除 ai 分组
40
+ */
41
+ export declare function configReset(group?: string): void;
42
+ export {};
43
+ //# sourceMappingURL=config.d.ts.map