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,10 @@
1
+ /**
2
+ * HTTP Agent 配置
3
+ */
4
+ import type { Agent } from 'https';
5
+ /**
6
+ * 获取 HTTPS Agent
7
+ * 优先从配置文件读取,其次从环境变量读取
8
+ */
9
+ export declare function getHttpsAgent(): Agent | undefined;
10
+ //# sourceMappingURL=agent.d.ts.map
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ /**
3
+ * HTTP Agent 配置
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getHttpsAgent = getHttpsAgent;
7
+ const https_proxy_agent_1 = require("https-proxy-agent");
8
+ const config_1 = require("../../config");
9
+ /**
10
+ * 获取 HTTPS Agent
11
+ * 优先从配置文件读取,其次从环境变量读取
12
+ */
13
+ function getHttpsAgent() {
14
+ const proxyUrl = (0, config_1.getProxyUrl)();
15
+ return proxyUrl ? new https_proxy_agent_1.HttpsProxyAgent(proxyUrl) : undefined;
16
+ }
17
+ //# sourceMappingURL=agent.js.map
@@ -0,0 +1,10 @@
1
+ import type { IFetchStrategy, FetchResult, FetchOptions, FetchEvents } from './types';
2
+ export declare class SequentialStrategy implements IFetchStrategy {
3
+ fetch(urls: string[], options?: FetchOptions, events?: FetchEvents): AsyncGenerator<FetchResult>;
4
+ }
5
+ export declare class Fetcher {
6
+ private strategy;
7
+ constructor(strategy: IFetchStrategy);
8
+ fetch(urls: string[], options?: FetchOptions, events?: FetchEvents): AsyncGenerator<FetchResult>;
9
+ }
10
+ //# sourceMappingURL=fetcher.d.ts.map
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.Fetcher = exports.SequentialStrategy = void 0;
7
+ const axios_1 = __importDefault(require("axios"));
8
+ const agent_1 = require("./agent");
9
+ const config_1 = require("../../config");
10
+ /**
11
+ * 将响应数据转换为字符串
12
+ */
13
+ function responseToString(data) {
14
+ if (typeof data === 'string') {
15
+ return data;
16
+ }
17
+ try {
18
+ return JSON.stringify(data);
19
+ }
20
+ catch {
21
+ return String(data);
22
+ }
23
+ }
24
+ class SequentialStrategy {
25
+ async *fetch(urls, options, events) {
26
+ const total = urls.length;
27
+ const httpsAgent = (0, agent_1.getHttpsAgent)();
28
+ for (let i = 0; i < total; i++) {
29
+ const url = urls[i];
30
+ // 触发开始事件
31
+ events?.onStart?.(url, i, total);
32
+ try {
33
+ const response = await axios_1.default.get(url, {
34
+ timeout: options?.timeout ?? (0, config_1.getConfig)().fetch?.timeout ?? 30000,
35
+ ...(options?.headers && { headers: options.headers }),
36
+ ...(httpsAgent && { httpsAgent }),
37
+ proxy: false,
38
+ validateStatus: () => true,
39
+ });
40
+ const isSuccess = response.status >= 200 && response.status < 300;
41
+ const responseBody = responseToString(response.data);
42
+ const result = {
43
+ url,
44
+ content: isSuccess ? responseBody : null,
45
+ success: isSuccess,
46
+ statusCode: response.status,
47
+ ...(isSuccess ? {} : { errorBody: responseBody }),
48
+ };
49
+ // 触发成功/失败事件
50
+ if (result.success) {
51
+ events?.onSuccess?.(result, i, total);
52
+ }
53
+ else {
54
+ events?.onError?.(result, i, total);
55
+ }
56
+ yield result;
57
+ }
58
+ catch (error) {
59
+ const result = {
60
+ url,
61
+ content: null,
62
+ success: false,
63
+ error: error,
64
+ };
65
+ events?.onError?.(result, i, total);
66
+ yield result;
67
+ }
68
+ }
69
+ }
70
+ }
71
+ exports.SequentialStrategy = SequentialStrategy;
72
+ class Fetcher {
73
+ constructor(strategy) {
74
+ this.strategy = strategy;
75
+ }
76
+ async *fetch(urls, options, events) {
77
+ yield* this.strategy.fetch(urls, options, events);
78
+ }
79
+ }
80
+ exports.Fetcher = Fetcher;
81
+ //# sourceMappingURL=fetcher.js.map
@@ -0,0 +1,3 @@
1
+ export * from './types';
2
+ export * from './fetcher';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./types"), exports);
18
+ __exportStar(require("./fetcher"), exports);
19
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Fetcher 模块类型定义
3
+ */
4
+ /** 请求配置选项 */
5
+ export interface FetchOptions {
6
+ headers?: Record<string, string>;
7
+ timeout?: number;
8
+ }
9
+ /** 抓取结果 */
10
+ export interface FetchResult {
11
+ url: string;
12
+ content: string | null;
13
+ success: boolean;
14
+ error?: Error;
15
+ statusCode?: number;
16
+ /** 非 2xx 响应时的响应体 */
17
+ errorBody?: string;
18
+ }
19
+ /** 抓取事件回调 */
20
+ export interface FetchEvents {
21
+ onStart?: (url: string, index: number, total: number) => void;
22
+ onSuccess?: (result: FetchResult, index: number, total: number) => void;
23
+ onError?: (result: FetchResult, index: number, total: number) => void;
24
+ }
25
+ /** 抓取策略接口 */
26
+ export interface IFetchStrategy {
27
+ fetch(urls: string[], options?: FetchOptions, events?: FetchEvents): AsyncGenerator<FetchResult>;
28
+ }
29
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ /**
3
+ * Fetcher 模块类型定义
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1,15 @@
1
+ /**
2
+ * ANSI 终端颜色常量
3
+ *
4
+ * 供 logger 和 CLI 输出模块共享,避免重复定义。
5
+ */
6
+ export declare const COLORS: {
7
+ readonly reset: "\u001B[0m";
8
+ readonly bold: "\u001B[1m";
9
+ readonly red: "\u001B[31m";
10
+ readonly green: "\u001B[32m";
11
+ readonly yellow: "\u001B[33m";
12
+ readonly cyan: "\u001B[36m";
13
+ readonly gray: "\u001B[90m";
14
+ };
15
+ //# sourceMappingURL=colors.d.ts.map
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ /**
3
+ * ANSI 终端颜色常量
4
+ *
5
+ * 供 logger 和 CLI 输出模块共享,避免重复定义。
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.COLORS = void 0;
9
+ exports.COLORS = {
10
+ reset: '\x1b[0m',
11
+ bold: '\x1b[1m',
12
+ red: '\x1b[31m',
13
+ green: '\x1b[32m',
14
+ yellow: '\x1b[33m',
15
+ cyan: '\x1b[36m',
16
+ gray: '\x1b[90m',
17
+ };
18
+ //# sourceMappingURL=colors.js.map
@@ -0,0 +1,16 @@
1
+ /**
2
+ * 全局日志模块
3
+ *
4
+ * 提供四级日志(error > warn > info > debug),全局单例。
5
+ * 无外部依赖,所有层均可使用。
6
+ *
7
+ * 用法:
8
+ * import { logger } from '../../infra/logger';
9
+ * logger.info('消息');
10
+ *
11
+ * 入口处设置级别:
12
+ * logger.setLevel('debug');
13
+ */
14
+ export { logger } from './logger';
15
+ export type { LogLevel } from './logger';
16
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ /**
3
+ * 全局日志模块
4
+ *
5
+ * 提供四级日志(error > warn > info > debug),全局单例。
6
+ * 无外部依赖,所有层均可使用。
7
+ *
8
+ * 用法:
9
+ * import { logger } from '../../infra/logger';
10
+ * logger.info('消息');
11
+ *
12
+ * 入口处设置级别:
13
+ * logger.setLevel('debug');
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.logger = void 0;
17
+ var logger_1 = require("./logger");
18
+ Object.defineProperty(exports, "logger", { enumerable: true, get: function () { return logger_1.logger; } });
19
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,34 @@
1
+ /**
2
+ * 全局日志模块 — 基于级别过滤
3
+ *
4
+ * 级别优先级:error > warn > info > debug
5
+ * 设置为 'info' 时,'debug' 消息会被过滤。
6
+ *
7
+ * 使用 ANSI 转义码实现终端彩色输出,无外部依赖。
8
+ */
9
+ export type LogLevel = 'error' | 'warn' | 'info' | 'debug';
10
+ /** 未知总数的哨兵值 */
11
+ export declare const UNKNOWN_TOTAL = -1;
12
+ export declare const logger: {
13
+ /** 设置最低日志级别,低于此级别的消息将被过滤 */
14
+ setLevel(level: LogLevel): void;
15
+ /** 获取当前日志级别 */
16
+ getLevel(): LogLevel;
17
+ /** 调试信息 — 仅 verbose 模式可见 */
18
+ debug(msg: string): void;
19
+ /** 普通信息 — 正常输出 */
20
+ info(msg: string): void;
21
+ /** 警告信息 — 非致命异常 */
22
+ warn(msg: string): void;
23
+ /** 错误信息 — 操作失败 */
24
+ error(msg: string): void;
25
+ /** 章节标题 — 分隔不同操作阶段 */
26
+ section(title: string): void;
27
+ /** 成功信息 — 带绿色对勾前缀 */
28
+ success(msg: string): void;
29
+ /** 缩进详情 — 补充上一行输出 */
30
+ detail(msg: string): void;
31
+ /** 进度显示 — 页码或步骤进度 */
32
+ progress(current: number, total: number, label: string): void;
33
+ };
34
+ //# sourceMappingURL=logger.d.ts.map
@@ -0,0 +1,101 @@
1
+ "use strict";
2
+ /**
3
+ * 全局日志模块 — 基于级别过滤
4
+ *
5
+ * 级别优先级:error > warn > info > debug
6
+ * 设置为 'info' 时,'debug' 消息会被过滤。
7
+ *
8
+ * 使用 ANSI 转义码实现终端彩色输出,无外部依赖。
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.logger = exports.UNKNOWN_TOTAL = void 0;
12
+ const colors_1 = require("./colors");
13
+ /** 未知总数的哨兵值 */
14
+ exports.UNKNOWN_TOTAL = -1;
15
+ // -- 常量 --------------------------------------------------------------------
16
+ /** 级别优先级(数值越大越严重) */
17
+ const LEVEL_PRIORITY = {
18
+ debug: 0,
19
+ info: 1,
20
+ warn: 2,
21
+ error: 3,
22
+ };
23
+ /** 级别显示标签 */
24
+ const LEVEL_LABELS = {
25
+ debug: `${colors_1.COLORS.gray}[DEBUG]${colors_1.COLORS.reset}`,
26
+ info: '',
27
+ warn: `${colors_1.COLORS.yellow}[WARN]${colors_1.COLORS.reset}`,
28
+ error: `${colors_1.COLORS.red}[ERROR]${colors_1.COLORS.reset}`,
29
+ };
30
+ // -- 状态 --------------------------------------------------------------------
31
+ let currentLevel = 'info';
32
+ // -- 内部函数 ----------------------------------------------------------------
33
+ function shouldLog(level) {
34
+ return LEVEL_PRIORITY[level] >= LEVEL_PRIORITY[currentLevel];
35
+ }
36
+ function formatMessage(level, msg) {
37
+ const label = LEVEL_LABELS[level];
38
+ return label ? `${label} ${msg}` : msg;
39
+ }
40
+ // -- 公共 API ----------------------------------------------------------------
41
+ exports.logger = {
42
+ /** 设置最低日志级别,低于此级别的消息将被过滤 */
43
+ setLevel(level) {
44
+ currentLevel = level;
45
+ },
46
+ /** 获取当前日志级别 */
47
+ getLevel() {
48
+ return currentLevel;
49
+ },
50
+ /** 调试信息 — 仅 verbose 模式可见 */
51
+ debug(msg) {
52
+ if (shouldLog('debug')) {
53
+ console.log(formatMessage('debug', msg));
54
+ }
55
+ },
56
+ /** 普通信息 — 正常输出 */
57
+ info(msg) {
58
+ if (shouldLog('info')) {
59
+ console.log(formatMessage('info', msg));
60
+ }
61
+ },
62
+ /** 警告信息 — 非致命异常 */
63
+ warn(msg) {
64
+ if (shouldLog('warn')) {
65
+ console.warn(formatMessage('warn', msg));
66
+ }
67
+ },
68
+ /** 错误信息 — 操作失败 */
69
+ error(msg) {
70
+ if (shouldLog('error')) {
71
+ console.error(formatMessage('error', msg));
72
+ }
73
+ },
74
+ // -- CLI 格式化输出 --------------------------------------------------------
75
+ /** 章节标题 — 分隔不同操作阶段 */
76
+ section(title) {
77
+ if (shouldLog('info')) {
78
+ console.log(`\n${title}`);
79
+ }
80
+ },
81
+ /** 成功信息 — 带绿色对勾前缀 */
82
+ success(msg) {
83
+ if (shouldLog('info')) {
84
+ console.log(` ${colors_1.COLORS.green}Done:${colors_1.COLORS.reset} ${msg}`);
85
+ }
86
+ },
87
+ /** 缩进详情 — 补充上一行输出 */
88
+ detail(msg) {
89
+ if (shouldLog('info')) {
90
+ console.log(` ${msg}`);
91
+ }
92
+ },
93
+ /** 进度显示 — 页码或步骤进度 */
94
+ progress(current, total, label) {
95
+ if (shouldLog('info')) {
96
+ const display = total === exports.UNKNOWN_TOTAL ? `${current + 1}` : `${current + 1}/${total}`;
97
+ console.log(` ${label} (${display})...`);
98
+ }
99
+ },
100
+ };
101
+ //# sourceMappingURL=logger.js.map
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Storage 过期数据清理
3
+ *
4
+ * 根据配置清理过期的原始数据文件(raw.json / analyzed.json)。
5
+ * result.json 永远不会被清理。
6
+ *
7
+ * 清理策略:
8
+ * - keepRaw=true → 永不清理
9
+ * - keepRaw=false → 检查文件 mtime,超过 rawRetention 天则删除
10
+ */
11
+ import type { DataFileType } from './types';
12
+ /**
13
+ * 清理指定用户的过期数据文件
14
+ *
15
+ * 根据 config.data.keepRaw 和 config.data.rawRetention 决定清理行为:
16
+ * - keepRaw=true:不做任何操作
17
+ * - keepRaw=false:删除超过 rawRetention 天的 raw.json 和 analyzed.json
18
+ * - result.json 永远不受影响
19
+ *
20
+ * @param username - V2EX 用户名
21
+ * @returns 被删除的文件类型列表
22
+ */
23
+ export declare function cleanExpiredData(username: string): DataFileType[];
24
+ //# sourceMappingURL=cleaner.d.ts.map
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ /**
3
+ * Storage 过期数据清理
4
+ *
5
+ * 根据配置清理过期的原始数据文件(raw.json / analyzed.json)。
6
+ * result.json 永远不会被清理。
7
+ *
8
+ * 清理策略:
9
+ * - keepRaw=true → 永不清理
10
+ * - keepRaw=false → 检查文件 mtime,超过 rawRetention 天则删除
11
+ */
12
+ var __importDefault = (this && this.__importDefault) || function (mod) {
13
+ return (mod && mod.__esModule) ? mod : { "default": mod };
14
+ };
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.cleanExpiredData = cleanExpiredData;
17
+ const fs_1 = __importDefault(require("fs"));
18
+ const config_1 = require("../../config");
19
+ const paths_1 = require("./paths");
20
+ /** 需要清理的文件类型(result 永不清理) */
21
+ const CLEANABLE_TYPES = ['raw', 'analyzed'];
22
+ /**
23
+ * 判断文件是否已过期
24
+ * @param filePath - 文件路径
25
+ * @param retentionDays - 保留天数
26
+ * @returns true 表示文件已过期,应被清理
27
+ */
28
+ function isExpired(filePath, retentionDays) {
29
+ try {
30
+ const stat = fs_1.default.statSync(filePath);
31
+ const ageMs = Date.now() - stat.mtimeMs;
32
+ const retentionMs = retentionDays * 24 * 60 * 60 * 1000;
33
+ return ageMs > retentionMs;
34
+ }
35
+ catch {
36
+ return false;
37
+ }
38
+ }
39
+ /**
40
+ * 清理指定用户的过期数据文件
41
+ *
42
+ * 根据 config.data.keepRaw 和 config.data.rawRetention 决定清理行为:
43
+ * - keepRaw=true:不做任何操作
44
+ * - keepRaw=false:删除超过 rawRetention 天的 raw.json 和 analyzed.json
45
+ * - result.json 永远不受影响
46
+ *
47
+ * @param username - V2EX 用户名
48
+ * @returns 被删除的文件类型列表
49
+ */
50
+ function cleanExpiredData(username) {
51
+ const config = (0, config_1.getConfig)();
52
+ const keepRaw = config.data?.keepRaw ?? false;
53
+ const retentionDays = Math.max(0, config.data?.rawRetention ?? 1);
54
+ // 永久保留模式,不清理
55
+ if (keepRaw) {
56
+ return [];
57
+ }
58
+ const deleted = [];
59
+ for (const type of CLEANABLE_TYPES) {
60
+ const filePath = (0, paths_1.getDataFilePath)(username, type);
61
+ if (fs_1.default.existsSync(filePath) && isExpired(filePath, retentionDays)) {
62
+ try {
63
+ fs_1.default.unlinkSync(filePath);
64
+ deleted.push(type);
65
+ }
66
+ catch {
67
+ // 文件可能在检查后被外部删除,忽略 ENOENT
68
+ }
69
+ }
70
+ }
71
+ return deleted;
72
+ }
73
+ //# sourceMappingURL=cleaner.js.map
@@ -0,0 +1,7 @@
1
+ export type { DataFileType, WriteOptions } from './types';
2
+ export { DATA_FILE_NAMES } from './types';
3
+ export { getUserDataDir, getDataFilePath } from './paths';
4
+ export { readDataFile } from './reader';
5
+ export { writeDataFile } from './writer';
6
+ export { cleanExpiredData } from './cleaner';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.cleanExpiredData = exports.writeDataFile = exports.readDataFile = exports.getDataFilePath = exports.getUserDataDir = exports.DATA_FILE_NAMES = void 0;
4
+ var types_1 = require("./types");
5
+ Object.defineProperty(exports, "DATA_FILE_NAMES", { enumerable: true, get: function () { return types_1.DATA_FILE_NAMES; } });
6
+ var paths_1 = require("./paths");
7
+ Object.defineProperty(exports, "getUserDataDir", { enumerable: true, get: function () { return paths_1.getUserDataDir; } });
8
+ Object.defineProperty(exports, "getDataFilePath", { enumerable: true, get: function () { return paths_1.getDataFilePath; } });
9
+ var reader_1 = require("./reader");
10
+ Object.defineProperty(exports, "readDataFile", { enumerable: true, get: function () { return reader_1.readDataFile; } });
11
+ var writer_1 = require("./writer");
12
+ Object.defineProperty(exports, "writeDataFile", { enumerable: true, get: function () { return writer_1.writeDataFile; } });
13
+ var cleaner_1 = require("./cleaner");
14
+ Object.defineProperty(exports, "cleanExpiredData", { enumerable: true, get: function () { return cleaner_1.cleanExpiredData; } });
15
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Storage 路径解析
3
+ *
4
+ * 数据目录结构:
5
+ * ~/.v2er-insight/data/{username}/
6
+ * ├── raw.json # 抓取的原始数据
7
+ * ├── analyzed.json # Analyzer 输出
8
+ * └── result.json # AI 分析结果
9
+ */
10
+ import type { DataFileType } from './types';
11
+ /**
12
+ * 获取用户数据目录路径
13
+ * @param username - V2EX 用户名
14
+ * @returns 如 ~/.v2er-insight/data/livid/
15
+ * @throws Error 用户名包含非法字符时抛出
16
+ */
17
+ export declare function getUserDataDir(username: string): string;
18
+ /**
19
+ * 获取指定用户的数据文件路径
20
+ * @param username - V2EX 用户名
21
+ * @param type - 数据文件类型
22
+ * @returns 如 ~/.v2er-insight/data/livid/raw.json
23
+ * @throws Error 用户名包含非法字符时抛出
24
+ */
25
+ export declare function getDataFilePath(username: string, type: DataFileType): string;
26
+ //# sourceMappingURL=paths.d.ts.map
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ /**
3
+ * Storage 路径解析
4
+ *
5
+ * 数据目录结构:
6
+ * ~/.v2er-insight/data/{username}/
7
+ * ├── raw.json # 抓取的原始数据
8
+ * ├── analyzed.json # Analyzer 输出
9
+ * └── result.json # AI 分析结果
10
+ */
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.getUserDataDir = getUserDataDir;
16
+ exports.getDataFilePath = getDataFilePath;
17
+ const path_1 = __importDefault(require("path"));
18
+ const path_2 = require("../../config/path");
19
+ const types_1 = require("./types");
20
+ /** 数据根目录名 */
21
+ const DATA_DIR = 'data';
22
+ /** 合法用户名格式:仅允许字母、数字、下划线、连字符 */
23
+ const USERNAME_PATTERN = /^[a-zA-Z0-9_-]+$/;
24
+ /**
25
+ * 校验用户名是否合法
26
+ * @throws Error 用户名包含非法字符时抛出
27
+ */
28
+ function validateUsername(username) {
29
+ if (!USERNAME_PATTERN.test(username)) {
30
+ throw new Error(`用户名 "${username}" 包含非法字符,仅允许字母、数字、下划线和连字符`);
31
+ }
32
+ }
33
+ /**
34
+ * 获取用户数据目录路径
35
+ * @param username - V2EX 用户名
36
+ * @returns 如 ~/.v2er-insight/data/livid/
37
+ * @throws Error 用户名包含非法字符时抛出
38
+ */
39
+ function getUserDataDir(username) {
40
+ validateUsername(username);
41
+ return path_1.default.join((0, path_2.getConfigDir)(), DATA_DIR, username);
42
+ }
43
+ /**
44
+ * 获取指定用户的数据文件路径
45
+ * @param username - V2EX 用户名
46
+ * @param type - 数据文件类型
47
+ * @returns 如 ~/.v2er-insight/data/livid/raw.json
48
+ * @throws Error 用户名包含非法字符时抛出
49
+ */
50
+ function getDataFilePath(username, type) {
51
+ return path_1.default.join(getUserDataDir(username), types_1.DATA_FILE_NAMES[type]);
52
+ }
53
+ //# sourceMappingURL=paths.js.map
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Storage 文件读取
3
+ *
4
+ * 从用户数据目录读取 JSON 文件并解析为对象。
5
+ * 文件不存在或解析失败时返回 null,不抛出异常。
6
+ */
7
+ import type { DataFileType } from './types';
8
+ /**
9
+ * 读取指定用户的数据文件
10
+ * @param username - V2EX 用户名
11
+ * @param type - 数据文件类型(raw / analyzed / result)
12
+ * @returns 解析后的对象,文件不存在或解析失败返回 null
13
+ */
14
+ export declare function readDataFile<T>(username: string, type: DataFileType): T | null;
15
+ //# sourceMappingURL=reader.d.ts.map
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ /**
3
+ * Storage 文件读取
4
+ *
5
+ * 从用户数据目录读取 JSON 文件并解析为对象。
6
+ * 文件不存在或解析失败时返回 null,不抛出异常。
7
+ */
8
+ var __importDefault = (this && this.__importDefault) || function (mod) {
9
+ return (mod && mod.__esModule) ? mod : { "default": mod };
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.readDataFile = readDataFile;
13
+ const fs_1 = __importDefault(require("fs"));
14
+ const paths_1 = require("./paths");
15
+ /**
16
+ * 读取指定用户的数据文件
17
+ * @param username - V2EX 用户名
18
+ * @param type - 数据文件类型(raw / analyzed / result)
19
+ * @returns 解析后的对象,文件不存在或解析失败返回 null
20
+ */
21
+ function readDataFile(username, type) {
22
+ const filePath = (0, paths_1.getDataFilePath)(username, type);
23
+ if (!fs_1.default.existsSync(filePath)) {
24
+ return null;
25
+ }
26
+ try {
27
+ const content = fs_1.default.readFileSync(filePath, 'utf-8');
28
+ return JSON.parse(content);
29
+ }
30
+ catch {
31
+ return null;
32
+ }
33
+ }
34
+ //# sourceMappingURL=reader.js.map
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Storage 模块类型定义
3
+ *
4
+ * 定义数据文件类型和读写选项。
5
+ */
6
+ /** 数据文件类型 */
7
+ export type DataFileType = 'raw' | 'analyzed' | 'result';
8
+ /**
9
+ * 数据文件类型到文件名的映射
10
+ * 供 paths 模块使用
11
+ */
12
+ export declare const DATA_FILE_NAMES: Record<DataFileType, string>;
13
+ /** 写入选项 */
14
+ export interface WriteOptions {
15
+ /**
16
+ * 是否格式化 JSON 输出(美化缩进)
17
+ * 默认 true
18
+ */
19
+ pretty?: boolean;
20
+ }
21
+ //# sourceMappingURL=types.d.ts.map