byteplan-cli 1.0.2 → 1.2.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 (65) hide show
  1. package/package.json +7 -3
  2. package/skills/byteplan-analysis/SKILL.md +1078 -0
  3. package/skills/byteplan-api/API_REFERENCE.md +249 -0
  4. package/skills/byteplan-api/SKILL.md +96 -0
  5. package/skills/byteplan-api/package.json +16 -0
  6. package/skills/byteplan-api/scripts/api.js +973 -0
  7. package/skills/byteplan-excel/SKILL.md +212 -0
  8. package/skills/byteplan-excel/examples/margin-analysis.json +40 -0
  9. package/skills/byteplan-excel/package.json +12 -0
  10. package/skills/byteplan-excel/pnpm-lock.yaml +68 -0
  11. package/skills/byteplan-excel/scripts/generate_excel.js +156 -0
  12. package/skills/byteplan-html/SKILL.md +490 -0
  13. package/skills/byteplan-html/examples/example-output.html +184 -0
  14. package/skills/byteplan-html/examples/generate-ppt-style-html.js +611 -0
  15. package/skills/byteplan-html/examples/margin-contribution-analysis.json +152 -0
  16. package/skills/byteplan-html/package.json +18 -0
  17. package/skills/byteplan-html/scripts/generate_html.js +517 -0
  18. package/skills/byteplan-ppt/SKILL.md +394 -0
  19. package/skills/byteplan-ppt/examples/margin-contribution-analysis.json +152 -0
  20. package/skills/byteplan-ppt/package.json +16 -0
  21. package/skills/byteplan-ppt/pnpm-lock.yaml +138 -0
  22. package/skills/byteplan-ppt/scripts/check_ppt_overlap.js +318 -0
  23. package/skills/byteplan-ppt/scripts/generate_ppt.js +680 -0
  24. package/skills/byteplan-video/SKILL.md +606 -0
  25. package/skills/byteplan-video/examples/sample-video-data.json +82 -0
  26. package/skills/byteplan-video/remotion-project/package.json +22 -0
  27. package/skills/byteplan-video/remotion-project/pnpm-lock.yaml +1646 -0
  28. package/skills/byteplan-video/remotion-project/remotion.config.ts +6 -0
  29. package/skills/byteplan-video/remotion-project/scene_durations.json +32 -0
  30. package/skills/byteplan-video/remotion-project/scripts/generate_audio.js +279 -0
  31. package/skills/byteplan-video/remotion-project/src/DynamicReport.tsx +172 -0
  32. package/skills/byteplan-video/remotion-project/src/Root.tsx +51 -0
  33. package/skills/byteplan-video/remotion-project/src/SalesReport.tsx +107 -0
  34. package/skills/byteplan-video/remotion-project/src/index.tsx +4 -0
  35. package/skills/byteplan-video/remotion-project/src/scenes/ChartSlide.tsx +201 -0
  36. package/skills/byteplan-video/remotion-project/src/scenes/CoverSlide.tsx +61 -0
  37. package/skills/byteplan-video/remotion-project/src/scenes/EndSlide.tsx +60 -0
  38. package/skills/byteplan-video/remotion-project/src/scenes/InsightSlide.tsx +101 -0
  39. package/skills/byteplan-video/remotion-project/src/scenes/KpiSlide.tsx +84 -0
  40. package/skills/byteplan-video/remotion-project/src/scenes/RecommendationSlide.tsx +100 -0
  41. package/skills/byteplan-video/remotion-project/tsconfig.json +13 -0
  42. package/skills/byteplan-video/remotion-project/video_data.json +76 -0
  43. package/skills/byteplan-video/scripts/generate_video.js +270 -0
  44. package/skills/byteplan-video/templates/package.json +31 -0
  45. package/skills/byteplan-video/templates/pnpm-lock.yaml +2200 -0
  46. package/skills/byteplan-video/templates/remotion.config.ts +9 -0
  47. package/skills/byteplan-video/templates/scripts/generate-audio.ts +55 -0
  48. package/skills/byteplan-video/templates/src/components/BarChartScene.tsx +153 -0
  49. package/skills/byteplan-video/templates/src/components/InsightScene.tsx +135 -0
  50. package/skills/byteplan-video/templates/src/components/LineChartScene.tsx +214 -0
  51. package/skills/byteplan-video/templates/src/components/SceneFactory.tsx +34 -0
  52. package/skills/byteplan-video/templates/src/components/SummaryScene.tsx +155 -0
  53. package/skills/byteplan-video/templates/src/components/TitleScene.tsx +130 -0
  54. package/skills/byteplan-video/templates/src/compositions/AnalysisVideo.tsx +39 -0
  55. package/skills/byteplan-video/templates/src/index.tsx +28 -0
  56. package/skills/byteplan-video/templates/src/register-root.tsx +4 -0
  57. package/skills/byteplan-video/templates/src/storyboard/types.ts +46 -0
  58. package/skills/byteplan-video/templates/tsconfig.json +17 -0
  59. package/skills/byteplan-video/templates/tsconfig.scripts.json +13 -0
  60. package/skills/byteplan-word/SKILL.md +233 -0
  61. package/skills/byteplan-word/package.json +12 -0
  62. package/skills/byteplan-word/pnpm-lock.yaml +120 -0
  63. package/skills/byteplan-word/scripts/generate_word.js +548 -0
  64. package/src/cli.js +4 -0
  65. package/src/commands/skills.js +279 -0
