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.
- package/README.md +215 -0
- package/dist/cli/commands/ai.d.ts +13 -0
- package/dist/cli/commands/ai.js +153 -0
- package/dist/cli/commands/analyze.d.ts +13 -0
- package/dist/cli/commands/analyze.js +80 -0
- package/dist/cli/commands/config.d.ts +43 -0
- package/dist/cli/commands/config.js +267 -0
- package/dist/cli/commands/fetch.d.ts +13 -0
- package/dist/cli/commands/fetch.js +150 -0
- package/dist/cli/commands/index.d.ts +10 -0
- package/dist/cli/commands/index.js +22 -0
- package/dist/cli/commands/run.d.ts +23 -0
- package/dist/cli/commands/run.js +52 -0
- package/dist/cli/commands/show.d.ts +13 -0
- package/dist/cli/commands/show.js +154 -0
- package/dist/cli/index.d.ts +6 -0
- package/dist/cli/index.js +107 -0
- package/dist/cli/types.d.ts +58 -0
- package/dist/cli/types.js +6 -0
- package/dist/cli/utils/error.d.ts +6 -0
- package/dist/cli/utils/error.js +18 -0
- package/dist/cli/utils.d.ts +20 -0
- package/dist/cli/utils.js +48 -0
- package/dist/cli/workflow/orchestrator.d.ts +15 -0
- package/dist/cli/workflow/orchestrator.js +144 -0
- package/dist/cli/workflow/recovery.d.ts +10 -0
- package/dist/cli/workflow/recovery.js +134 -0
- package/dist/cli/workflow/state.d.ts +19 -0
- package/dist/cli/workflow/state.js +45 -0
- package/dist/cli/workflow/types.d.ts +60 -0
- package/dist/cli/workflow/types.js +3 -0
- package/dist/config/defaults.d.ts +48 -0
- package/dist/config/defaults.js +42 -0
- package/dist/config/index.d.ts +16 -0
- package/dist/config/index.js +21 -0
- package/dist/config/path.d.ts +11 -0
- package/dist/config/path.js +28 -0
- package/dist/config/proxy.d.ts +16 -0
- package/dist/config/proxy.js +39 -0
- package/dist/config/storage.d.ts +23 -0
- package/dist/config/storage.js +85 -0
- package/dist/config/types/ai.d.ts +31 -0
- package/dist/config/types/ai.js +13 -0
- package/dist/config/types/analyzer.d.ts +15 -0
- package/dist/config/types/analyzer.js +6 -0
- package/dist/config/types/data.d.ts +20 -0
- package/dist/config/types/data.js +6 -0
- package/dist/config/types/fetch.d.ts +9 -0
- package/dist/config/types/fetch.js +6 -0
- package/dist/config/types/index.d.ts +32 -0
- package/dist/config/types/index.js +11 -0
- package/dist/config/types/log.d.ts +11 -0
- package/dist/config/types/log.js +6 -0
- package/dist/core/ai/index.d.ts +11 -0
- package/dist/core/ai/index.js +18 -0
- package/dist/core/ai/parser/index.d.ts +12 -0
- package/dist/core/ai/parser/index.js +44 -0
- package/dist/core/ai/parser/validator.d.ts +18 -0
- package/dist/core/ai/parser/validator.js +179 -0
- package/dist/core/ai/prompt/index.d.ts +20 -0
- package/dist/core/ai/prompt/index.js +75 -0
- package/dist/core/ai/prompt/system-prompt.md +210 -0
- package/dist/core/ai/providers/gemini.d.ts +25 -0
- package/dist/core/ai/providers/gemini.js +74 -0
- package/dist/core/ai/providers/index.d.ts +6 -0
- package/dist/core/ai/providers/index.js +9 -0
- package/dist/core/ai/types/index.d.ts +7 -0
- package/dist/core/ai/types/index.js +6 -0
- package/dist/core/ai/types/options.d.ts +14 -0
- package/dist/core/ai/types/options.js +6 -0
- package/dist/core/ai/types/provider.d.ts +19 -0
- package/dist/core/ai/types/provider.js +6 -0
- package/dist/core/ai/types/result.d.ts +64 -0
- package/dist/core/ai/types/result.js +6 -0
- package/dist/core/ai/utils/api-key.d.ts +15 -0
- package/dist/core/ai/utils/api-key.js +36 -0
- package/dist/core/ai/utils/index.d.ts +6 -0
- package/dist/core/ai/utils/index.js +11 -0
- package/dist/core/ai/utils/retry.d.ts +15 -0
- package/dist/core/ai/utils/retry.js +37 -0
- package/dist/core/analyzer/builder.d.ts +23 -0
- package/dist/core/analyzer/builder.js +113 -0
- package/dist/core/analyzer/content/chunker.d.ts +18 -0
- package/dist/core/analyzer/content/chunker.js +74 -0
- package/dist/core/analyzer/content/index.d.ts +7 -0
- package/dist/core/analyzer/content/index.js +13 -0
- package/dist/core/analyzer/content/transformer.d.ts +19 -0
- package/dist/core/analyzer/content/transformer.js +33 -0
- package/dist/core/analyzer/index.d.ts +17 -0
- package/dist/core/analyzer/index.js +21 -0
- package/dist/core/analyzer/periods/detector.d.ts +17 -0
- package/dist/core/analyzer/periods/detector.js +36 -0
- package/dist/core/analyzer/periods/index.d.ts +6 -0
- package/dist/core/analyzer/periods/index.js +11 -0
- package/dist/core/analyzer/periods/splitter.d.ts +11 -0
- package/dist/core/analyzer/periods/splitter.js +35 -0
- package/dist/core/analyzer/stats/index.d.ts +7 -0
- package/dist/core/analyzer/stats/index.js +13 -0
- package/dist/core/analyzer/stats/reply-stats.d.ts +15 -0
- package/dist/core/analyzer/stats/reply-stats.js +45 -0
- package/dist/core/analyzer/stats/topic-stats.d.ts +16 -0
- package/dist/core/analyzer/stats/topic-stats.js +51 -0
- package/dist/core/analyzer/stats/user-overview.d.ts +9 -0
- package/dist/core/analyzer/stats/user-overview.js +52 -0
- package/dist/core/analyzer/types/index.d.ts +7 -0
- package/dist/core/analyzer/types/index.js +6 -0
- package/dist/core/analyzer/types/input.d.ts +13 -0
- package/dist/core/analyzer/types/input.js +6 -0
- package/dist/core/analyzer/types/internal.d.ts +28 -0
- package/dist/core/analyzer/types/internal.js +6 -0
- package/dist/core/analyzer/types/output.d.ts +68 -0
- package/dist/core/analyzer/types/output.js +6 -0
- package/dist/core/analyzer/utils/date-parser.d.ts +41 -0
- package/dist/core/analyzer/utils/date-parser.js +118 -0
- package/dist/core/analyzer/utils/index.d.ts +6 -0
- package/dist/core/analyzer/utils/index.js +18 -0
- package/dist/core/analyzer/utils/stats.d.ts +12 -0
- package/dist/core/analyzer/utils/stats.js +64 -0
- package/dist/core/v2ex/index.d.ts +10 -0
- package/dist/core/v2ex/index.js +27 -0
- package/dist/core/v2ex/parsers/index.d.ts +8 -0
- package/dist/core/v2ex/parsers/index.js +15 -0
- package/dist/core/v2ex/parsers/replies-page.d.ts +11 -0
- package/dist/core/v2ex/parsers/replies-page.js +114 -0
- package/dist/core/v2ex/parsers/selectors/index.d.ts +10 -0
- package/dist/core/v2ex/parsers/selectors/index.js +18 -0
- package/dist/core/v2ex/parsers/selectors/pagination.d.ts +11 -0
- package/dist/core/v2ex/parsers/selectors/pagination.js +14 -0
- package/dist/core/v2ex/parsers/selectors/replies-page.d.ts +21 -0
- package/dist/core/v2ex/parsers/selectors/replies-page.js +24 -0
- package/dist/core/v2ex/parsers/selectors/topic-detail.d.ts +19 -0
- package/dist/core/v2ex/parsers/selectors/topic-detail.js +22 -0
- package/dist/core/v2ex/parsers/selectors/topics-list-page.d.ts +11 -0
- package/dist/core/v2ex/parsers/selectors/topics-list-page.js +14 -0
- package/dist/core/v2ex/parsers/selectors/user-profile.d.ts +11 -0
- package/dist/core/v2ex/parsers/selectors/user-profile.js +14 -0
- package/dist/core/v2ex/parsers/topic-detail.d.ts +11 -0
- package/dist/core/v2ex/parsers/topic-detail.js +94 -0
- package/dist/core/v2ex/parsers/topics-list-page.d.ts +11 -0
- package/dist/core/v2ex/parsers/topics-list-page.js +90 -0
- package/dist/core/v2ex/parsers/user-profile.d.ts +11 -0
- package/dist/core/v2ex/parsers/user-profile.js +70 -0
- package/dist/core/v2ex/parsers/utils/index.d.ts +6 -0
- package/dist/core/v2ex/parsers/utils/index.js +9 -0
- package/dist/core/v2ex/parsers/utils/pagination.d.ts +19 -0
- package/dist/core/v2ex/parsers/utils/pagination.js +29 -0
- package/dist/core/v2ex/types/entities.d.ts +45 -0
- package/dist/core/v2ex/types/entities.js +7 -0
- package/dist/core/v2ex/types/index.d.ts +6 -0
- package/dist/core/v2ex/types/index.js +6 -0
- package/dist/core/v2ex/types/parse-result.d.ts +64 -0
- package/dist/core/v2ex/types/parse-result.js +7 -0
- package/dist/core/v2ex/urls/constants.d.ts +5 -0
- package/dist/core/v2ex/urls/constants.js +8 -0
- package/dist/core/v2ex/urls/index.d.ts +7 -0
- package/dist/core/v2ex/urls/index.js +16 -0
- package/dist/core/v2ex/urls/topic-urls.d.ts +19 -0
- package/dist/core/v2ex/urls/topic-urls.js +48 -0
- package/dist/core/v2ex/urls/user-urls.d.ts +24 -0
- package/dist/core/v2ex/urls/user-urls.js +36 -0
- package/dist/core/v2ex/use-cases/index.d.ts +8 -0
- package/dist/core/v2ex/use-cases/index.js +14 -0
- package/dist/core/v2ex/use-cases/types.d.ts +31 -0
- package/dist/core/v2ex/use-cases/types.js +7 -0
- package/dist/core/v2ex/use-cases/user/index.d.ts +10 -0
- package/dist/core/v2ex/use-cases/user/index.js +16 -0
- package/dist/core/v2ex/use-cases/user/profile.d.ts +14 -0
- package/dist/core/v2ex/use-cases/user/profile.js +51 -0
- package/dist/core/v2ex/use-cases/user/replies.d.ts +14 -0
- package/dist/core/v2ex/use-cases/user/replies.js +20 -0
- package/dist/core/v2ex/use-cases/user/topic-urls.d.ts +21 -0
- package/dist/core/v2ex/use-cases/user/topic-urls.js +29 -0
- package/dist/core/v2ex/use-cases/user/topics-detail.d.ts +30 -0
- package/dist/core/v2ex/use-cases/user/topics-detail.js +62 -0
- package/dist/core/v2ex/use-cases/utils/index.d.ts +6 -0
- package/dist/core/v2ex/use-cases/utils/index.js +9 -0
- package/dist/core/v2ex/use-cases/utils/page-orchestrator.d.ts +24 -0
- package/dist/core/v2ex/use-cases/utils/page-orchestrator.js +93 -0
- package/dist/infra/fetcher/agent.d.ts +10 -0
- package/dist/infra/fetcher/agent.js +17 -0
- package/dist/infra/fetcher/fetcher.d.ts +10 -0
- package/dist/infra/fetcher/fetcher.js +81 -0
- package/dist/infra/fetcher/index.d.ts +3 -0
- package/dist/infra/fetcher/index.js +19 -0
- package/dist/infra/fetcher/types.d.ts +29 -0
- package/dist/infra/fetcher/types.js +6 -0
- package/dist/infra/logger/colors.d.ts +15 -0
- package/dist/infra/logger/colors.js +18 -0
- package/dist/infra/logger/index.d.ts +16 -0
- package/dist/infra/logger/index.js +19 -0
- package/dist/infra/logger/logger.d.ts +34 -0
- package/dist/infra/logger/logger.js +101 -0
- package/dist/infra/storage/cleaner.d.ts +24 -0
- package/dist/infra/storage/cleaner.js +73 -0
- package/dist/infra/storage/index.d.ts +7 -0
- package/dist/infra/storage/index.js +15 -0
- package/dist/infra/storage/paths.d.ts +26 -0
- package/dist/infra/storage/paths.js +53 -0
- package/dist/infra/storage/reader.d.ts +15 -0
- package/dist/infra/storage/reader.js +34 -0
- package/dist/infra/storage/types.d.ts +21 -0
- package/dist/infra/storage/types.js +18 -0
- package/dist/infra/storage/writer.d.ts +16 -0
- package/dist/infra/storage/writer.js +31 -0
- package/package.json +89 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* show 命令 — 展示 AI 分析结果
|
|
4
|
+
*
|
|
5
|
+
* 读取 result.json,以结构化格式输出到终端。
|
|
6
|
+
* 支持 --json 原始输出和 --brief 简略版。
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.runShow = runShow;
|
|
10
|
+
const storage_1 = require("../../infra/storage");
|
|
11
|
+
const logger_1 = require("../../infra/logger");
|
|
12
|
+
const colors_1 = require("../../infra/logger/colors");
|
|
13
|
+
const recovery_1 = require("../workflow/recovery");
|
|
14
|
+
// -- 格式化工具 --------------------------------------------------------------
|
|
15
|
+
/** OCEAN 五维特质的中文标签 */
|
|
16
|
+
const OCEAN_LABELS = {
|
|
17
|
+
openness: '开放性',
|
|
18
|
+
conscientiousness: '尽责性',
|
|
19
|
+
extraversion: '外向性',
|
|
20
|
+
agreeableness: '宜人性',
|
|
21
|
+
neuroticism: '神经质',
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* 渲染分数条 (0-100)
|
|
25
|
+
*
|
|
26
|
+
* 示例: ████████░░ 80
|
|
27
|
+
*/
|
|
28
|
+
function renderScoreBar(score, width = 10) {
|
|
29
|
+
if (!Number.isFinite(score))
|
|
30
|
+
return '░'.repeat(width) + ' N/A';
|
|
31
|
+
const clamped = Math.max(0, Math.min(100, score));
|
|
32
|
+
const filled = Math.round((clamped / 100) * width);
|
|
33
|
+
const empty = width - filled;
|
|
34
|
+
return `${'█'.repeat(filled)}${'░'.repeat(empty)} ${clamped}`;
|
|
35
|
+
}
|
|
36
|
+
/** 风险等级对应的显示样式 */
|
|
37
|
+
function formatRiskLevel(level) {
|
|
38
|
+
switch (level) {
|
|
39
|
+
case 'safe':
|
|
40
|
+
return `${colors_1.COLORS.green}安全${colors_1.COLORS.reset}`;
|
|
41
|
+
case 'suspicious':
|
|
42
|
+
return `${colors_1.COLORS.yellow}可疑${colors_1.COLORS.reset}`;
|
|
43
|
+
case 'high_risk':
|
|
44
|
+
return `${colors_1.COLORS.red}高风险${colors_1.COLORS.reset}`;
|
|
45
|
+
default:
|
|
46
|
+
return level;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// -- 输出模式 ----------------------------------------------------------------
|
|
50
|
+
/** --brief: 简略版输出 */
|
|
51
|
+
function printBrief(result) {
|
|
52
|
+
console.log(`\n${colors_1.COLORS.bold}${colors_1.COLORS.cyan}=== 用户画像摘要 ===${colors_1.COLORS.reset}\n`);
|
|
53
|
+
console.log(result.summary);
|
|
54
|
+
console.log(`\n${colors_1.COLORS.bold}关键指标${colors_1.COLORS.reset}`);
|
|
55
|
+
console.log(` 职业方向: ${result.professional.career_path}`);
|
|
56
|
+
console.log(` 技术水平: ${result.professional.level}`);
|
|
57
|
+
console.log(` 人生阶段: ${result.personal.life_stage}`);
|
|
58
|
+
console.log(` 风险评估: ${formatRiskLevel(result.risk.level)}`);
|
|
59
|
+
}
|
|
60
|
+
/** 默认: 完整格式化输出 */
|
|
61
|
+
function printFull(result) {
|
|
62
|
+
// Summary
|
|
63
|
+
console.log(`\n${colors_1.COLORS.bold}${colors_1.COLORS.cyan}=== 用户画像分析 ===${colors_1.COLORS.reset}\n`);
|
|
64
|
+
console.log(result.summary);
|
|
65
|
+
// Professional
|
|
66
|
+
console.log(`\n${colors_1.COLORS.bold}[职业画像]${colors_1.COLORS.reset}`);
|
|
67
|
+
console.log(` 方向: ${result.professional.career_path}`);
|
|
68
|
+
console.log(` 水平: ${result.professional.level}`);
|
|
69
|
+
console.log(` 技术栈: ${(result.professional.tech_stack ?? []).join(', ')}`);
|
|
70
|
+
console.log(` 专注一致性: ${result.professional.focus_coherence}`);
|
|
71
|
+
const timeline = result.professional.evolution?.timeline ?? [];
|
|
72
|
+
if (timeline.length > 0) {
|
|
73
|
+
console.log(` ${colors_1.COLORS.gray}演变轨迹:${colors_1.COLORS.reset}`);
|
|
74
|
+
for (const entry of timeline) {
|
|
75
|
+
console.log(` ${entry.period} → ${entry.focus}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// Personal
|
|
79
|
+
console.log(`\n${colors_1.COLORS.bold}[个人生活]${colors_1.COLORS.reset}`);
|
|
80
|
+
console.log(` 人生阶段: ${result.personal.life_stage}`);
|
|
81
|
+
console.log(` 兴趣爱好: ${(result.personal.hobbies ?? []).join(', ')}`);
|
|
82
|
+
console.log(` 价值取向: ${(result.personal.values ?? []).join(', ')}`);
|
|
83
|
+
// Psychological (OCEAN)
|
|
84
|
+
console.log(`\n${colors_1.COLORS.bold}[心理画像 — OCEAN]${colors_1.COLORS.reset}`);
|
|
85
|
+
const { scores } = result.psychological;
|
|
86
|
+
for (const [key, label] of Object.entries(OCEAN_LABELS)) {
|
|
87
|
+
const score = scores[key];
|
|
88
|
+
console.log(` ${label.padEnd(4)} ${renderScoreBar(score)}`);
|
|
89
|
+
}
|
|
90
|
+
console.log(` 关键词: ${(result.psychological.keywords ?? []).join(', ')}`);
|
|
91
|
+
// Behavioral
|
|
92
|
+
console.log(`\n${colors_1.COLORS.bold}[行为画像]${colors_1.COLORS.reset}`);
|
|
93
|
+
console.log(` 社区角色: ${result.behavioral.role}`);
|
|
94
|
+
console.log(` 互动风格: ${result.behavioral.interaction_style}`);
|
|
95
|
+
console.log(` 活跃模式: ${result.behavioral.active_pattern}`);
|
|
96
|
+
console.log(` 热度敏感: ${result.behavioral.heat_sensitivity}`);
|
|
97
|
+
// Social
|
|
98
|
+
console.log(`\n${colors_1.COLORS.bold}[社交画像]${colors_1.COLORS.reset}`);
|
|
99
|
+
console.log(` 内容吸引力: ${result.social.content_appeal}`);
|
|
100
|
+
console.log(` 讨论深度: ${result.social.discussion_depth}`);
|
|
101
|
+
// Risk
|
|
102
|
+
console.log(`\n${colors_1.COLORS.bold}[风险评估]${colors_1.COLORS.reset}`);
|
|
103
|
+
console.log(` 等级: ${formatRiskLevel(result.risk.level)}`);
|
|
104
|
+
console.log(` 理由: ${result.risk.reason}`);
|
|
105
|
+
console.log('');
|
|
106
|
+
}
|
|
107
|
+
// -- 命令入口 ----------------------------------------------------------------
|
|
108
|
+
/**
|
|
109
|
+
* 执行 show 命令
|
|
110
|
+
*/
|
|
111
|
+
async function runShow(username, options) {
|
|
112
|
+
const result = (0, storage_1.readDataFile)(username, 'result');
|
|
113
|
+
if (!result) {
|
|
114
|
+
logger_1.logger.error(`未找到 ${username} 的分析结果`);
|
|
115
|
+
logger_1.logger.info('请先运行: v2er ai <username>');
|
|
116
|
+
return {
|
|
117
|
+
step: 'show',
|
|
118
|
+
status: 'failed',
|
|
119
|
+
reasonCode: 'SHOW_RESULT_MISSING',
|
|
120
|
+
message: '缺少 result.json,无法展示报告',
|
|
121
|
+
recoverable: true,
|
|
122
|
+
recoverActions: (0, recovery_1.getRecoveryActions)('SHOW_RESULT_MISSING', { username }),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
// --json: 原始 JSON 输出
|
|
126
|
+
if (options.json) {
|
|
127
|
+
console.log(JSON.stringify(result, null, 2));
|
|
128
|
+
return {
|
|
129
|
+
step: 'show',
|
|
130
|
+
status: 'success',
|
|
131
|
+
message: '已输出 JSON 结果',
|
|
132
|
+
meta: { mode: 'json' },
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
// --brief: 简略版
|
|
136
|
+
if (options.brief) {
|
|
137
|
+
printBrief(result);
|
|
138
|
+
return {
|
|
139
|
+
step: 'show',
|
|
140
|
+
status: 'success',
|
|
141
|
+
message: '已输出简略报告',
|
|
142
|
+
meta: { mode: 'brief' },
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
// 默认: 完整格式化
|
|
146
|
+
printFull(result);
|
|
147
|
+
return {
|
|
148
|
+
step: 'show',
|
|
149
|
+
status: 'success',
|
|
150
|
+
message: '已输出完整报告',
|
|
151
|
+
meta: { mode: 'full' },
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=show.js.map
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* CLI 入口
|
|
5
|
+
*/
|
|
6
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
7
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
8
|
+
};
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
require("dotenv/config");
|
|
11
|
+
const config_1 = require("../config");
|
|
12
|
+
const commander_1 = require("commander");
|
|
13
|
+
const commands_1 = require("./commands");
|
|
14
|
+
const logger_1 = require("../infra/logger");
|
|
15
|
+
const package_json_1 = __importDefault(require("../../package.json"));
|
|
16
|
+
// 从配置文件初始化日志级别(必须在其他初始化之前)
|
|
17
|
+
const configLogLevel = (0, config_1.getConfig)().log?.level;
|
|
18
|
+
if (configLogLevel) {
|
|
19
|
+
logger_1.logger.setLevel(configLogLevel);
|
|
20
|
+
}
|
|
21
|
+
// 为原生 fetch() 设置代理(AI 模块使用)
|
|
22
|
+
(0, config_1.initFetchProxy)();
|
|
23
|
+
commander_1.program
|
|
24
|
+
.name('v2er')
|
|
25
|
+
.description('V2EX user insight - Analysis and profiling tool')
|
|
26
|
+
.version(package_json_1.default.version);
|
|
27
|
+
// 主命令 - 一键分析
|
|
28
|
+
commander_1.program
|
|
29
|
+
.argument('[username]', 'V2EX username')
|
|
30
|
+
.option('--force', 'Force re-fetch from scratch')
|
|
31
|
+
.option('--model [name]', 'Specify AI model (or select interactively)')
|
|
32
|
+
.option('--thinking-level [level]', 'Specify thinking level (or select interactively)')
|
|
33
|
+
.option('-v, --verbose', 'Show debug output')
|
|
34
|
+
.action(async (username, options, command) => {
|
|
35
|
+
if (!username) {
|
|
36
|
+
command.help();
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
await (0, commands_1.runPipeline)(username, options);
|
|
40
|
+
});
|
|
41
|
+
// fetch - 抓取数据
|
|
42
|
+
// NOTE: 子命令仅对 failed 设置 exitCode=1,partial 视为可接受的降级成功(exitCode=0)。
|
|
43
|
+
// 这与 pipeline 模式不同(partial 会导致 exitCode=1),因为 pipeline 中 partial 影响后续步骤质量。
|
|
44
|
+
commander_1.program
|
|
45
|
+
.command('fetch')
|
|
46
|
+
.description('Fetch user profile, topics and replies')
|
|
47
|
+
.argument('<username>', 'V2EX username')
|
|
48
|
+
.option('--topics', 'Fetch topics only')
|
|
49
|
+
.option('--replies', 'Fetch replies only')
|
|
50
|
+
.option('--force', 'Force refetch even if cache exists')
|
|
51
|
+
.action(async (username, options) => {
|
|
52
|
+
const result = await (0, commands_1.runFetch)(username, options);
|
|
53
|
+
if (result.status === 'failed')
|
|
54
|
+
process.exitCode = 1;
|
|
55
|
+
});
|
|
56
|
+
// analyze - 数据分析
|
|
57
|
+
commander_1.program
|
|
58
|
+
.command('analyze')
|
|
59
|
+
.description('Process raw data and generate statistics')
|
|
60
|
+
.argument('<username>', 'V2EX username')
|
|
61
|
+
.action(async (username) => {
|
|
62
|
+
const result = await (0, commands_1.runAnalyze)(username);
|
|
63
|
+
if (result.status === 'failed')
|
|
64
|
+
process.exitCode = 1;
|
|
65
|
+
});
|
|
66
|
+
// ai - AI 画像
|
|
67
|
+
commander_1.program
|
|
68
|
+
.command('ai')
|
|
69
|
+
.description('Generate AI user profile and analysis')
|
|
70
|
+
.argument('<username>', 'V2EX username')
|
|
71
|
+
.option('--model [name]', 'Specify Gemini model (or select interactively)')
|
|
72
|
+
.option('--thinking-level [level]', 'Specify thinking level: minimal | low | medium | high')
|
|
73
|
+
.action(async (username, options) => {
|
|
74
|
+
const result = await (0, commands_1.runAi)(username, options);
|
|
75
|
+
if (result.status === 'failed')
|
|
76
|
+
process.exitCode = 1;
|
|
77
|
+
});
|
|
78
|
+
// show - 展示结果
|
|
79
|
+
commander_1.program
|
|
80
|
+
.command('show')
|
|
81
|
+
.description('Show analysis report')
|
|
82
|
+
.argument('<username>', 'V2EX username')
|
|
83
|
+
.option('--json', 'Output raw JSON')
|
|
84
|
+
.option('--brief', 'Show brief summary only')
|
|
85
|
+
.action(async (username, options) => {
|
|
86
|
+
const result = await (0, commands_1.runShow)(username, options);
|
|
87
|
+
if (result.status === 'failed')
|
|
88
|
+
process.exitCode = 1;
|
|
89
|
+
});
|
|
90
|
+
// config - 配置管理
|
|
91
|
+
const config = commander_1.program.command('config').description('Manage configuration');
|
|
92
|
+
config
|
|
93
|
+
.command('proxy [url]')
|
|
94
|
+
.description('Set, view, or clear proxy')
|
|
95
|
+
.option('--clear', 'Clear proxy setting')
|
|
96
|
+
.action(commands_1.configProxy);
|
|
97
|
+
config.command('show [group]').description('Show current configuration').action(commands_1.configShow);
|
|
98
|
+
config
|
|
99
|
+
.command('set <path> <value>')
|
|
100
|
+
.description('Set a configuration value (e.g. ai.model gemini-2.5-flash)')
|
|
101
|
+
.action(commands_1.configSet);
|
|
102
|
+
config
|
|
103
|
+
.command('reset [group]')
|
|
104
|
+
.description('Reset configuration to defaults (all or specific group)')
|
|
105
|
+
.action(commands_1.configReset);
|
|
106
|
+
commander_1.program.parse();
|
|
107
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI 命令选项类型定义
|
|
3
|
+
*/
|
|
4
|
+
import type { ThinkingLevel } from '../config';
|
|
5
|
+
/**
|
|
6
|
+
* fetch 命令选项
|
|
7
|
+
*/
|
|
8
|
+
export interface FetchCommandOptions {
|
|
9
|
+
/** 仅抓取话题 */
|
|
10
|
+
topics?: boolean;
|
|
11
|
+
/** 仅抓取回复 */
|
|
12
|
+
replies?: boolean;
|
|
13
|
+
/** 强制重新抓取,忽略缓存 */
|
|
14
|
+
force?: boolean;
|
|
15
|
+
/** 由一键流程触发时开启,供命令控制日志粒度 */
|
|
16
|
+
pipeline?: boolean;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* ai 命令选项
|
|
20
|
+
*/
|
|
21
|
+
export interface AiCommandOptions {
|
|
22
|
+
/** 临时指定模型,或 true 表示触发交互选择 */
|
|
23
|
+
model?: string | true;
|
|
24
|
+
/** 临时覆盖思考等级,或 true 表示触发交互选择 */
|
|
25
|
+
thinkingLevel?: ThinkingLevel | true;
|
|
26
|
+
/** 由一键流程触发时开启,供命令控制日志粒度 */
|
|
27
|
+
pipeline?: boolean;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* show 命令选项
|
|
31
|
+
*/
|
|
32
|
+
export interface ShowCommandOptions {
|
|
33
|
+
/** 输出原始 JSON */
|
|
34
|
+
json?: boolean;
|
|
35
|
+
/** 简略版输出(仅 summary + 关键指标) */
|
|
36
|
+
brief?: boolean;
|
|
37
|
+
/** 由一键流程触发时开启,供命令控制日志粒度 */
|
|
38
|
+
pipeline?: boolean;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* 主命令 `v2er <username>` 选项
|
|
42
|
+
*
|
|
43
|
+
* Commander 将 --model [name] 解析为 string | true:
|
|
44
|
+
* - `--model gemini-2.5-pro` → model = 'gemini-2.5-pro'
|
|
45
|
+
* - `--model`(无值)→ model = true(触发交互选择)
|
|
46
|
+
* --thinking-level 同理。
|
|
47
|
+
*/
|
|
48
|
+
export interface RunCommandOptions {
|
|
49
|
+
/** 强制重新抓取,忽略缓存 */
|
|
50
|
+
force?: boolean;
|
|
51
|
+
/** 临时指定模型,或 true 表示触发交互选择 */
|
|
52
|
+
model?: string | true;
|
|
53
|
+
/** 临时覆盖思考等级,或 true 表示触发交互选择 */
|
|
54
|
+
thinkingLevel?: ThinkingLevel | true;
|
|
55
|
+
/** 显示 debug 级别日志 */
|
|
56
|
+
verbose?: boolean;
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.extractErrorDetails = extractErrorDetails;
|
|
4
|
+
function extractErrorDetails(error) {
|
|
5
|
+
if (error instanceof Error) {
|
|
6
|
+
const raw = error.stack ?? `${error.name}: ${error.message}`;
|
|
7
|
+
return {
|
|
8
|
+
message: error.message,
|
|
9
|
+
raw,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
const raw = String(error);
|
|
13
|
+
return {
|
|
14
|
+
message: raw,
|
|
15
|
+
raw,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=error.js.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI 共享工具函数
|
|
3
|
+
*/
|
|
4
|
+
import type { FetchResult, FetchEvents } from '../infra/fetcher';
|
|
5
|
+
/**
|
|
6
|
+
* 打印抓取错误详情
|
|
7
|
+
*
|
|
8
|
+
* 优化输出格式:第一行使用 logger.error 显示错误标题,
|
|
9
|
+
* 后续细节使用 logger.detail 保持缩进对齐。
|
|
10
|
+
*/
|
|
11
|
+
export declare function logFetchError(result: FetchResult): void;
|
|
12
|
+
/**
|
|
13
|
+
* 创建通用的抓取事件回调
|
|
14
|
+
*
|
|
15
|
+
* 用于命令行抓取过程中的进度展示和错误记录。
|
|
16
|
+
*
|
|
17
|
+
* @param label - 进度条前的标签(如 "获取资料"、"抓取帖子")
|
|
18
|
+
*/
|
|
19
|
+
export declare function createFetchEvents(label: string): FetchEvents;
|
|
20
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* CLI 共享工具函数
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.logFetchError = logFetchError;
|
|
7
|
+
exports.createFetchEvents = createFetchEvents;
|
|
8
|
+
const logger_1 = require("../infra/logger");
|
|
9
|
+
/** 响应体预览最大长度 */
|
|
10
|
+
const ERROR_BODY_PREVIEW_LENGTH = 200;
|
|
11
|
+
/**
|
|
12
|
+
* 打印抓取错误详情
|
|
13
|
+
*
|
|
14
|
+
* 优化输出格式:第一行使用 logger.error 显示错误标题,
|
|
15
|
+
* 后续细节使用 logger.detail 保持缩进对齐。
|
|
16
|
+
*/
|
|
17
|
+
function logFetchError(result) {
|
|
18
|
+
logger_1.logger.error(`抓取失败: ${result.url}`);
|
|
19
|
+
if (result.statusCode !== undefined) {
|
|
20
|
+
logger_1.logger.detail(`状态码: ${result.statusCode}`);
|
|
21
|
+
}
|
|
22
|
+
if (result.error) {
|
|
23
|
+
logger_1.logger.detail(`错误信息: ${result.error.message}`);
|
|
24
|
+
}
|
|
25
|
+
if (result.errorBody) {
|
|
26
|
+
const preview = result.errorBody.substring(0, ERROR_BODY_PREVIEW_LENGTH).replace(/\s+/g, ' ');
|
|
27
|
+
const suffix = result.errorBody.length > ERROR_BODY_PREVIEW_LENGTH ? '...' : '';
|
|
28
|
+
logger_1.logger.detail(`响应预览: ${preview}${suffix}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* 创建通用的抓取事件回调
|
|
33
|
+
*
|
|
34
|
+
* 用于命令行抓取过程中的进度展示和错误记录。
|
|
35
|
+
*
|
|
36
|
+
* @param label - 进度条前的标签(如 "获取资料"、"抓取帖子")
|
|
37
|
+
*/
|
|
38
|
+
function createFetchEvents(label) {
|
|
39
|
+
return {
|
|
40
|
+
onStart: (_url, index, total) => {
|
|
41
|
+
logger_1.logger.progress(index, total, label);
|
|
42
|
+
},
|
|
43
|
+
onError: (result) => {
|
|
44
|
+
logFetchError(result);
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { RunOutcome, RunWorkflowOptions, StepRunResult, WorkflowStep } from './types';
|
|
2
|
+
type StepExecutor = () => Promise<StepRunResult>;
|
|
3
|
+
type WorkflowExecutorSet = Record<WorkflowStep, StepExecutor>;
|
|
4
|
+
/**
|
|
5
|
+
* 构建工作流步骤执行器集合,供编排层按步骤名调度。
|
|
6
|
+
*/
|
|
7
|
+
export declare function buildStepExecutors(options: RunWorkflowOptions): WorkflowExecutorSet;
|
|
8
|
+
/**
|
|
9
|
+
* 执行一键工作流并聚合步骤结果。
|
|
10
|
+
* @param options 一键工作流入口参数
|
|
11
|
+
* @returns 聚合后的流程执行结果
|
|
12
|
+
*/
|
|
13
|
+
export declare function runWorkflow(options: RunWorkflowOptions): Promise<RunOutcome>;
|
|
14
|
+
export {};
|
|
15
|
+
//# sourceMappingURL=orchestrator.d.ts.map
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildStepExecutors = buildStepExecutors;
|
|
4
|
+
exports.runWorkflow = runWorkflow;
|
|
5
|
+
const logger_1 = require("../../infra/logger");
|
|
6
|
+
const commands_1 = require("../commands");
|
|
7
|
+
const error_1 = require("../utils/error");
|
|
8
|
+
const state_1 = require("./state");
|
|
9
|
+
const STEP_LABEL = {
|
|
10
|
+
fetch: '抓取',
|
|
11
|
+
analyze: '分析',
|
|
12
|
+
ai: 'AI',
|
|
13
|
+
show: '展示',
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* 构建工作流步骤执行器集合,供编排层按步骤名调度。
|
|
17
|
+
*/
|
|
18
|
+
function buildStepExecutors(options) {
|
|
19
|
+
const fetchOptions = {
|
|
20
|
+
force: options.force,
|
|
21
|
+
pipeline: true,
|
|
22
|
+
};
|
|
23
|
+
const aiOptions = {
|
|
24
|
+
model: options.model,
|
|
25
|
+
thinkingLevel: options.thinkingLevel,
|
|
26
|
+
pipeline: true,
|
|
27
|
+
};
|
|
28
|
+
const showOptions = {
|
|
29
|
+
pipeline: true,
|
|
30
|
+
};
|
|
31
|
+
return {
|
|
32
|
+
fetch: () => (0, commands_1.runFetch)(options.username, fetchOptions),
|
|
33
|
+
analyze: () => (0, commands_1.runAnalyze)(options.username, { pipeline: true }),
|
|
34
|
+
ai: () => (0, commands_1.runAi)(options.username, aiOptions),
|
|
35
|
+
show: () => (0, commands_1.runShow)(options.username, showOptions),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* 执行一键工作流并聚合步骤结果。
|
|
40
|
+
* @param options 一键工作流入口参数
|
|
41
|
+
* @returns 聚合后的流程执行结果
|
|
42
|
+
*/
|
|
43
|
+
async function runWorkflow(options) {
|
|
44
|
+
const state = (0, state_1.detectWorkflowState)(options.username);
|
|
45
|
+
const entryStep = (0, state_1.resolveEntryStep)(state, options.force);
|
|
46
|
+
const plan = (0, state_1.buildExecutionPlan)(entryStep);
|
|
47
|
+
const executors = buildStepExecutors(options);
|
|
48
|
+
const results = [];
|
|
49
|
+
logger_1.logger.debug(`workflow state: raw=${state.hasRaw}, analyzed=${state.hasAnalyzed}, result=${state.hasResult}`);
|
|
50
|
+
logger_1.logger.debug(`workflow entry: ${entryStep}`);
|
|
51
|
+
logger_1.logger.debug(`workflow plan: ${plan.join(' -> ')}`);
|
|
52
|
+
let hasPartial = false;
|
|
53
|
+
for (const [index, step] of plan.entries()) {
|
|
54
|
+
try {
|
|
55
|
+
const result = await executors[step]();
|
|
56
|
+
results.push(result);
|
|
57
|
+
printStepLine(result, index, plan.length);
|
|
58
|
+
if (result.status === 'partial') {
|
|
59
|
+
hasPartial = true;
|
|
60
|
+
printResultSummary(result);
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (result.status === 'failed') {
|
|
64
|
+
printResultSummary(result);
|
|
65
|
+
return {
|
|
66
|
+
overallStatus: 'failed',
|
|
67
|
+
exitCode: 1,
|
|
68
|
+
failedStep: step,
|
|
69
|
+
results,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
const { message, raw } = (0, error_1.extractErrorDetails)(error);
|
|
75
|
+
const result = {
|
|
76
|
+
step,
|
|
77
|
+
status: 'failed',
|
|
78
|
+
reasonCode: 'UNKNOWN_ERROR',
|
|
79
|
+
message: `步骤执行异常: ${message}`,
|
|
80
|
+
meta: {
|
|
81
|
+
rawError: raw,
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
results.push(result);
|
|
85
|
+
printStepLine(result, index, plan.length);
|
|
86
|
+
printResultSummary(result);
|
|
87
|
+
return {
|
|
88
|
+
overallStatus: 'failed',
|
|
89
|
+
exitCode: 1,
|
|
90
|
+
failedStep: step,
|
|
91
|
+
results,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
overallStatus: hasPartial ? 'partial' : 'success',
|
|
97
|
+
exitCode: hasPartial ? 1 : 0,
|
|
98
|
+
results,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function printStepLine(result, index, total) {
|
|
102
|
+
const progress = `[${index + 1}/${total}]`;
|
|
103
|
+
const stepLabel = STEP_LABEL[result.step];
|
|
104
|
+
const message = result.message ? ` ${result.message}` : '';
|
|
105
|
+
if (result.status === 'failed') {
|
|
106
|
+
logger_1.logger.error(`${progress} ${stepLabel}失败${message}`);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (result.status === 'partial') {
|
|
110
|
+
logger_1.logger.warn(`${progress} ${stepLabel}部分完成${message}`);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (result.status === 'skipped') {
|
|
114
|
+
logger_1.logger.info(`${progress} ${stepLabel}已跳过${message}`);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
logger_1.logger.info(`${progress} ${stepLabel}完成${message}`);
|
|
118
|
+
}
|
|
119
|
+
function printResultSummary(result) {
|
|
120
|
+
if (result.reasonCode) {
|
|
121
|
+
logger_1.logger.info(`原因码: ${result.reasonCode}`);
|
|
122
|
+
}
|
|
123
|
+
const rawError = result.meta?.rawError;
|
|
124
|
+
if (typeof rawError === 'string') {
|
|
125
|
+
logger_1.logger.detail(rawError);
|
|
126
|
+
}
|
|
127
|
+
if (!result.recoverActions || result.recoverActions.length === 0) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
logger_1.logger.info('恢复建议:');
|
|
131
|
+
for (const action of result.recoverActions) {
|
|
132
|
+
printRecoveryAction(action);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function printRecoveryAction(action) {
|
|
136
|
+
if (action.type === 'command') {
|
|
137
|
+
logger_1.logger.detail(`命令: ${action.content}`);
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
logger_1.logger.detail(`操作: ${action.content}`);
|
|
141
|
+
}
|
|
142
|
+
logger_1.logger.detail(`说明: ${action.description}`);
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=orchestrator.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ReasonCode, RecoveryAction } from './types';
|
|
2
|
+
interface RecoveryRenderContext {
|
|
3
|
+
username?: string;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* 获取指定原因码对应的恢复动作列表,并按上下文渲染模板变量。
|
|
7
|
+
*/
|
|
8
|
+
export declare function getRecoveryActions(reasonCode?: ReasonCode, context?: RecoveryRenderContext): RecoveryAction[];
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=recovery.d.ts.map
|