@@ -0,0 +1,1078 @@
1
+ ---
2
+ name: byteplan-analysis
3
+ description: 从 BytePlan 平台查询数据并生成分析报告的 skill。处理流程:登录 → 生成分析任务列表(LLM) → 查询数据 → 输出 Markdown 格式数据列表。生成 PPT 或 Word 需要单独使用 byteplan-ppt 或 byteplan-word skill。
4
+ ---
5
+
6
+ # BytePlan Analysis Skill
7
+
8
+ ## 概述
9
+
10
+ 此 Skill 专注于从 BytePlan 平台查询数据并生成分析报告的**数据层**工作:
11
+
12
+ 1. **登录认证** - 通过 BytePlan 平台认证(凭证存储在 `~/.byteplan/.env`)
13
+ 2. **创建工作目录** - 为每个分析主题创建独立文件夹,防止干扰
14
+ 3. **生成分析任务列表** - 使用 LLM 根据分析主题生成 Analysis Task List
15
+ 4. **查询数据** - 按照任务列表依次查询 BytePlan 数据
16
+ 5. **输出数据** - 生成 Markdown 格式的数据列表
17
+ 6. **生成后续建议** - 使用 LLM 根据数据分析结果生成后续行动建议
18
+
19
+ **⚠️ 核心规则**:所有生成的文件(analysisPlan.json、analysis_report.md、Excel、PPT、Word 等)都必须放在独立工作目录 `~/.byteplan/workspaces/{主题}_{时间戳}/` 下,绝不能放在其他位置。
20
+
21
+ **⚠️ 分析完成后必须询问**:分析完成后,**必须立即主动询问用户**是否需要生成 Excel/PPT/Word/网页/视频 报告,**不能等用户主动要求**。
22
+
23
+ **注意**:生成 Excel/PPT/Word/网页/视频 报告需要使用单独的 skill:
24
+ - [byteplan-excel](../byteplan-excel/SKILL.md) - Excel 报告
25
+ - [byteplan-ppt](../byteplan-ppt/SKILL.md) - PPT 演示文稿
26
+ - [byteplan-word](../byteplan-word/SKILL.md) - Word 文档
27
+ - [byteplan-html](../byteplan-html/SKILL.md) - 网页报告
28
+ - [byteplan-video](../byteplan-video/SKILL.md) - 数据可视化视频
29
+
30
+ ## 目录结构(重要)
31
+
32
+ ```
33
+ ~/.byteplan/
34
+ ├── .env # 凭证和 token(全局共享)
35
+ │ ├── BP_ENV=uat
36
+ │ ├── BP_USER=手机号
37
+ │ ├── BP_PASSWORD=密码
38
+ │ ├── ACCESS_TOKEN=...
39
+ │ └── TOKEN_EXPIRES_IN=...
40
+
41
+ └── workspaces/ # 分析主题工作目录
42
+ ├── 计算机资产折旧_20260331_224600/
43
+ │ ├── analysisPlan.json # 分析任务列表
44
+ │ ├── analysis_report.md # Markdown 报告
45
+ │ └── raw_data/ # 原始数据(可选)
46
+
47
+ ├── 计算机资产折旧_20260401_103000/ # 重新分析时创建新目录
48
+ │ ├── analysisPlan.json
49
+ │ └── analysis_report.md
50
+
51
+ └── 库存周转分析_20260402_090000/
52
+ │ ├── analysisPlan.json
53
+ │ └── analysis_report.md
54
+ └ ...
55
+ ```
56
+
57
+ **工作目录命名规则**:
58
+ - 格式:`{分析主题简称}_{时间戳}`
59
+ - 时间戳:`YYYYMMDD_HHMMSS`
60
+ - 示例:`计算机资产折旧_20260331_224600`
61
+
62
+ **⚠️ 文件位置规则**:
63
+
64
+ | 文件类型 | 正确位置 | 错误位置 |
65
+ |----------|----------|----------|
66
+ | analysisPlan.json | `workspaces/{主题}_{时间戳}/` ✅ | `~/.byteplan/` ❌ |
67
+ | analysis_report.md | `workspaces/{主题}_{时间戳}/` ✅ | `~/.openclaw/workspace/` ❌ |
68
+ | Excel/PPT/Word | `workspaces/{主题}_{时间戳}/` ✅ | `/tmp/` ❌ |
69
+ | 所有输出文件 | **同一工作目录** ✅ | 分散在不同目录 ❌ |
70
+
71
+ **重新分析处理**:
72
+ - 当用户说"重新分析"时,自动创建新的工作目录
73
+ - 使用新的时间戳,保留历史分析记录
74
+ - 例如:第一次 `计算机资产折旧_20260331_224600`,重新分析 `计算机资产折旧_20260401_103000`
75
+
76
+ ## 核心流程
77
+
78
+ ```
79
+ 用户输入分析主题
80
+
81
+ 【第一步】创建工作目录(~/.byteplan/workspaces/{主题}_{时间戳}/)← 重要!
82
+
83
+ 登录认证(优先使用 ~/.byteplan/.env 缓存 token)
84
+
85
+ 查询可用模型
86
+
87
+ 使用 LLM 生成分析任务列表 (Analysis Task List)
88
+
89
+ 保存 analysisPlan.json 到工作目录 ← 所有文件都在此目录!
90
+
91
+ 依次执行任务查询数据
92
+
93
+ 输出 analysis_report.md 到工作目录 ← 所有文件都在此目录!
94
+
95
+ 使用 LLM 生成后续建议
96
+
97
+ 【必须执行】询问用户:是否生成 网页/PPT/Word?
98
+
99
+ 生成报告文件到【同一工作目录】← 所有文件都在此目录!
100
+ ```
101
+
102
+ **工作目录创建时机**:
103
+ - ✅ 用户输入新的分析主题 → 创建新工作目录
104
+ - ✅ 用户说"重新分析" → 创建新工作目录(保留历史)
105
+ - ❌ 用户说"继续分析"或"查看上次分析" → 使用现有工作目录
106
+
107
+ ## 依赖
108
+
109
+ 本 skill 依赖 [byteplan-api](../byteplan-api/SKILL.md) 提供 API 调用能力。
110
+
111
+ ### API 导入方式
112
+
113
+ ```javascript
114
+ import { login, loginWithEnv, getUserInfo, setEnvironment, queryModels, getModelData, getModelColumns } from '/Users/fudebao/.claude/skills/byteplan-api/scripts/api.js';
115
+ ```
116
+
117
+ ## 前置条件
118
+
119
+ ### 凭证存储位置
120
+
121
+ 系统使用 `~/.byteplan/.env` 存储凭证(用户主目录下的 byteplan 文件夹):
122
+
123
+ ```env
124
+ BP_ENV=uat # UAT 环境
125
+ BP_USER=你的手机号
126
+ BP_PASSWORD="你的密码"
127
+ # 以下字段由系统自动管理(登录成功后自动写入)
128
+ ACCESS_TOKEN= # 访问令牌
129
+ REFRESH_TOKEN= # 刷新令牌
130
+ TOKEN_EXPIRES_IN= # 过期时间戳
131
+ ```
132
+
133
+ **首次使用时**:如果没有凭证文件,系统会询问用户输入账号密码,登录成功后自动创建 `~/.byteplan/` 目录并保存凭证。
134
+
135
+ ## 工作流程详解
136
+
137
+ ### 步骤 1:创建工作目录
138
+
139
+ **在开始分析之前,首先创建独立的工作目录,防止不同分析主题干扰。**
140
+
141
+ ```javascript
142
+ import { homedir } from 'os';
143
+ import path from 'path';
144
+ import { existsSync, mkdirSync } from 'fs';
145
+
146
+ // 基础目录
147
+ const BYTEPLAN_DIR = path.join(homedir(), '.byteplan');
148
+ const WORKSPACES_DIR = path.join(BYTEPLAN_DIR, 'workspaces');
149
+
150
+ // 确保 workspaces 目录存在
151
+ if (!existsSync(WORKSPACES_DIR)) {
152
+ mkdirSync(WORKSPACES_DIR, { recursive: true });
153
+ }
154
+
155
+ // 生成工作目录名称
156
+ function generateWorkspaceName(theme: string): string {
157
+ // 简化主题名称(取前20个字符,去除特殊字符)
158
+ const shortTheme = theme.replace(/[^\u4e00-\u9fa5a-zA-Z0-9]/g, '').substring(0, 20);
159
+
160
+ // 生成时间戳
161
+ const now = new Date();
162
+ const timestamp = `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, '0')}${String(now.getDate()).padStart(2, '0')}_${String(now.getHours()).padStart(2, '0')}${String(now.getMinutes()).padStart(2, '0')}${String(now.getSeconds()).padStart(2, '0')}`;
163
+
164
+ return `${shortTheme}_${timestamp}`;
165
+ }
166
+
167
+ // 检查是否需要创建新目录
168
+ function shouldCreateNewWorkspace(userInput: string, existingWorkspaces: string[]): boolean {
169
+ // 用户说"重新分析" → 创建新目录
170
+ if (userInput.includes('重新分析') || userInput.includes('重新') || userInput.includes('再分析')) {
171
+ return true;
172
+ }
173
+
174
+ // 用户说"继续分析"或"查看上次分析" → 使用现有目录
175
+ if (userInput.includes('继续') || userInput.includes('查看') || userInput.includes('上次')) {
176
+ return false;
177
+ }
178
+
179
+ // 新的分析主题 → 创建新目录
180
+ return true;
181
+ }
182
+
183
+ // 创建工作目录
184
+ const workspaceName = generateWorkspaceName(analysisTheme);
185
+ const workspacePath = path.join(WORKSPACES_DIR, workspaceName);
186
+
187
+ if (!existsSync(workspacePath)) {
188
+ mkdirSync(workspacePath, { recursive: true });
189
+ console.log(`✅ 创建工作目录: ${workspaceName}`);
190
+ } else {
191
+ console.log(`📁 使用现有工作目录: ${workspaceName}`);
192
+ }
193
+
194
+ // 切换工作目录(后续文件操作都在此目录下)
195
+ process.chdir(workspacePath);
196
+ console.log(`📌 当前工作目录: ${workspacePath}`);
197
+ ```
198
+
199
+ **工作目录创建逻辑**:
200
+
201
+ | 用户意图 | 处理方式 | 示例 |
202
+ |----------|----------|------|
203
+ | 新分析主题 | 创建新目录 | "分析计算机资产" → 创建新目录 |
204
+ | 重新分析 | 创建新目录(保留历史) | "重新分析" → 创建新目录 |
205
+ | 继续分析 | 使用最近目录 | "继续上次的分析" → 使用现有目录 |
206
+ | 查看历史 | 不创建新目录 | "查看上次的分析结果" → 使用现有目录 |
207
+
208
+ ### 步骤 2:验证登录状态或重新登录
209
+
210
+ **优化流程**:先检查 `~/.byteplan/.env` 中缓存的 token 是否有效,避免每次都重新登录。
211
+
212
+ 1. 使用 UAT 环境登录
213
+ 2. 尝试使用 `~/.byteplan/.env` 中缓存的 token(如果存在且未过期)
214
+ 3. 如果缓存 token 有效,直接使用;否则重新登录
215
+ 4. 登录成功后**必须显示当前租户信息**
216
+
217
+ ```javascript
218
+ import { login, loginWithEnv, getUserInfo, setEnvironment, getToken } from '/Users/fudebao/.claude/skills/byteplan-api/scripts/api.js';
219
+ import { existsSync, readFileSync } from 'fs';
220
+ import { homedir } from 'os';
221
+ import path from 'path';
222
+
223
+ // 凭证文件路径
224
+ const BYTEPLAN_DIR = path.join(homedir(), '.byteplan');
225
+ const ENV_FILE = path.join(BYTEPLAN_DIR, '.env');
226
+
227
+ // 1. 设置环境为 UAT
228
+ setEnvironment('uat');
229
+
230
+ // 2. 尝试使用缓存的 token(优先)
231
+ let token;
232
+ let loginResult;
233
+ let isCachedLogin = false;
234
+
235
+ // 尝试使用 ~/.byteplan/.env 中缓存的 token
236
+ if (existsSync(ENV_FILE)) {
237
+ loginResult = await loginWithEnv('uat', false).catch(() => null);
238
+ if (loginResult?.access_token) {
239
+ token = loginResult.access_token;
240
+ isCachedLogin = loginResult._cached === true; // 标记是否使用缓存
241
+ }
242
+ }
243
+
244
+ // 3. 如果缓存无效,询问用户输入凭证并重新登录
245
+ if (!token) {
246
+ // 检查是否有保存的账号密码
247
+ const envContent = existsSync(ENV_FILE) ? readFileSync(ENV_FILE, 'utf-8') : '';
248
+ const hasCredentials = envContent.includes('BP_USER=') || envContent.includes('USER_NAME=');
249
+
250
+ if (hasCredentials) {
251
+ // 有凭证但 token 过期,尝试重新登录
252
+ loginResult = await loginWithEnv(selectedEnv, true).catch(() => null);
253
+ if (loginResult?.access_token) {
254
+ token = loginResult.access_token;
255
+ }
256
+ }
257
+
258
+ // 仍然没有 token,需要用户输入
259
+ if (!token) {
260
+ // 使用 AskUserQuestion 询问用户输入账号密码
261
+ // ... 获取 username 和 password
262
+ loginResult = await login(username, password, selectedEnv);
263
+ token = loginResult.access_token;
264
+ }
265
+ }
266
+
267
+ // 4. 显示登录状态和租户信息
268
+ const userInfo = await getUserInfo(token);
269
+ const currentTenant = userInfo.user?.tenantName || '未知租户';
270
+
271
+ if (isCachedLogin) {
272
+ console.log(`✅ 使用缓存的登录状态,无需重新登录`);
273
+ } else {
274
+ console.log(`✅ 登录成功!Token 已缓存到 ~/.byteplan/.env`);
275
+ }
276
+ console.log(`📌 当前租户: ${currentTenant}`);
277
+ console.log(`📌 当前环境: ${selectedEnv}`);
278
+ ```
279
+
280
+ #### 登录状态说明
281
+
282
+ | 状态 | 说明 | 处理方式 |
283
+ |------|------|----------|
284
+ | 缓存有效 | `~/.byteplan/.env` 中有未过期的 token | 直接使用,无需重新登录 |
285
+ | 缓存过期 | token 已过期(提前5分钟判定) | 使用保存的凭证重新登录 |
286
+ | 无凭证 | `~/.byteplan/.env` 不存在或缺少账号密码 | 询问用户输入并登录 |
287
+
288
+ #### API 函数说明
289
+
290
+ | 函数 | 描述 |
291
+ |------|------|
292
+ | `loginWithEnv(env, forceReLogin)` | 使用 `~/.byteplan/.env` 凭证登录,`forceReLogin=false` 时优先使用缓存 |
293
+ | `getToken()` | 获取当前缓存的 token |
294
+ | `isTokenExpired()` | 检查 token 是否过期(内部函数) |
295
+ | `login(username, password, env)` | 手动输入凭证登录 |
296
+
297
+ ### 步骤 3:查询可用模型
298
+
299
+ ```javascript
300
+ import { queryModels } from '/Users/fudebao/.claude/skills/byteplan-api/scripts/api.js';
301
+
302
+ const models = await queryModels(token);
303
+
304
+ // 筛选与分析主题相关的模型(可选)
305
+ const relatedModels = models.filter(m => {
306
+ const name = m.modelName || '';
307
+ const code = m.modelCode || '';
308
+ // 根据分析主题关键词筛选
309
+ return name.includes('资产') || code.includes('ASSET');
310
+ });
311
+
312
+ // 保存模型列表
313
+ const modelList = models.map(m => ({
314
+ name: m.modelName,
315
+ code: m.modelCode
316
+ }));
317
+ ```
318
+
319
+ ### 步骤 4:使用 LLM 生成分析任务列表
320
+
321
+ **调用 LLM 生成分析任务列表 JSON**
322
+
323
+ #### LLM Prompt 模板
324
+
325
+ ```
326
+ # 角色
327
+ 你是一个企业级数据分析规划 Agent(Analysis Planner)。
328
+
329
+ # 任务
330
+ 根据用户提供的【分析主题】,生成一份【分析任务列表】,用于驱动后续的数据查询和分析。
331
+
332
+ # 可用模型列表
333
+ ```json
334
+ {available_models_json}
335
+ ```
336
+
337
+ # 分析主题
338
+ {analysis_theme}
339
+
340
+ # 你必须遵守的规则
341
+
342
+ 1. 不要进行任何数据计算
343
+ 2. 不要猜测数据内容
344
+ 3. 只输出合法 JSON,符合 AnalysisTaskList Schema
345
+ 4. 每个任务只对应一个数据查询
346
+ 5. 任务之间逻辑清晰、层级合理
347
+
348
+ # AnalysisTaskList Schema
349
+
350
+ ```json
351
+ {
352
+ "analysisPlanId": "PLAN_ID",
353
+ "planName": "计划名称",
354
+ "planDescription": "计划描述",
355
+ "currentDate": "2026-03-27",
356
+ "tasks": [
357
+ {
358
+ "taskId": "TASK_001",
359
+ "taskName": "任务名称",
360
+ "description": "任务描述",
361
+ "modelName": "数据模型名称",
362
+ "modelCode": "数据模型代码",
363
+ "filters": {
364
+ "type": "condition",
365
+ "field": "筛选字段",
366
+ "operator": "=",
367
+ "value": "筛选值"
368
+ },
369
+ "outputFormat": "表格/卡片/列表",
370
+ "outputFields": ["字段1", "字段2", "..."]
371
+ }
372
+ ],
373
+ "summary": {
374
+ "totalTasks": 5,
375
+ "keyMilestones": ["任务1", "任务2", "..."]
376
+ }
377
+ }
378
+ ```
379
+
380
+ # 输出要求
381
+
382
+ 只输出 JSON,不要有其他内容。
383
+ ```
384
+
385
+ ### 步骤 5:保存分析任务列表
386
+
387
+ ```javascript
388
+ const analysisPlan = JSON.parse(llmResponse);
389
+ writeFileSync('analysisPlan.json', JSON.stringify(analysisPlan, null, 2));
390
+ console.log(`分析任务列表已保存,共 ${analysisPlan.tasks.length} 个任务`);
391
+ ```
392
+
393
+ ### 步骤 6:执行分析任务
394
+
395
+ ```javascript
396
+ import { getModelData, getModelColumns } from '/Users/fudebao/.claude/skills/byteplan-api/scripts/api.js';
397
+
398
+ const plan = JSON.parse(readFileSync('analysisPlan.json'));
399
+ const results = [];
400
+
401
+ // 依次执行每个任务
402
+ for (const task of plan.tasks) {
403
+ console.log(`\n▶ 执行任务: ${task.taskName}`);
404
+
405
+ try {
406
+ const params = {
407
+ pageNum: 1,
408
+ pageSize: 1000
409
+ };
410
+
411
+ // 添加筛选条件
412
+ if (task.filters) {
413
+ params.customQuery = task.filters;
414
+ }
415
+
416
+ const data = await getModelData(token, task.modelCode, params);
417
+
418
+ results.push({
419
+ taskId: task.taskId,
420
+ taskName: task.taskName,
421
+ description: task.description,
422
+ modelName: task.modelName,
423
+ data: data.data || [],
424
+ total: data.total || data.data?.length || 0
425
+ });
426
+
427
+ console.log(` ✅ 获取 ${data.data?.length || 0} 条数据`);
428
+ } catch (error) {
429
+ console.log(` ❌ 查询失败: ${error.message}`);
430
+ results.push({
431
+ taskId: task.taskId,
432
+ taskName: task.taskName,
433
+ description: task.description,
434
+ modelName: task.modelName,
435
+ data: [],
436
+ error: error.message
437
+ });
438
+ }
439
+ }
440
+ ```
441
+
442
+ ### 步骤 7:生成 Markdown 格式数据
443
+
444
+ ```javascript
445
+ import { writeFileSync } from 'fs';
446
+
447
+ function generateMarkdownReport(plan, results) {
448
+ let md = `# ${plan.planName}\n\n`;
449
+ md += `> 分析主题: ${plan.planDescription}\n`;
450
+ md += `> 生成时间: ${new Date().toLocaleString('zh-CN')}\n\n`;
451
+
452
+ md += `---\n\n`;
453
+
454
+ // 执行摘要
455
+ md += `## 📊 执行摘要\n\n`;
456
+ md += `| 指标 | 数值 |\n`;
457
+ md += `|------|------|\n`;
458
+ md += `| 总任务数 | ${plan.tasks.length} |\n`;
459
+ md += `| 成功 | ${results.filter(r => !r.error).length} |\n`;
460
+ md += `| 失败 | ${results.filter(r => r.error).length} |\n\n`;
461
+
462
+ // 任务详情
463
+ md += `## 📋 任务详情\n\n`;
464
+
465
+ for (const result of results) {
466
+ md += `### ${result.taskId}: ${result.taskName}\n\n`;
467
+ md += `**描述**: ${result.description}\n`;
468
+ md += `**数据模型**: ${result.modelName}\n\n`;
469
+
470
+ if (result.error) {
471
+ md += `❌ **查询失败**: ${result.error}\n\n`;
472
+ continue;
473
+ }
474
+
475
+ md += `✅ 获取数据 **${result.total}** 条\n\n`;
476
+
477
+ // 如果有数据,生成表格
478
+ if (result.data && result.data.length > 0) {
479
+ const firstItem = result.data[0];
480
+ const fields = Object.keys(firstItem);
481
+
482
+ md += `| ${fields.join(' | ') } |\n`;
483
+ md += `| ${fields.map(() => '---').join(' | ') } |\n`;
484
+
485
+ const maxRows = Math.min(result.data.length, 20); // 最多显示20行
486
+ for (let i = 0; i < maxRows; i++) {
487
+ const row = result.data[i];
488
+ md += `| ${fields.map(f => {
489
+ let val = row[f];
490
+ if (typeof val === 'object' && val !== null) {
491
+ val = val.name || val.code || JSON.stringify(val);
492
+ }
493
+ return String(val || '').replace(/\n/g, ' ').substring(0, 50);
494
+ }).join(' | ')} |\n`;
495
+ }
496
+
497
+ if (result.data.length > maxRows) {
498
+ md += `\n*... 共 ${result.data.length} 条数据*\n`;
499
+ }
500
+ }
501
+
502
+ md += `\n---\n\n`;
503
+ }
504
+
505
+ // 数据洞察(基于数据结构)
506
+ md += `## 💡 数据洞察\n\n`;
507
+ md += `> 基于查询结果的数据特征分析\n\n`;
508
+
509
+ for (const result of results) {
510
+ if (result.data && result.data.length > 0) {
511
+ const firstItem = result.data[0];
512
+
513
+ // 统计数值字段
514
+ const numericFields = Object.keys(firstItem).filter(f => {
515
+ const val = firstItem[f];
516
+ return typeof val === 'number';
517
+ });
518
+
519
+ if (numericFields.length > 0) {
520
+ md += `### ${result.taskName} - 数值统计\n\n`;
521
+ md += `| 字段 | 类型 | 说明 |\n`;
522
+ md += `|------|------|------|\n`;
523
+ numericFields.slice(0, 5).forEach(f => {
524
+ md += `| ${f} | 数值 | ${result.taskName}相关指标 |\n`;
525
+ });
526
+ md += `\n`;
527
+ }
528
+ }
529
+ }
530
+
531
+ // 后续建议(基于数据分析)
532
+ // 注意:此处只是占位,实际建议内容由步骤 7.5 的 LLM 生成后追加
533
+ md += `## 🎯 后续建议\n\n`;
534
+ md += `> 基于以上数据分析,以下是建议的后续行动\n\n`;
535
+ md += `> (后续建议将在 LLM 分析后追加,详见步骤 7.5)\n\n`;
536
+
537
+ // 数据来源
538
+ md += `## 📚 数据来源\n\n`;
539
+ md += `- 租户: ${currentTenant}\n`;
540
+ md += `- 数据模型: ${results.map(r => r.modelName).join(', ')}\n`;
541
+ md += `- 查询时间: ${new Date().toISOString()}\n`;
542
+
543
+ return md;
544
+ }
545
+
546
+ const markdown = generateMarkdownReport(plan, results);
547
+ writeFileSync('analysis_report.md', markdown);
548
+ console.log('\n✅ Markdown 报告已生成: analysis_report.md');
549
+ ```
550
+
551
+ ### 步骤 7.5:生成后续建议(LLM 分析)
552
+
553
+ 在生成 Markdown 报告后,需要调用 LLM 根据数据分析结果生成后续建议。
554
+
555
+ #### LLM Prompt 模板
556
+
557
+ ```
558
+ # 角色
559
+ 你是一个企业级数据分析顾问(Analysis Consultant)。
560
+
561
+ # 任务
562
+ 根据以下数据分析结果,生成后续行动建议。
563
+
564
+ # 分析主题
565
+ {plan_name}: {plan_description}
566
+
567
+ # 数据分析结果
568
+ {analysis_results_json}
569
+
570
+ # 你必须遵守的规则
571
+
572
+ 1. 建议必须基于实际数据,不得臆造
573
+ 2. 建议要具体、可执行,避免泛泛而谈
574
+ 3. 如果发现数据异常,明确指出并给出排查建议
575
+ 4. 建议要分优先级,区分"立即行动"和"后续关注"
576
+ 5. 如果数据不足以得出结论,明确说明需要补充的数据
577
+
578
+ # 建议结构
579
+
580
+ 请按以下结构输出建议(Markdown 格式):
581
+
582
+ ## 🔴 需要立即关注的问题
583
+ - 列出数据中发现的问题或异常
584
+ - 每个问题给出可能的原因和影响
585
+
586
+ ## 🟡 建议深入分析的方向
587
+ - 列出值得进一步探索的数据维度
588
+ - 说明每个方向的分析价值
589
+
590
+ ## 🟢 可执行的业务建议
591
+ - 给出具体的行动建议
592
+ - 说明预期效果和实施要点
593
+
594
+ ## 📋 后续数据收集建议
595
+ - 如需补充数据,说明需要收集的数据
596
+ - 说明数据用途和收集方式
597
+
598
+ # 输出要求
599
+
600
+ 只输出 Markdown 格式的建议内容,不要有其他内容。
601
+ ```
602
+
603
+ #### 实现代码
604
+
605
+ ```javascript
606
+ import { writeFileSync, readFileSync } from 'fs';
607
+
608
+ async function generateFollowUpSuggestions(plan, results) {
609
+ // 准备数据摘要(避免数据量过大)
610
+ const dataSummary = results.map(r => ({
611
+ taskId: r.taskId,
612
+ taskName: r.taskName,
613
+ description: r.description,
614
+ modelName: r.modelName,
615
+ totalRecords: r.total,
616
+ hasError: !!r.error,
617
+ sampleData: r.data?.slice(0, 5) || [], // 只取前5条作为样本
618
+ numericStats: extractNumericStats(r.data)
619
+ }));
620
+
621
+ // 调用 LLM 生成建议
622
+ const prompt = `
623
+ # 角色
624
+ 你是一个企业级数据分析顾问(Analysis Consultant)。
625
+
626
+ # 任务
627
+ 根据以下数据分析结果,生成后续行动建议。
628
+
629
+ # 分析主题
630
+ ${plan.planName}: ${plan.planDescription}
631
+
632
+ # 数据分析结果
633
+ \`\`\`json
634
+ ${JSON.stringify(dataSummary, null, 2)}
635
+ \`\`\`
636
+
637
+ # 你必须遵守的规则
638
+
639
+ 1. 建议必须基于实际数据,不得臆造
640
+ 2. 建议要具体、可执行,避免泛泛而谈
641
+ 3. 如果发现数据异常,明确指出并给出排查建议
642
+ 4. 建议要分优先级,区分"立即行动"和"后续关注"
643
+ 5. 如果数据不足以得出结论,明确说明需要补充的数据
644
+
645
+ # 建议结构
646
+
647
+ 请按以下结构输出建议(Markdown 格式):
648
+
649
+ ## 🔴 需要立即关注的问题
650
+ - 列出数据中发现的问题或异常
651
+ - 每个问题给出可能的原因和影响
652
+
653
+ ## 🟡 建议深入分析的方向
654
+ - 列出值得进一步探索的数据维度
655
+ - 说明每个方向的分析价值
656
+
657
+ ## 🟢 可执行的业务建议
658
+ - 给出具体的行动建议
659
+ - 说明预期效果和实施要点
660
+
661
+ ## 📋 后续数据收集建议
662
+ - 如需补充数据,说明需要收集的数据
663
+ - 说明数据用途和收集方式
664
+
665
+ # 输出要求
666
+
667
+ 只输出 Markdown 格式的建议内容,不要有其他内容。
668
+ `;
669
+
670
+ // 这里调用 Claude 或其他 LLM API
671
+ // const suggestions = await callLLM(prompt);
672
+ // return suggestions;
673
+
674
+ // 临时返回示例结构(实际应调用 LLM)
675
+ return generateDefaultSuggestions(results);
676
+ }
677
+
678
+ // 提取数值字段的统计信息
679
+ function extractNumericStats(data) {
680
+ if (!data || data.length === 0) return {};
681
+
682
+ const numericFields = {};
683
+ const firstItem = data[0];
684
+
685
+ for (const field of Object.keys(firstItem)) {
686
+ if (typeof firstItem[field] === 'number') {
687
+ const values = data.map(d => d[field]).filter(v => typeof v === 'number');
688
+ if (values.length > 0) {
689
+ numericFields[field] = {
690
+ min: Math.min(...values),
691
+ max: Math.max(...values),
692
+ avg: values.reduce((a, b) => a + b, 0) / values.length,
693
+ count: values.length
694
+ };
695
+ }
696
+ }
697
+ }
698
+
699
+ return numericFields;
700
+ }
701
+
702
+ // 生成默认建议结构(当 LLM 不可用时)
703
+ function generateDefaultSuggestions(results) {
704
+ let suggestions = `## 🔴 需要立即关注的问题\n\n`;
705
+ suggestions += `> 请根据实际数据分析结果填写\n\n`;
706
+
707
+ // 检查是否有查询失败的任务
708
+ const failedTasks = results.filter(r => r.error);
709
+ if (failedTasks.length > 0) {
710
+ suggestions += `- **数据查询异常**: ${failedTasks.length} 个任务查询失败,建议检查数据源连接\n`;
711
+ }
712
+
713
+ suggestions += `\n## 🟡 建议深入分析的方向\n\n`;
714
+ suggestions += `> 请根据分析主题和数据特征填写\n\n`;
715
+ suggestions += `- 建议进一步分析数据的趋势变化\n`;
716
+ suggestions += `- 建议对比不同维度的数据分布\n`;
717
+
718
+ suggestions += `\n## 🟢 可执行的业务建议\n\n`;
719
+ suggestions += `> 请根据数据分析结论填写\n\n`;
720
+ suggestions += `- 建议定期更新数据,保持分析的时效性\n`;
721
+ suggestions += `- 建议建立数据监控机制,及时发现异常\n`;
722
+
723
+ suggestions += `\n## 📋 后续数据收集建议\n\n`;
724
+ suggestions += `> 请根据分析缺口填写\n\n`;
725
+ suggestions += `- 如需更全面的分析,建议补充历史数据\n`;
726
+
727
+ return suggestions;
728
+ }
729
+
730
+ // 更新报告,追加后续建议
731
+ function appendSuggestionsToReport(reportPath, suggestions) {
732
+ const originalContent = readFileSync(reportPath, 'utf-8');
733
+ const lines = originalContent.split('\n');
734
+
735
+ // 找到"数据来源"章节的位置
736
+ const sourceIndex = lines.findIndex(line => line.includes('## 📚 数据来源'));
737
+
738
+ if (sourceIndex !== -1) {
739
+ // 在数据来源之前插入后续建议
740
+ lines.splice(sourceIndex, 0, suggestions + '\n');
741
+ } else {
742
+ // 追加到末尾
743
+ lines.push('\n' + suggestions);
744
+ }
745
+
746
+ writeFileSync(reportPath, lines.join('\n'));
747
+ }
748
+
749
+ // 使用示例
750
+ const suggestions = await generateFollowUpSuggestions(plan, results);
751
+ appendSuggestionsToReport('analysis_report.md', suggestions);
752
+ console.log('✅ 后续建议已追加到报告');
753
+ ```
754
+
755
+ #### 建议内容要求
756
+
757
+ 生成的后续建议应包含以下维度:
758
+
759
+ | 建议类型 | 说明 | 示例 |
760
+ |----------|------|------|
761
+ | 🔴 立即关注 | 数据异常、风险点、紧急问题 | 发现资产折旧异常、数据缺失严重 |
762
+ | 🟡 深入分析 | 值得进一步探索的方向 | 细分品类分析、时间趋势对比 |
763
+ | 🟢 业务建议 | 可执行的行动建议 | 优化采购策略、调整折旧政策 |
764
+ | 📋 数据收集 | 需要补充的数据 | 历史数据、外部对标数据 |
765
+
766
+ ### ⚠️ 步骤 8:询问用户是否生成报告文档(工作流必须执行)
767
+
768
+ **🔴 这是工作流的必要环节,必须执行,不能跳过!**
769
+
770
+ **🔴 不能等用户主动要求,分析完成后必须立即主动询问!**
771
+
772
+ 分析数据生成完成后,**立即主动询问用户下一步操作**,输出类似以下内容:
773
+
774
+ ```
775
+ ✅ 分析完成!
776
+ 工作目录: ~/.byteplan/workspaces/边际分析_20260331_230800/
777
+ - analysis_report.md 已生成(含后续建议)
778
+ - analysisPlan.json 已保存
779
+
780
+ 请问是否需要生成其他格式的报告?
781
+ - Excel 报告(结构化数据表格)
782
+ - PPT 报告(演示文稿格式)
783
+ - Word 报告(文档格式)
784
+ - 网页报告(支持演示模式)
785
+ - 视频报告(数据可视化视频)
786
+ ```
787
+
788
+ #### 提供的选项
789
+
790
+ | 选项 | 说明 | 执行动作 |
791
+ |------|------|----------|
792
+ | 生成 Excel 报告 | 结构化数据表格,多 Sheet | 调用 [byteplan-excel](../byteplan-excel/SKILL.md) |
793
+ | 生成 PPT 报告 | PowerPoint 演示格式 | 调用 [byteplan-ppt](../byteplan-ppt/SKILL.md) |
794
+ | 生成 Word 报告 | Word 文档格式 | 调用 [byteplan-word](../byteplan-word/SKILL.md) |
795
+ | 生成网页报告 | 支持演示模式 | 调用 [byteplan-html](../byteplan-html/SKILL.md) |
796
+ | 生成视频报告 | 数据可视化视频 | 调用 [byteplan-video](../byteplan-video/SKILL.md) |
797
+ | 查看 Markdown | 直接查看已生成的 md 文件 | 展示文件内容 |
798
+ | 不需要 | 结束本次分析 | 结束对话 |
799
+
800
+ #### 用户选择后自动执行
801
+
802
+ 根据用户选择,**直接调用对应的 skill**:
803
+
804
+ - 用户选择 Excel → 执行 byteplan-excel skill
805
+ - 用户选择 PPT → 执行 byteplan-ppt skill
806
+ - 用户选择 Word → 执行 byteplan-word skill
807
+ - 用户选择网页 → 执行 byteplan-html skill
808
+ - 用户选择 Video → 执行 byteplan-video skill
809
+ - 用户选择 Markdown → 展示 analysis_report.md 内容
810
+
811
+ **注意**:
812
+ 1. **不要只是告诉用户"请使用 xxx skill"**,而是直接执行对应的 skill
813
+ 2. **不能等用户主动要求**,分析完成后必须立即主动询问
814
+ 3. 所有生成的报告文件必须输出到**同一工作目录**
815
+
816
+ ## API 参考
817
+
818
+ | 函数 | 描述 |
819
+ |------|------|
820
+ | `setEnvironment(env)` | 设置登录环境(仅支持 'uat') |
821
+ | `getEnvironment()` | 获取当前环境 |
822
+ | `login(username, password, env?)` | 使用凭证登录,token 自动缓存到 ~/.byteplan/.env |
823
+ | `loginWithEnv(env?, forceReLogin?)` | 使用 ~/.byteplan/.env 凭证登录,默认优先使用缓存 token |
824
+ | `getToken()` | 获取当前缓存的 access_token |
825
+ | `getUserInfo(token)` | 获取用户和租户信息 |
826
+ | `queryModels(token, options?)` | 列出可用模型 |
827
+ | `getModelData(token, modelCode, params)` | 查询模型数据 |
828
+ | `getModelColumns(token, modelCodes)` | 获取模型字段定义 |
829
+
830
+ ### loginWithEnv 参数说明
831
+
832
+ | 参数 | 类型 | 默认值 | 说明 |
833
+ |------|------|--------|------|
834
+ | `env` | 'uat' | 'uat' | 登录环境 |
835
+ | `forceReLogin` | boolean | false | 是否强制重新登录(忽略缓存) |
836
+
837
+ 返回值中 `_cached: true` 表示使用了缓存的 token。
838
+
839
+ ## 查询语法
840
+
841
+ ### 条件结构
842
+
843
+ ```javascript
844
+ {
845
+ type: 'condition',
846
+ field: '字段名',
847
+ operator: '=', // 支持: =, !=, >, <, >=, <=, LIKE, IN, BETWEEN
848
+ value: '值'
849
+ }
850
+ ```
851
+
852
+ ### 条件组结构(多条件)
853
+
854
+ ```javascript
855
+ {
856
+ type: 'group',
857
+ logic: 'AND', // AND 或 OR
858
+ children: [
859
+ { type: 'condition', field: '字段1', operator: '=', value: '值1' },
860
+ { type: 'condition', field: '字段2', operator: '>', value: '值2' }
861
+ ]
862
+ }
863
+ ```
864
+
865
+ ## 完整示例
866
+
867
+ 用户请求:"分析计算机类实物资产的折旧趋势"
868
+
869
+ ### 1. 创建工作目录
870
+
871
+ ```javascript
872
+ import { homedir } from 'os';
873
+ import path from 'path';
874
+ import { mkdirSync } from 'fs';
875
+
876
+ const BYTEPLAN_DIR = path.join(homedir(), '.byteplan');
877
+ const WORKSPACES_DIR = path.join(BYTEPLAN_DIR, 'workspaces');
878
+ const workspaceName = '计算机资产折旧_20260331_224600';
879
+ const workspacePath = path.join(WORKSPACES_DIR, workspaceName);
880
+
881
+ mkdirSync(workspacePath, { recursive: true });
882
+ process.chdir(workspacePath);
883
+ console.log(`✅ 工作目录: ${workspacePath}`);
884
+ ```
885
+
886
+ ### 2. 登录并获取模型
887
+
888
+ ```javascript
889
+ const models = await queryModels(token);
890
+ const modelList = models.map(m => ({ name: m.modelName, code: m.modelCode }));
891
+ ```
892
+
893
+ ### 3. 调用 LLM 生成任务列表
894
+
895
+ 将模型列表和分析主题发送给 LLM:
896
+
897
+ ```
898
+ 分析主题:分析计算机类实物资产的折旧趋势
899
+
900
+ 可用模型列表:
901
+ [
902
+ {"name": "资产基础信息表", "code": "CASMN_ASSET_BASE_INFO"},
903
+ {"name": "资产明细表", "code": "CASMN_ASSET_INFO_DETAIL"},
904
+ {"name": "资产类别", "code": "CASMN_ASSET_CATEGORY"},
905
+ {"name": "已到期未报废资产明细", "code": "CASMN_ASSET_EXPIRED_NOT_SCRAPPED"}
906
+ ]
907
+ ```
908
+
909
+ ### 4. 保存并执行任务
910
+
911
+ ```javascript
912
+ const plan = JSON.parse(llmResponse);
913
+ writeFileSync('analysisPlan.json', JSON.stringify(plan, null, 2));
914
+
915
+ // 执行每个任务...
916
+ const results = [];
917
+ for (const task of plan.tasks) {
918
+ const data = await getModelData(token, task.modelCode, { pageNum: 1, pageSize: 1000 });
919
+ results.push({ ...task, data: data.data });
920
+ }
921
+ ```
922
+
923
+ ### 5. 生成 Markdown 报告
924
+
925
+ ```javascript
926
+ const markdown = generateMarkdownReport(plan, results);
927
+ writeFileSync('analysis_report.md', markdown);
928
+ ```
929
+
930
+ ### 6. 生成后续建议
931
+
932
+ ```javascript
933
+ const suggestions = await generateFollowUpSuggestions(plan, results);
934
+ appendSuggestionsToReport('analysis_report.md', suggestions);
935
+ console.log('✅ 后续建议已追加到报告');
936
+ ```
937
+
938
+ ### 7. 询问用户下一步操作(工作流必须执行)
939
+
940
+ 分析完成后,**立即询问用户下一步操作**:
941
+
942
+ ```
943
+ ✅ 分析完成!
944
+ 工作目录: ~/.byteplan/workspaces/计算机资产折旧_20260331_224600/
945
+ - analysis_report.md 已生成(含后续建议)
946
+ - analysisPlan.json 已保存
947
+
948
+ 请问是否需要生成其他格式的报告?
949
+ - 网页报告(支持演示模式)
950
+ - PPT 报告(演示格式)
951
+ - Word 报告(文档格式)
952
+ - 仅查看 Markdown
953
+ ```
954
+
955
+ 根据用户选择直接调用对应 skill:
956
+ - **网页** → 执行 byteplan-html
957
+ - **PPT** → 执行 byteplan-ppt
958
+ - **Word** → 执行 byteplan-word
959
+ - **Markdown** → 展示 analysis_report.md 内容
960
+
961
+ ## 输出文件
962
+
963
+ | 文件 | 描述 | 存储位置 | 必须 |
964
+ |------|------|----------|------|
965
+ | `analysisPlan.json` | 分析任务列表 JSON | 工作目录 | ✅ |
966
+ | `analysis_report.md` | Markdown 格式的数据报告(含后续建议) | 工作目录 | ✅ |
967
+ | `report_data.json` | 报告数据文件(供下游 Excel/PPT/Word/HTML skill 消费) | 工作目录 | ✅ |
968
+
969
+ ### report_data.json 规范
970
+
971
+ **⚠️ 此文件是下游报告 skill(html/ppt/word/excel)的标准输入,必须生成!**
972
+
973
+ 分析完成后,必须根据查询结果生成 `report_data.json`,格式如下:
974
+
975
+ ```json
976
+ {
977
+ "title": "报告标题",
978
+ "subtitle": "副标题",
979
+ "period": "2026年Q1",
980
+ "analysisGoal": "分析目标关键词",
981
+ "sections": ["章节1", "章节2"],
982
+ "summary": [
983
+ { "rank": "🥇 第一名", "name": "项目名", "type": "类型", "value": "¥1,000万", "rate": "利润率 90%" }
984
+ ],
985
+ "kpis": [
986
+ { "icon": "💰", "label": "指标名", "value": "数值", "unit": "单位" }
987
+ ],
988
+ "charts": [
989
+ {
990
+ "title": "图表标题",
991
+ "type": "bar|line|pie|horizontalBar",
992
+ "data": [{ "name": "分类名", "value": 100 }]
993
+ }
994
+ ],
995
+ "tableData": {
996
+ "title": "明细表标题",
997
+ "columns": ["列1", "列2"],
998
+ "rows": [["值1", "值2"]]
999
+ },
1000
+ "insights": [
1001
+ { "title": "洞察标题", "content": "洞察内容", "warning": false }
1002
+ ],
1003
+ "recommendations": [
1004
+ { "title": "建议标题", "content": "建议内容" }
1005
+ ],
1006
+ "source": "BytePlan 数据平台"
1007
+ }
1008
+ ```
1009
+
1010
+ **关键规则**:
1011
+ - `charts` 数组中每个图表的 `type` 可省略,下游脚本会根据标题关键词自动选择
1012
+ - `summary` 最多 3 条,`kpis` 最多 4 个,`insights` 和 `recommendations` 各最多 4 条
1013
+ - 所有数值型字段使用数字类型(非字符串),方便图表渲染
1014
+ - 文件必须保存在工作目录根路径下(与其他输出文件同级)
1015
+
1016
+ **输出文件位置**:所有分析结果文件保存在当前工作目录 `~/.byteplan/workspaces/{主题}_{时间戳}/`
1017
+
1018
+ ## ⚠️ 重要规则:文件必须放在独立工作目录
1019
+
1020
+ **这是本 skill 最核心的规则,必须严格遵守!**
1021
+
1022
+ ### 所有输出文件的位置
1023
+
1024
+ ```
1025
+ ~/.byteplan/workspaces/{主题}_{时间戳}/
1026
+ ├── analysisPlan.json ← 分析任务列表(必须)
1027
+ ├── analysis_report.md ← Markdown 报告(必须)
1028
+ ├── 边际分析报告.xlsx ← Excel 报告(可选)
1029
+ ├── 边际分析报告.pptx ← PPT 报告(可选)
1030
+ ├── 边际分析报告.docx ← Word 报告(可选)
1031
+ └── raw_data/ ← 原始数据(可选)
1032
+ ```
1033
+
1034
+ ### ❌ 错误做法
1035
+
1036
+ | 错误位置 | 说明 |
1037
+ |----------|------|
1038
+ | `~/.openclaw/workspace/` | ❌ 不能放在 OpenClaw workspace 根目录 |
1039
+ | `~/.byteplan/` | ❌ 不能放在 byteplan 根目录 |
1040
+ | `/tmp/` | ❌ 不能放在临时目录 |
1041
+ | 当前目录 | ❌ 不能放在任意当前工作目录 |
1042
+
1043
+ ### ✅ 正确做法
1044
+
1045
+ 1. **第一步就是创建工作目录**:`~/.byteplan/workspaces/{主题}_{时间戳}/`
1046
+ 2. **切换到该目录**:`process.chdir(workspacePath)`
1047
+ 3. **所有后续文件操作都在此目录下**
1048
+ 4. **生成的 Excel/PPT/Word 也必须放在同一目录**
1049
+
1050
+ ### 目录切换代码示例
1051
+
1052
+ ```javascript
1053
+ // 创建并切换到工作目录(这是第一步!)
1054
+ const workspacePath = path.join(WORKSPACES_DIR, workspaceName);
1055
+ mkdirSync(workspacePath, { recursive: true });
1056
+ process.chdir(workspacePath); // ← 关键:切换当前工作目录
1057
+
1058
+ // 之后所有 writeFileSync 都会写入此目录
1059
+ writeFileSync('analysisPlan.json', ...); // → workspacePath/analysisPlan.json
1060
+ writeFileSync('analysis_report.md', ...); // → workspacePath/analysis_report.md
1061
+ writeFileSync('边际分析报告.xlsx', ...); // → workspacePath/边际分析报告.xlsx
1062
+ ```
1063
+
1064
+ ---
1065
+
1066
+ ## 注意事项
1067
+
1068
+ - **⚠️ 工作目录隔离(最重要)**:所有文件必须放在 `~/.byteplan/workspaces/{主题}_{时间戳}/`,绝不能放在其他位置
1069
+ - **🔴 分析后必须询问(最重要)**:分析完成后,**必须立即主动询问**用户是否需要生成 Excel/PPT/Word/网页/视频,**不能等用户主动要求**
1070
+ - **重新分析处理**:用户说"重新分析"时创建新目录,保留历史分析记录
1071
+ - **登录状态检查(重要)**:优先使用 `loginWithEnv(env, false)` 检查 `~/.byteplan/.env` 中缓存的 token,避免每次重新登录
1072
+ - **首次使用时**:如果没有 `~/.byteplan/.env` 文件或缺少凭证,必须询问用户输入账号密码
1073
+ - **登录成功后**:必须调用 `getUserInfo` 获取并显示当前租户信息
1074
+ - **生成任务列表**:必须使用 LLM 生成,不要自行编造
1075
+ - **执行任务时**:严格按照任务列表中的定义执行查询
1076
+ - **数据分析**:只输出数据结构,不做计算和结论
1077
+ - **生成后续建议**:必须使用 LLM 根据实际数据生成,建议要具体可执行
1078
+ - Token 会自动缓存到 `~/.byteplan/.env` 文件,有效期通常为 24 小时(提前 5 分钟判定过期)