tickflow-assist 0.3.3 → 0.3.5
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 +29 -5
- package/dist/background/daily-update.worker.d.ts +7 -1
- package/dist/background/daily-update.worker.js +127 -3
- package/dist/bootstrap.js +3 -1
- package/dist/config/env.d.ts +17 -0
- package/dist/config/env.js +30 -0
- package/dist/config/normalize.js +25 -12
- package/dist/dev/tickflow-assist-cli.js +83 -17
- package/dist/prompts/analysis/index.d.ts +1 -0
- package/dist/prompts/analysis/index.js +1 -0
- package/dist/prompts/analysis/pre-market-brief-prompt.d.ts +14 -0
- package/dist/prompts/analysis/pre-market-brief-prompt.js +49 -0
- package/dist/services/alert-service.js +3 -0
- package/dist/services/analysis-service.js +4 -3
- package/dist/services/jin10-flash-monitor-service.js +17 -2
- package/dist/services/jin10-mcp-service.d.ts +4 -0
- package/dist/services/jin10-mcp-service.js +60 -4
- package/dist/services/monitor-service.d.ts +3 -0
- package/dist/services/monitor-service.js +78 -13
- package/dist/services/mx-search-service.js +3 -2
- package/dist/services/pre-market-brief-service.d.ts +21 -0
- package/dist/services/pre-market-brief-service.js +289 -0
- package/dist/services/trading-calendar-service.d.ts +4 -0
- package/dist/services/trading-calendar-service.js +11 -0
- package/dist/storage/repositories/jin10-flash-repo.d.ts +1 -0
- package/dist/storage/repositories/jin10-flash-repo.js +23 -1
- package/dist/types/daily-update.d.ts +7 -0
- package/openclaw.plugin.json +51 -23
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -2,9 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
基于 [OpenClaw](https://openclaw.ai) 的 A 股监控与分析插件。它使用 [TickFlow](https://tickflow.org/auth/register?ref=BUJ54JEDGE) 获取行情与财务数据,并可选接入 [金十数据 MCP](https://mcp.jin10.com/app/) 快讯流,结合 LLM 生成技术面、基本面、资讯面的综合判断,并把结果持久化到本地 LanceDB。
|
|
4
4
|
|
|
5
|
-
最近更新:`v0.3.
|
|
5
|
+
最近更新:`v0.3.5` 对齐 OpenClaw `2026.4.11` metadata 与社区安装提示,修复源码升级时本地链接扫描 `node_modules` 失败的问题,并支持通过环境变量回退 TickFlow / LLM / MX / Jin10 配置。完整发布记录见 <https://github.com/robinspt/tickflow-assist/blob/main/CHANGELOG.md>。
|
|
6
6
|
|
|
7
|
-
当前主线按 OpenClaw `v2026.3.31+` 对齐,并已验证社区安装在 `v2026.4.
|
|
7
|
+
当前主线按 OpenClaw `v2026.3.31+` 对齐,并已验证社区安装在 `v2026.4.11` 上兼容。
|
|
8
|
+
|
|
9
|
+
## 安装前准备
|
|
10
|
+
|
|
11
|
+
在执行社区安装前,建议先确认你已经准备好以下配置:
|
|
12
|
+
|
|
13
|
+
- 核心必需:`tickflowApiKey`、`llmApiKey`、`llmBaseUrl`、`llmModel`
|
|
14
|
+
- 告警投递:`alertChannel`、`alertTarget`、`alertAccount`
|
|
15
|
+
- 可选增强:`mxSearchApiKey`、`jin10ApiToken`
|
|
16
|
+
|
|
17
|
+
其中,`configure-openclaw` 会把上述配置写入 `~/.openclaw/openclaw.json` 的 `plugins.entries["tickflow-assist"].config`,插件启用后会在本地 `databasePath` 下持久化 LanceDB 数据,并运行监控 / 日更等后台服务。
|
|
18
|
+
如果你不想把密钥写进配置文件,运行时也支持环境变量回退,优先级是 `openclaw.json / local.config.json` > 环境变量 > 默认值。
|
|
19
|
+
常用环境变量:`TICKFLOW_ASSIST_TICKFLOW_API_KEY` / `TICKFLOW_API_KEY`、`TICKFLOW_ASSIST_LLM_API_KEY` / `LLM_API_KEY`、`TICKFLOW_ASSIST_LLM_BASE_URL` / `LLM_BASE_URL`、`TICKFLOW_ASSIST_LLM_MODEL` / `LLM_MODEL`、`TICKFLOW_ASSIST_MX_SEARCH_API_KEY` / `MX_SEARCH_API_KEY` / `MX_APIKEY`、`TICKFLOW_ASSIST_JIN10_API_TOKEN` / `JIN10_API_TOKEN`。
|
|
8
20
|
|
|
9
21
|
## 安装
|
|
10
22
|
|
|
@@ -19,8 +31,8 @@ openclaw config validate
|
|
|
19
31
|
openclaw gateway restart
|
|
20
32
|
```
|
|
21
33
|
|
|
22
|
-
安装阶段允许先落插件,再通过第二条命令写入 `tickflowApiKey`、`llmApiKey` 等正式配置。
|
|
23
|
-
`configure-openclaw` 会写入 `~/.openclaw/openclaw.json` 中的 `plugins.entries["tickflow-assist"].config`,并打印后续建议执行的命令;它不再自动执行 `openclaw`、`uv`
|
|
34
|
+
安装阶段允许先落插件,再通过第二条命令写入 `tickflowApiKey`、`llmApiKey`、`llmBaseUrl`、`llmModel` 等正式配置。
|
|
35
|
+
`configure-openclaw` 会写入 `~/.openclaw/openclaw.json` 中的 `plugins.entries["tickflow-assist"].config`,并打印后续建议执行的命令;它不再自动执行 `openclaw`、`uv` 或系统包安装命令,也不会重新执行插件安装;如果你已经设置了环境变量,密钥项可留空,输入 `-` 可主动清空已有配置并切回环境变量。
|
|
24
36
|
如果检测到 `plugins.installs["tickflow-assist"]` 来自 `clawhub`,向导还会把被旧版本钉死的 `spec` 归一化为 `clawhub:tickflow-assist`,避免后续升级继续锁在旧版本。
|
|
25
37
|
|
|
26
38
|
如果你希望先审阅配置,再只打印最少的后续步骤,可使用:
|
|
@@ -84,6 +96,18 @@ plugins.entries["tickflow-assist"].config
|
|
|
84
96
|
- 能力补充:`mxSearchApiKey`、`jin10ApiToken`
|
|
85
97
|
|
|
86
98
|
其中,`mxSearchApiKey` 用于 `mx_search`、`mx_select_stock` 以及非 `Expert` 财务链路的 lite 补充;`jin10ApiToken` 用于 24 小时金十数据快讯监控;`jin10FlashNightAlert` 默认 `false`(开启夜间静默),设为 `true` 可恢复 24 小时快讯告警;`alertTarget`、`alertAccount` 建议在准备启用 `test_alert`、实时监控告警、金十数据快讯告警和定时通知前一并配好,避免配置不完整导致功能缺失。
|
|
99
|
+
如果你使用环境变量,运行时支持以下回退:
|
|
100
|
+
|
|
101
|
+
- `tickflowApiUrl`:`TICKFLOW_ASSIST_TICKFLOW_API_URL` / `TICKFLOW_API_URL`
|
|
102
|
+
- `tickflowApiKey`:`TICKFLOW_ASSIST_TICKFLOW_API_KEY` / `TICKFLOW_API_KEY`
|
|
103
|
+
- `tickflowApiKeyLevel`:`TICKFLOW_ASSIST_TICKFLOW_API_KEY_LEVEL` / `TICKFLOW_API_KEY_LEVEL`
|
|
104
|
+
- `llmBaseUrl`:`TICKFLOW_ASSIST_LLM_BASE_URL` / `LLM_BASE_URL`
|
|
105
|
+
- `llmApiKey`:`TICKFLOW_ASSIST_LLM_API_KEY` / `LLM_API_KEY`
|
|
106
|
+
- `llmModel`:`TICKFLOW_ASSIST_LLM_MODEL` / `LLM_MODEL`
|
|
107
|
+
- `mxSearchApiUrl`:`TICKFLOW_ASSIST_MX_SEARCH_API_URL` / `MX_SEARCH_API_URL`
|
|
108
|
+
- `mxSearchApiKey`:`TICKFLOW_ASSIST_MX_SEARCH_API_KEY` / `MX_SEARCH_API_KEY` / `MX_APIKEY`
|
|
109
|
+
- `jin10McpUrl`:`TICKFLOW_ASSIST_JIN10_MCP_URL` / `JIN10_MCP_URL`
|
|
110
|
+
- `jin10ApiToken`:`TICKFLOW_ASSIST_JIN10_API_TOKEN` / `JIN10_API_TOKEN`
|
|
87
111
|
|
|
88
112
|
## 功能
|
|
89
113
|
|
|
@@ -107,4 +131,4 @@ plugins.entries["tickflow-assist"].config
|
|
|
107
131
|
|
|
108
132
|
## 仓库
|
|
109
133
|
|
|
110
|
-
- GitHub:
|
|
134
|
+
- GitHub: [robinspt/tickflow-assist](https://github.com/robinspt/tickflow-assist)
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { UpdateService } from "../services/update-service.js";
|
|
2
2
|
import { AlertService } from "../services/alert-service.js";
|
|
3
3
|
import { PostCloseReviewService } from "../services/post-close-review-service.js";
|
|
4
|
+
import { PreMarketBriefService } from "../services/pre-market-brief-service.js";
|
|
4
5
|
import { TradingCalendarService } from "../services/trading-calendar-service.js";
|
|
5
6
|
import type { DailyUpdateState } from "../types/daily-update.js";
|
|
6
7
|
export declare class DailyUpdateWorker {
|
|
7
8
|
private readonly updateService;
|
|
9
|
+
private readonly preMarketBriefService;
|
|
8
10
|
private readonly postCloseReviewService;
|
|
9
11
|
private readonly tradingCalendarService;
|
|
10
12
|
private readonly baseDir;
|
|
@@ -12,7 +14,7 @@ export declare class DailyUpdateWorker {
|
|
|
12
14
|
private readonly notifyEnabled;
|
|
13
15
|
private readonly configSource;
|
|
14
16
|
private readonly intervalMs;
|
|
15
|
-
constructor(updateService: UpdateService, postCloseReviewService: PostCloseReviewService | null, tradingCalendarService: TradingCalendarService, baseDir: string, alertService: AlertService, notifyEnabled: boolean, configSource: "openclaw_plugin" | "local_config", intervalMs?: number);
|
|
17
|
+
constructor(updateService: UpdateService, preMarketBriefService: PreMarketBriefService, postCloseReviewService: PostCloseReviewService | null, tradingCalendarService: TradingCalendarService, baseDir: string, alertService: AlertService, notifyEnabled: boolean, configSource: "openclaw_plugin" | "local_config", intervalMs?: number);
|
|
16
18
|
run(force?: boolean): Promise<string>;
|
|
17
19
|
runLoop(signal?: AbortSignal, runtimeHost?: "project_scheduler" | "plugin_service", runtimeConfigSource?: "openclaw_plugin" | "local_config"): Promise<void>;
|
|
18
20
|
stopLoop(): Promise<{
|
|
@@ -30,17 +32,21 @@ export declare class DailyUpdateWorker {
|
|
|
30
32
|
getState(): Promise<DailyUpdateState>;
|
|
31
33
|
getStatusReport(): Promise<string>;
|
|
32
34
|
private runScheduledPasses;
|
|
35
|
+
private runScheduledPreMarketBriefPass;
|
|
33
36
|
private runScheduledUpdatePass;
|
|
34
37
|
private runScheduledReviewPass;
|
|
35
38
|
private getScheduledReviewReadiness;
|
|
36
39
|
private recordReviewSkip;
|
|
40
|
+
private recordPreMarketSkip;
|
|
37
41
|
private executeDailyUpdateAndRecord;
|
|
38
42
|
private executeReviewAndRecord;
|
|
43
|
+
private executePreMarketBriefAndRecord;
|
|
39
44
|
private getStateFilePath;
|
|
40
45
|
private recordHeartbeat;
|
|
41
46
|
private readState;
|
|
42
47
|
private writeState;
|
|
43
48
|
private maybeSendDailyUpdateNotification;
|
|
49
|
+
private maybeSendPreMarketBriefNotification;
|
|
44
50
|
private maybeSendReviewNotification;
|
|
45
51
|
}
|
|
46
52
|
export declare function isPidAlive(pid: number): boolean;
|
|
@@ -2,6 +2,7 @@ import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { chinaToday, formatChinaDateTime } from "../utils/china-time.js";
|
|
4
4
|
import { sleepWithAbort } from "../utils/abortable-sleep.js";
|
|
5
|
+
const PRE_MARKET_BRIEF_READY_TIME = "09:20";
|
|
5
6
|
const DAILY_UPDATE_READY_TIME = "15:25";
|
|
6
7
|
const POST_CLOSE_REVIEW_READY_TIME = "20:00";
|
|
7
8
|
const DEFAULT_STATE = {
|
|
@@ -28,9 +29,17 @@ const DEFAULT_STATE = {
|
|
|
28
29
|
lastReviewResultType: null,
|
|
29
30
|
lastReviewResultSummary: null,
|
|
30
31
|
reviewConsecutiveFailures: 0,
|
|
32
|
+
lastPreMarketAttemptAt: null,
|
|
33
|
+
lastPreMarketAttemptDate: null,
|
|
34
|
+
lastPreMarketSuccessAt: null,
|
|
35
|
+
lastPreMarketSuccessDate: null,
|
|
36
|
+
lastPreMarketResultType: null,
|
|
37
|
+
lastPreMarketResultSummary: null,
|
|
38
|
+
preMarketConsecutiveFailures: 0,
|
|
31
39
|
};
|
|
32
40
|
export class DailyUpdateWorker {
|
|
33
41
|
updateService;
|
|
42
|
+
preMarketBriefService;
|
|
34
43
|
postCloseReviewService;
|
|
35
44
|
tradingCalendarService;
|
|
36
45
|
baseDir;
|
|
@@ -38,8 +47,9 @@ export class DailyUpdateWorker {
|
|
|
38
47
|
notifyEnabled;
|
|
39
48
|
configSource;
|
|
40
49
|
intervalMs;
|
|
41
|
-
constructor(updateService, postCloseReviewService, tradingCalendarService, baseDir, alertService, notifyEnabled, configSource, intervalMs = 15 * 60 * 1000) {
|
|
50
|
+
constructor(updateService, preMarketBriefService, postCloseReviewService, tradingCalendarService, baseDir, alertService, notifyEnabled, configSource, intervalMs = 15 * 60 * 1000) {
|
|
42
51
|
this.updateService = updateService;
|
|
52
|
+
this.preMarketBriefService = preMarketBriefService;
|
|
43
53
|
this.postCloseReviewService = postCloseReviewService;
|
|
44
54
|
this.tradingCalendarService = tradingCalendarService;
|
|
45
55
|
this.baseDir = baseDir;
|
|
@@ -150,19 +160,31 @@ export class DailyUpdateWorker {
|
|
|
150
160
|
const state = await this.readState();
|
|
151
161
|
const today = chinaToday();
|
|
152
162
|
const lines = [
|
|
153
|
-
"🕒 定时日更 / 收盘复盘状态",
|
|
163
|
+
"🕒 盘前资讯 / 定时日更 / 收盘复盘状态",
|
|
154
164
|
`状态: ${formatProcessState(state)}`,
|
|
155
165
|
`运行方式: ${formatRuntimeHost(state)}`,
|
|
156
166
|
`配置来源: ${this.configSource}`,
|
|
157
|
-
`调度: ${Math.floor(this.intervalMs / 60_000)} 分钟对齐轮询 | 日更 ${DAILY_UPDATE_READY_TIME} 后执行 | 复盘 ${POST_CLOSE_REVIEW_READY_TIME} 后执行`,
|
|
167
|
+
`调度: ${Math.floor(this.intervalMs / 60_000)} 分钟对齐轮询 | 盘前资讯 ${PRE_MARKET_BRIEF_READY_TIME} 后执行 | 日更 ${DAILY_UPDATE_READY_TIME} 后执行 | 复盘 ${POST_CLOSE_REVIEW_READY_TIME} 后执行`,
|
|
158
168
|
`最近心跳: ${state.lastHeartbeatAt ?? "暂无"}`,
|
|
159
169
|
"",
|
|
170
|
+
"盘前资讯:",
|
|
171
|
+
`• 今日已推送: ${state.lastPreMarketSuccessDate === today ? "是" : "否"}`,
|
|
172
|
+
`• 最近尝试: ${state.lastPreMarketAttemptAt ?? "暂无"}`,
|
|
173
|
+
`• 最近成功: ${state.lastPreMarketSuccessAt ?? "暂无"}`,
|
|
174
|
+
`• 最近结果: ${formatResultType(state.lastPreMarketResultType)}`,
|
|
175
|
+
"",
|
|
160
176
|
"日更执行:",
|
|
161
177
|
`• 今日已更新: ${state.lastSuccessDate === today ? "是" : "否"}`,
|
|
162
178
|
`• 最近尝试: ${state.lastAttemptAt ?? "暂无"}`,
|
|
163
179
|
`• 最近成功: ${state.lastSuccessAt ?? "暂无"}`,
|
|
164
180
|
`• 最近结果: ${formatResultType(state.lastResultType)}`,
|
|
165
181
|
];
|
|
182
|
+
if (state.preMarketConsecutiveFailures > 0) {
|
|
183
|
+
lines.push(`• 连续失败: ${state.preMarketConsecutiveFailures}`);
|
|
184
|
+
}
|
|
185
|
+
if (state.lastPreMarketResultSummary) {
|
|
186
|
+
lines.push(`• 最近摘要: ${state.lastPreMarketResultSummary}`);
|
|
187
|
+
}
|
|
166
188
|
if (state.consecutiveFailures > 0) {
|
|
167
189
|
lines.push(`• 连续失败: ${state.consecutiveFailures}`);
|
|
168
190
|
}
|
|
@@ -179,9 +201,26 @@ export class DailyUpdateWorker {
|
|
|
179
201
|
return lines.join("\n");
|
|
180
202
|
}
|
|
181
203
|
async runScheduledPasses() {
|
|
204
|
+
await this.runScheduledPreMarketBriefPass();
|
|
182
205
|
await this.runScheduledUpdatePass();
|
|
183
206
|
await this.runScheduledReviewPass();
|
|
184
207
|
}
|
|
208
|
+
async runScheduledPreMarketBriefPass() {
|
|
209
|
+
const today = chinaToday();
|
|
210
|
+
const state = await this.readState();
|
|
211
|
+
if (hasCompletedScheduledWindow(state.lastPreMarketSuccessDate, state.lastPreMarketSuccessAt, today, PRE_MARKET_BRIEF_READY_TIME)) {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
if (hasAttemptedScheduledWindow(state.lastPreMarketAttemptDate, state.lastPreMarketAttemptAt, today, PRE_MARKET_BRIEF_READY_TIME)) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
const readiness = await this.tradingCalendarService.canRunPreMarketBrief();
|
|
218
|
+
if (!readiness.ok) {
|
|
219
|
+
await this.recordPreMarketSkip(state, today, readiness.reason);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
await this.executePreMarketBriefAndRecord("scheduled");
|
|
223
|
+
}
|
|
185
224
|
async runScheduledUpdatePass() {
|
|
186
225
|
const today = chinaToday();
|
|
187
226
|
const state = await this.readState();
|
|
@@ -245,6 +284,16 @@ export class DailyUpdateWorker {
|
|
|
245
284
|
reviewConsecutiveFailures: 0,
|
|
246
285
|
});
|
|
247
286
|
}
|
|
287
|
+
async recordPreMarketSkip(state, today, reason) {
|
|
288
|
+
await this.writeState({
|
|
289
|
+
...state,
|
|
290
|
+
lastPreMarketAttemptAt: formatChinaDateTime(),
|
|
291
|
+
lastPreMarketAttemptDate: today,
|
|
292
|
+
lastPreMarketResultType: "skipped",
|
|
293
|
+
lastPreMarketResultSummary: reason,
|
|
294
|
+
preMarketConsecutiveFailures: 0,
|
|
295
|
+
});
|
|
296
|
+
}
|
|
248
297
|
async executeDailyUpdateAndRecord(force, trigger, throwOnError) {
|
|
249
298
|
const today = chinaToday();
|
|
250
299
|
const state = await this.readState();
|
|
@@ -345,6 +394,52 @@ export class DailyUpdateWorker {
|
|
|
345
394
|
return output;
|
|
346
395
|
}
|
|
347
396
|
}
|
|
397
|
+
async executePreMarketBriefAndRecord(trigger) {
|
|
398
|
+
const today = chinaToday();
|
|
399
|
+
const state = await this.readState();
|
|
400
|
+
const attemptedAt = formatChinaDateTime();
|
|
401
|
+
try {
|
|
402
|
+
const result = await this.preMarketBriefService.run();
|
|
403
|
+
const output = createPreMarketBriefExecutionOutput(result);
|
|
404
|
+
const nextState = {
|
|
405
|
+
...state,
|
|
406
|
+
lastPreMarketAttemptAt: attemptedAt,
|
|
407
|
+
lastPreMarketAttemptDate: today,
|
|
408
|
+
lastPreMarketResultType: output.resultType,
|
|
409
|
+
lastPreMarketResultSummary: summarizePreMarketBriefResult(output.message),
|
|
410
|
+
preMarketConsecutiveFailures: output.resultType === "failed" ? state.preMarketConsecutiveFailures + 1 : 0,
|
|
411
|
+
};
|
|
412
|
+
if (output.resultType === "success") {
|
|
413
|
+
nextState.lastPreMarketSuccessAt = attemptedAt;
|
|
414
|
+
nextState.lastPreMarketSuccessDate = today;
|
|
415
|
+
}
|
|
416
|
+
await this.writeState(nextState);
|
|
417
|
+
if (trigger === "scheduled") {
|
|
418
|
+
await this.maybeSendPreMarketBriefNotification(output);
|
|
419
|
+
}
|
|
420
|
+
return output;
|
|
421
|
+
}
|
|
422
|
+
catch (error) {
|
|
423
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
424
|
+
const failureText = `⚠️ 开盘前资讯简报失败: ${message}`;
|
|
425
|
+
await this.writeState({
|
|
426
|
+
...state,
|
|
427
|
+
lastPreMarketAttemptAt: attemptedAt,
|
|
428
|
+
lastPreMarketAttemptDate: today,
|
|
429
|
+
lastPreMarketResultType: "failed",
|
|
430
|
+
lastPreMarketResultSummary: failureText,
|
|
431
|
+
preMarketConsecutiveFailures: state.preMarketConsecutiveFailures + 1,
|
|
432
|
+
});
|
|
433
|
+
const output = {
|
|
434
|
+
resultType: "failed",
|
|
435
|
+
message: failureText,
|
|
436
|
+
};
|
|
437
|
+
if (trigger === "scheduled") {
|
|
438
|
+
await this.maybeSendPreMarketBriefNotification(output);
|
|
439
|
+
}
|
|
440
|
+
return output;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
348
443
|
getStateFilePath() {
|
|
349
444
|
return path.join(this.baseDir, "daily-update-state.json");
|
|
350
445
|
}
|
|
@@ -389,6 +484,17 @@ export class DailyUpdateWorker {
|
|
|
389
484
|
const message = this.alertService.formatSystemNotification("📊 定时日更完成", normalizeResultLines(output.message));
|
|
390
485
|
await this.alertService.send(message);
|
|
391
486
|
}
|
|
487
|
+
async maybeSendPreMarketBriefNotification(output) {
|
|
488
|
+
if (!this.notifyEnabled || output.resultType === "skipped") {
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
if (output.resultType !== "success") {
|
|
492
|
+
const message = this.alertService.formatSystemNotification("❌ 开盘前资讯简报失败", selectPreMarketBriefNotificationLines(output.message));
|
|
493
|
+
await this.alertService.send(message);
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
await this.alertService.send(output.message);
|
|
497
|
+
}
|
|
392
498
|
async maybeSendReviewNotification(output) {
|
|
393
499
|
if (!this.notifyEnabled || output.resultType === "skipped") {
|
|
394
500
|
return;
|
|
@@ -425,6 +531,12 @@ function createReviewExecutionOutput(reviewResult) {
|
|
|
425
531
|
combinedText: joinMessages(reviewResult.overviewMessage, ...reviewResult.detailMessages),
|
|
426
532
|
};
|
|
427
533
|
}
|
|
534
|
+
function createPreMarketBriefExecutionOutput(result) {
|
|
535
|
+
return {
|
|
536
|
+
resultType: result.resultType,
|
|
537
|
+
message: result.message,
|
|
538
|
+
};
|
|
539
|
+
}
|
|
428
540
|
function joinMessages(...parts) {
|
|
429
541
|
return parts
|
|
430
542
|
.map((part) => part?.trim())
|
|
@@ -467,6 +579,9 @@ function summarizeUpdateResult(result) {
|
|
|
467
579
|
function summarizeReviewResult(result) {
|
|
468
580
|
return selectReviewSummaryLines(result).join(" | ");
|
|
469
581
|
}
|
|
582
|
+
function summarizePreMarketBriefResult(result) {
|
|
583
|
+
return selectPreMarketBriefNotificationLines(result).join(" | ");
|
|
584
|
+
}
|
|
470
585
|
function selectUpdateSummaryLines(result) {
|
|
471
586
|
const lines = normalizeResultLines(result);
|
|
472
587
|
const head = lines.slice(0, 2);
|
|
@@ -491,6 +606,12 @@ function selectReviewNotificationLines(result) {
|
|
|
491
606
|
const highlights = lines.filter((line) => /^(🧭|复盘数量:|关键位验证:|明日处理:|⚠️ 收盘复盘失败|⚠️ 收盘分析\/回测失败)/.test(line));
|
|
492
607
|
return dedupeLines([...head, ...highlights]).slice(0, 12);
|
|
493
608
|
}
|
|
609
|
+
function selectPreMarketBriefNotificationLines(result) {
|
|
610
|
+
const lines = normalizeResultLines(result);
|
|
611
|
+
const head = lines.slice(0, 4);
|
|
612
|
+
const highlights = lines.filter((line) => /^\*\*【/.test(line));
|
|
613
|
+
return dedupeLines([...head, ...highlights]).slice(0, 12);
|
|
614
|
+
}
|
|
494
615
|
function normalizeResultLines(result) {
|
|
495
616
|
return result
|
|
496
617
|
.split("\n")
|
|
@@ -536,6 +657,9 @@ function formatRuntimeHost(state) {
|
|
|
536
657
|
function hasCompletedScheduledWindow(successDate, successAt, today, readyTime) {
|
|
537
658
|
return successDate === today && extractChinaTime(successAt) >= readyTime;
|
|
538
659
|
}
|
|
660
|
+
function hasAttemptedScheduledWindow(attemptDate, attemptAt, today, readyTime) {
|
|
661
|
+
return attemptDate === today && extractChinaTime(attemptAt) >= readyTime;
|
|
662
|
+
}
|
|
539
663
|
function extractChinaTime(dateTime) {
|
|
540
664
|
return dateTime?.slice(11, 16) ?? "00:00";
|
|
541
665
|
}
|
package/dist/bootstrap.js
CHANGED
|
@@ -35,6 +35,7 @@ import { AlertMediaService } from "./services/alert-media-service.js";
|
|
|
35
35
|
import { UpdateService } from "./services/update-service.js";
|
|
36
36
|
import { KeyLevelsBacktestService } from "./services/key-levels-backtest-service.js";
|
|
37
37
|
import { PostCloseReviewService } from "./services/post-close-review-service.js";
|
|
38
|
+
import { PreMarketBriefService } from "./services/pre-market-brief-service.js";
|
|
38
39
|
import { ReviewMemoryService } from "./services/review-memory-service.js";
|
|
39
40
|
import { CompositeAnalysisOrchestrator } from "./analysis/orchestrators/composite-analysis.orchestrator.js";
|
|
40
41
|
import { MarketAnalysisProvider } from "./analysis/providers/market-analysis.provider.js";
|
|
@@ -140,9 +141,10 @@ export function createAppContext(config, options = {}) {
|
|
|
140
141
|
const jin10FlashMonitorService = new Jin10FlashMonitorService(config.databasePath, config.jin10FlashPollInterval, config.jin10FlashRetentionDays, config.jin10FlashNightAlert, watchlistService, jin10McpService, analysisService, alertService, jin10FlashRepository, jin10FlashDeliveryRepository);
|
|
141
142
|
const updateService = new UpdateService(klineService, config.tickflowApiKeyLevel, indicatorService, klinesRepository, indicatorsRepository, intradayKlinesRepository, watchlistService, tradingCalendarService);
|
|
142
143
|
const postCloseReviewService = new PostCloseReviewService(watchlistService, compositeAnalysisOrchestrator, analysisService, postCloseReviewTask, keyLevelsRepository, keyLevelsHistoryRepository, klinesRepository, intradayKlinesRepository, jin10FlashDeliveryRepository, jin10FlashRepository);
|
|
144
|
+
const preMarketBriefService = new PreMarketBriefService(watchlistService, jin10McpService, jin10FlashRepository, analysisService);
|
|
143
145
|
const realtimeMonitorWorker = new RealtimeMonitorWorker(monitorService, config.requestInterval * 1000);
|
|
144
146
|
const jin10FlashWorker = new Jin10FlashWorker(jin10FlashMonitorService, config.jin10FlashPollInterval * 1000);
|
|
145
|
-
const dailyUpdateWorker = new DailyUpdateWorker(updateService, postCloseReviewService, tradingCalendarService, config.databasePath, alertService, config.dailyUpdateNotify, runtime.configSource);
|
|
147
|
+
const dailyUpdateWorker = new DailyUpdateWorker(updateService, preMarketBriefService, postCloseReviewService, tradingCalendarService, config.databasePath, alertService, config.dailyUpdateNotify, runtime.configSource);
|
|
146
148
|
let managedLoopAbortController = null;
|
|
147
149
|
let managedLoopPromise = null;
|
|
148
150
|
return {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export declare const CONFIG_ENV_FALLBACKS: {
|
|
2
|
+
readonly tickflowApiUrl: readonly ["TICKFLOW_ASSIST_TICKFLOW_API_URL", "TICKFLOW_API_URL"];
|
|
3
|
+
readonly tickflowApiKey: readonly ["TICKFLOW_ASSIST_TICKFLOW_API_KEY", "TICKFLOW_API_KEY"];
|
|
4
|
+
readonly tickflowApiKeyLevel: readonly ["TICKFLOW_ASSIST_TICKFLOW_API_KEY_LEVEL", "TICKFLOW_API_KEY_LEVEL"];
|
|
5
|
+
readonly mxSearchApiUrl: readonly ["TICKFLOW_ASSIST_MX_SEARCH_API_URL", "MX_SEARCH_API_URL"];
|
|
6
|
+
readonly mxSearchApiKey: readonly ["TICKFLOW_ASSIST_MX_SEARCH_API_KEY", "MX_SEARCH_API_KEY", "MX_APIKEY"];
|
|
7
|
+
readonly jin10McpUrl: readonly ["TICKFLOW_ASSIST_JIN10_MCP_URL", "JIN10_MCP_URL"];
|
|
8
|
+
readonly jin10ApiToken: readonly ["TICKFLOW_ASSIST_JIN10_API_TOKEN", "JIN10_API_TOKEN"];
|
|
9
|
+
readonly llmBaseUrl: readonly ["TICKFLOW_ASSIST_LLM_BASE_URL", "LLM_BASE_URL"];
|
|
10
|
+
readonly llmApiKey: readonly ["TICKFLOW_ASSIST_LLM_API_KEY", "LLM_API_KEY"];
|
|
11
|
+
readonly llmModel: readonly ["TICKFLOW_ASSIST_LLM_MODEL", "LLM_MODEL"];
|
|
12
|
+
};
|
|
13
|
+
export type EnvBackedConfigKey = keyof typeof CONFIG_ENV_FALLBACKS;
|
|
14
|
+
export declare function getEnvFallbackValue(names: readonly string[], env?: NodeJS.ProcessEnv): string;
|
|
15
|
+
export declare function getConfigEnvFallback(key: EnvBackedConfigKey, env?: NodeJS.ProcessEnv): string;
|
|
16
|
+
export declare function hasConfigEnvFallback(key: EnvBackedConfigKey, env?: NodeJS.ProcessEnv): boolean;
|
|
17
|
+
export declare function formatConfigEnvFallback(key: EnvBackedConfigKey): string;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export const CONFIG_ENV_FALLBACKS = {
|
|
2
|
+
tickflowApiUrl: ["TICKFLOW_ASSIST_TICKFLOW_API_URL", "TICKFLOW_API_URL"],
|
|
3
|
+
tickflowApiKey: ["TICKFLOW_ASSIST_TICKFLOW_API_KEY", "TICKFLOW_API_KEY"],
|
|
4
|
+
tickflowApiKeyLevel: ["TICKFLOW_ASSIST_TICKFLOW_API_KEY_LEVEL", "TICKFLOW_API_KEY_LEVEL"],
|
|
5
|
+
mxSearchApiUrl: ["TICKFLOW_ASSIST_MX_SEARCH_API_URL", "MX_SEARCH_API_URL"],
|
|
6
|
+
mxSearchApiKey: ["TICKFLOW_ASSIST_MX_SEARCH_API_KEY", "MX_SEARCH_API_KEY", "MX_APIKEY"],
|
|
7
|
+
jin10McpUrl: ["TICKFLOW_ASSIST_JIN10_MCP_URL", "JIN10_MCP_URL"],
|
|
8
|
+
jin10ApiToken: ["TICKFLOW_ASSIST_JIN10_API_TOKEN", "JIN10_API_TOKEN"],
|
|
9
|
+
llmBaseUrl: ["TICKFLOW_ASSIST_LLM_BASE_URL", "LLM_BASE_URL"],
|
|
10
|
+
llmApiKey: ["TICKFLOW_ASSIST_LLM_API_KEY", "LLM_API_KEY"],
|
|
11
|
+
llmModel: ["TICKFLOW_ASSIST_LLM_MODEL", "LLM_MODEL"],
|
|
12
|
+
};
|
|
13
|
+
export function getEnvFallbackValue(names, env = process.env) {
|
|
14
|
+
for (const name of names) {
|
|
15
|
+
const value = env[name];
|
|
16
|
+
if (typeof value === "string" && value.trim()) {
|
|
17
|
+
return value.trim();
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return "";
|
|
21
|
+
}
|
|
22
|
+
export function getConfigEnvFallback(key, env = process.env) {
|
|
23
|
+
return getEnvFallbackValue(CONFIG_ENV_FALLBACKS[key], env);
|
|
24
|
+
}
|
|
25
|
+
export function hasConfigEnvFallback(key, env = process.env) {
|
|
26
|
+
return Boolean(getConfigEnvFallback(key, env));
|
|
27
|
+
}
|
|
28
|
+
export function formatConfigEnvFallback(key) {
|
|
29
|
+
return CONFIG_ENV_FALLBACKS[key].join(" / ");
|
|
30
|
+
}
|
package/dist/config/normalize.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
+
import { getConfigEnvFallback } from "./env.js";
|
|
2
3
|
import { normalizeTickflowApiKeyLevel } from "./tickflow-access.js";
|
|
3
4
|
import { DEFAULT_PLUGIN_CONFIG } from "./schema.js";
|
|
4
5
|
function normalizeString(value, fallback = "") {
|
|
@@ -10,6 +11,10 @@ function normalizeString(value, fallback = "") {
|
|
|
10
11
|
}
|
|
11
12
|
return String(value).trim();
|
|
12
13
|
}
|
|
14
|
+
function normalizeStringWithFallback(value, fallback = "") {
|
|
15
|
+
const normalized = normalizeString(value);
|
|
16
|
+
return normalized || fallback;
|
|
17
|
+
}
|
|
13
18
|
function normalizeInteger(value, fallback) {
|
|
14
19
|
const parsed = Number(value ?? fallback);
|
|
15
20
|
return Number.isFinite(parsed) ? Math.max(1, Math.trunc(parsed)) : fallback;
|
|
@@ -40,22 +45,30 @@ function normalizeBoolean(value, fallback) {
|
|
|
40
45
|
}
|
|
41
46
|
export function normalizePluginConfig(input) {
|
|
42
47
|
const raw = (input ?? {});
|
|
43
|
-
const
|
|
44
|
-
const
|
|
48
|
+
const envTickflowApiUrl = getConfigEnvFallback("tickflowApiUrl");
|
|
49
|
+
const envTickflowApiKey = getConfigEnvFallback("tickflowApiKey");
|
|
50
|
+
const envTickflowApiKeyLevel = getConfigEnvFallback("tickflowApiKeyLevel");
|
|
51
|
+
const envMxSearchApiUrl = getConfigEnvFallback("mxSearchApiUrl");
|
|
52
|
+
const envMxSearchApiKey = getConfigEnvFallback("mxSearchApiKey");
|
|
53
|
+
const envJin10McpUrl = getConfigEnvFallback("jin10McpUrl");
|
|
54
|
+
const envJin10ApiToken = getConfigEnvFallback("jin10ApiToken");
|
|
55
|
+
const envLlmBaseUrl = getConfigEnvFallback("llmBaseUrl");
|
|
56
|
+
const envLlmApiKey = getConfigEnvFallback("llmApiKey");
|
|
57
|
+
const envLlmModel = getConfigEnvFallback("llmModel");
|
|
45
58
|
return {
|
|
46
|
-
tickflowApiUrl:
|
|
47
|
-
tickflowApiKey:
|
|
48
|
-
tickflowApiKeyLevel: normalizeTickflowApiKeyLevel(raw.tickflowApiKeyLevel, DEFAULT_PLUGIN_CONFIG.tickflowApiKeyLevel),
|
|
49
|
-
mxSearchApiUrl:
|
|
50
|
-
mxSearchApiKey:
|
|
51
|
-
jin10McpUrl:
|
|
52
|
-
jin10ApiToken:
|
|
59
|
+
tickflowApiUrl: normalizeStringWithFallback(raw.tickflowApiUrl, envTickflowApiUrl || DEFAULT_PLUGIN_CONFIG.tickflowApiUrl),
|
|
60
|
+
tickflowApiKey: normalizeStringWithFallback(raw.tickflowApiKey, envTickflowApiKey),
|
|
61
|
+
tickflowApiKeyLevel: normalizeTickflowApiKeyLevel(normalizeStringWithFallback(raw.tickflowApiKeyLevel, envTickflowApiKeyLevel), DEFAULT_PLUGIN_CONFIG.tickflowApiKeyLevel),
|
|
62
|
+
mxSearchApiUrl: normalizeStringWithFallback(raw.mxSearchApiUrl, envMxSearchApiUrl || DEFAULT_PLUGIN_CONFIG.mxSearchApiUrl),
|
|
63
|
+
mxSearchApiKey: normalizeStringWithFallback(raw.mxSearchApiKey, envMxSearchApiKey || DEFAULT_PLUGIN_CONFIG.mxSearchApiKey),
|
|
64
|
+
jin10McpUrl: normalizeStringWithFallback(raw.jin10McpUrl, envJin10McpUrl || DEFAULT_PLUGIN_CONFIG.jin10McpUrl),
|
|
65
|
+
jin10ApiToken: normalizeStringWithFallback(raw.jin10ApiToken, envJin10ApiToken),
|
|
53
66
|
jin10FlashPollInterval: normalizeInteger(raw.jin10FlashPollInterval, DEFAULT_PLUGIN_CONFIG.jin10FlashPollInterval),
|
|
54
67
|
jin10FlashRetentionDays: normalizeInteger(raw.jin10FlashRetentionDays, DEFAULT_PLUGIN_CONFIG.jin10FlashRetentionDays),
|
|
55
68
|
jin10FlashNightAlert: normalizeBoolean(raw.jin10FlashNightAlert, DEFAULT_PLUGIN_CONFIG.jin10FlashNightAlert),
|
|
56
|
-
llmBaseUrl:
|
|
57
|
-
llmApiKey:
|
|
58
|
-
llmModel:
|
|
69
|
+
llmBaseUrl: normalizeStringWithFallback(raw.llmBaseUrl, envLlmBaseUrl || DEFAULT_PLUGIN_CONFIG.llmBaseUrl),
|
|
70
|
+
llmApiKey: normalizeStringWithFallback(raw.llmApiKey, envLlmApiKey),
|
|
71
|
+
llmModel: normalizeStringWithFallback(raw.llmModel, envLlmModel || DEFAULT_PLUGIN_CONFIG.llmModel),
|
|
59
72
|
databasePath: normalizeString(raw.databasePath, DEFAULT_PLUGIN_CONFIG.databasePath),
|
|
60
73
|
calendarFile: normalizeString(raw.calendarFile, DEFAULT_PLUGIN_CONFIG.calendarFile),
|
|
61
74
|
requestInterval: normalizeInteger(raw.requestInterval, DEFAULT_PLUGIN_CONFIG.requestInterval),
|
|
@@ -6,6 +6,7 @@ import path from "node:path";
|
|
|
6
6
|
import process from "node:process";
|
|
7
7
|
import { createInterface } from "node:readline/promises";
|
|
8
8
|
import JSON5 from "json5";
|
|
9
|
+
import { formatConfigEnvFallback, hasConfigEnvFallback, } from "../config/env.js";
|
|
9
10
|
const PLUGIN_ID = "tickflow-assist";
|
|
10
11
|
const DEFAULT_CONFIG_PATH = path.join(os.homedir(), ".openclaw", "openclaw.json");
|
|
11
12
|
const DEFAULTS = {
|
|
@@ -59,6 +60,14 @@ Options:
|
|
|
59
60
|
--alert-account <name>
|
|
60
61
|
--alert-target <target>
|
|
61
62
|
-h, --help Show this help
|
|
63
|
+
|
|
64
|
+
Environment fallback:
|
|
65
|
+
tickflowApiKey ${formatConfigEnvFallback("tickflowApiKey")}
|
|
66
|
+
llmApiKey ${formatConfigEnvFallback("llmApiKey")}
|
|
67
|
+
llmBaseUrl ${formatConfigEnvFallback("llmBaseUrl")}
|
|
68
|
+
llmModel ${formatConfigEnvFallback("llmModel")}
|
|
69
|
+
mxSearchApiKey ${formatConfigEnvFallback("mxSearchApiKey")}
|
|
70
|
+
jin10ApiToken ${formatConfigEnvFallback("jin10ApiToken")}
|
|
62
71
|
`);
|
|
63
72
|
}
|
|
64
73
|
function parseArgs(argv) {
|
|
@@ -390,7 +399,7 @@ async function promptAlertChannel(rl, configPath, defaultChannel) {
|
|
|
390
399
|
choices.push({ value: "__manual__", label: "手动输入其他通道" });
|
|
391
400
|
selectedChannel = await promptSelect(rl, "检测到 openclaw.json 中已有配置,请选择推送通道", choices, defaultChannel);
|
|
392
401
|
if (selectedChannel === "__manual__") {
|
|
393
|
-
selectedChannel = await promptString(rl, "Alert Channel", defaultChannel, true);
|
|
402
|
+
selectedChannel = await promptString(rl, "Alert Channel", defaultChannel, { required: true });
|
|
394
403
|
}
|
|
395
404
|
}
|
|
396
405
|
else {
|
|
@@ -467,15 +476,29 @@ async function promptForConfig(options, existing, pluginDir, configPath) {
|
|
|
467
476
|
console.log(`OpenClaw 配置文件: ${configPath}`);
|
|
468
477
|
console.log(`插件目录: ${pluginDir}`);
|
|
469
478
|
console.log("");
|
|
470
|
-
|
|
479
|
+
console.log("为避免后续运行时反复补配置,下面这些字段建议一次性填完整。");
|
|
480
|
+
console.log("");
|
|
481
|
+
seed.tickflowApiKey = await promptString(rl, buildEnvAwarePromptLabel("TickFlow API Key", "tickflowApiKey", seed.tickflowApiKey), seed.tickflowApiKey, {
|
|
482
|
+
required: !hasConfigEnvFallback("tickflowApiKey"),
|
|
483
|
+
maskDefault: true,
|
|
484
|
+
allowClear: true,
|
|
485
|
+
});
|
|
471
486
|
seed.tickflowApiKeyLevel = normalizeApiKeyLevel(await promptSelect(rl, "TickFlow 订阅等级", [
|
|
472
487
|
{ value: "Free", label: "Free" },
|
|
473
488
|
{ value: "Start", label: "Start" },
|
|
474
489
|
{ value: "Pro", label: "Pro" },
|
|
475
490
|
{ value: "Expert", label: "Expert" },
|
|
476
491
|
], seed.tickflowApiKeyLevel));
|
|
477
|
-
seed.mxSearchApiKey = await promptString(rl, "MX Search API Key
|
|
478
|
-
|
|
492
|
+
seed.mxSearchApiKey = await promptString(rl, buildEnvAwarePromptLabel("MX Search API Key(建议填写)", "mxSearchApiKey", seed.mxSearchApiKey), seed.mxSearchApiKey, {
|
|
493
|
+
required: false,
|
|
494
|
+
maskDefault: true,
|
|
495
|
+
allowClear: true,
|
|
496
|
+
});
|
|
497
|
+
seed.jin10ApiToken = await promptString(rl, buildEnvAwarePromptLabel("Jin10 API Token(建议填写)", "jin10ApiToken", seed.jin10ApiToken), seed.jin10ApiToken, {
|
|
498
|
+
required: false,
|
|
499
|
+
maskDefault: true,
|
|
500
|
+
allowClear: true,
|
|
501
|
+
});
|
|
479
502
|
seed.jin10FlashPollInterval = await promptInteger(rl, "Jin10 快讯轮询间隔(秒)", seed.jin10FlashPollInterval, 10);
|
|
480
503
|
seed.jin10FlashRetentionDays = await promptInteger(rl, "Jin10 快讯保留天数", seed.jin10FlashRetentionDays, 1);
|
|
481
504
|
const nightAlertChoice = await promptSelect(rl, "Jin10 夜间静默", [
|
|
@@ -483,21 +506,31 @@ async function promptForConfig(options, existing, pluginDir, configPath) {
|
|
|
483
506
|
{ value: "false", label: "开启夜间静默(22:00~06:00 不告警)" },
|
|
484
507
|
], seed.jin10FlashNightAlert ? "true" : "false");
|
|
485
508
|
seed.jin10FlashNightAlert = nightAlertChoice === "true";
|
|
486
|
-
seed.llmBaseUrl = await promptString(rl, "LLM Base URL", seed.llmBaseUrl,
|
|
487
|
-
|
|
488
|
-
|
|
509
|
+
seed.llmBaseUrl = await promptString(rl, buildEnvAwarePromptLabel("LLM Base URL", "llmBaseUrl", seed.llmBaseUrl), seed.llmBaseUrl, {
|
|
510
|
+
required: true,
|
|
511
|
+
allowClear: hasConfigEnvFallback("llmBaseUrl"),
|
|
512
|
+
});
|
|
513
|
+
seed.llmApiKey = await promptString(rl, buildEnvAwarePromptLabel("LLM API Key", "llmApiKey", seed.llmApiKey), seed.llmApiKey, {
|
|
514
|
+
required: !hasConfigEnvFallback("llmApiKey"),
|
|
515
|
+
maskDefault: true,
|
|
516
|
+
allowClear: true,
|
|
517
|
+
});
|
|
518
|
+
seed.llmModel = await promptString(rl, buildEnvAwarePromptLabel("LLM Model", "llmModel", seed.llmModel), seed.llmModel, {
|
|
519
|
+
required: true,
|
|
520
|
+
allowClear: hasConfigEnvFallback("llmModel"),
|
|
521
|
+
});
|
|
489
522
|
console.log("");
|
|
490
523
|
const alertResult = await promptAlertChannel(rl, configPath, seed.alertChannel);
|
|
491
524
|
seed.alertChannel = alertResult.channel;
|
|
492
525
|
seed.alertAccount = alertResult.account;
|
|
493
526
|
let targetLabel = "Alert Target";
|
|
494
527
|
if (seed.alertAccount) {
|
|
495
|
-
targetLabel = `已选通道 [${seed.alertChannel}] 及账号 [${seed.alertAccount}],请输入 Alert Target
|
|
528
|
+
targetLabel = `已选通道 [${seed.alertChannel}] 及账号 [${seed.alertAccount}],请输入 Alert Target(建议填写)`;
|
|
496
529
|
}
|
|
497
530
|
else {
|
|
498
|
-
targetLabel = `已选通道 [${seed.alertChannel}],请输入 Alert Target
|
|
531
|
+
targetLabel = `已选通道 [${seed.alertChannel}],请输入 Alert Target(建议填写)`;
|
|
499
532
|
}
|
|
500
|
-
seed.alertTarget = await promptString(rl, targetLabel, seed.alertTarget, false);
|
|
533
|
+
seed.alertTarget = await promptString(rl, targetLabel, seed.alertTarget, { required: false });
|
|
501
534
|
seed.requestInterval = await promptInteger(rl, "Request Interval (seconds)", seed.requestInterval, 5);
|
|
502
535
|
seed.dailyUpdateNotify = await promptBoolean(rl, "Daily Update Notify", seed.dailyUpdateNotify);
|
|
503
536
|
}
|
|
@@ -507,12 +540,17 @@ async function promptForConfig(options, existing, pluginDir, configPath) {
|
|
|
507
540
|
assertRequired(seed);
|
|
508
541
|
return seed;
|
|
509
542
|
}
|
|
510
|
-
async function promptString(rl, label, defaultValue,
|
|
543
|
+
async function promptString(rl, label, defaultValue, options) {
|
|
511
544
|
while (true) {
|
|
512
|
-
const suffix = defaultValue
|
|
545
|
+
const suffix = defaultValue
|
|
546
|
+
? options.maskDefault ? " [已存在]" : ` [${defaultValue}]`
|
|
547
|
+
: "";
|
|
513
548
|
const answer = (await rl.question(`${label}${suffix}: `)).trim();
|
|
549
|
+
if (options.allowClear && answer === "-") {
|
|
550
|
+
return "";
|
|
551
|
+
}
|
|
514
552
|
const value = answer || defaultValue;
|
|
515
|
-
if (!required || value) {
|
|
553
|
+
if (!options.required || value) {
|
|
516
554
|
return value;
|
|
517
555
|
}
|
|
518
556
|
console.error(`${label} 不能为空`);
|
|
@@ -544,11 +582,11 @@ async function promptBoolean(rl, label, defaultValue) {
|
|
|
544
582
|
}
|
|
545
583
|
}
|
|
546
584
|
function assertRequired(config) {
|
|
547
|
-
if (!config.tickflowApiKey) {
|
|
548
|
-
throw new Error(
|
|
585
|
+
if (!config.tickflowApiKey && !hasConfigEnvFallback("tickflowApiKey")) {
|
|
586
|
+
throw new Error(`tickflowApiKey is required (or set ${formatConfigEnvFallback("tickflowApiKey")})`);
|
|
549
587
|
}
|
|
550
|
-
if (!config.llmApiKey) {
|
|
551
|
-
throw new Error(
|
|
588
|
+
if (!config.llmApiKey && !hasConfigEnvFallback("llmApiKey")) {
|
|
589
|
+
throw new Error(`llmApiKey is required (or set ${formatConfigEnvFallback("llmApiKey")})`);
|
|
552
590
|
}
|
|
553
591
|
}
|
|
554
592
|
function normalizeCommunityInstallSpec(root) {
|
|
@@ -806,6 +844,7 @@ async function configureOpenClaw(options) {
|
|
|
806
844
|
if (normalizedInstallSpec) {
|
|
807
845
|
console.log(`Community install spec normalized: clawhub:${PLUGIN_ID}`);
|
|
808
846
|
}
|
|
847
|
+
printActiveEnvFallbacks(config);
|
|
809
848
|
printNextSteps(options, config);
|
|
810
849
|
}
|
|
811
850
|
async function main() {
|
|
@@ -823,3 +862,30 @@ async function main() {
|
|
|
823
862
|
}
|
|
824
863
|
}
|
|
825
864
|
void main();
|
|
865
|
+
function buildEnvAwarePromptLabel(label, key, currentValue) {
|
|
866
|
+
if (!hasConfigEnvFallback(key)) {
|
|
867
|
+
return label;
|
|
868
|
+
}
|
|
869
|
+
const fallback = formatConfigEnvFallback(key);
|
|
870
|
+
if (currentValue) {
|
|
871
|
+
return `${label}(留空保留当前值;输入 - 清空并改用环境变量 ${fallback})`;
|
|
872
|
+
}
|
|
873
|
+
return `${label}(留空则使用环境变量 ${fallback})`;
|
|
874
|
+
}
|
|
875
|
+
function printActiveEnvFallbacks(config) {
|
|
876
|
+
const activeFallbacks = [
|
|
877
|
+
{ key: "tickflowApiKey", label: "TickFlow API Key", value: config.tickflowApiKey },
|
|
878
|
+
{ key: "mxSearchApiKey", label: "MX Search API Key", value: config.mxSearchApiKey },
|
|
879
|
+
{ key: "jin10ApiToken", label: "Jin10 API Token", value: config.jin10ApiToken },
|
|
880
|
+
{ key: "llmBaseUrl", label: "LLM Base URL", value: config.llmBaseUrl },
|
|
881
|
+
{ key: "llmApiKey", label: "LLM API Key", value: config.llmApiKey },
|
|
882
|
+
{ key: "llmModel", label: "LLM Model", value: config.llmModel },
|
|
883
|
+
].filter(({ key, value }) => !value && hasConfigEnvFallback(key));
|
|
884
|
+
if (activeFallbacks.length === 0) {
|
|
885
|
+
return;
|
|
886
|
+
}
|
|
887
|
+
console.log("Active env fallback:");
|
|
888
|
+
for (const entry of activeFallbacks) {
|
|
889
|
+
console.log(` ${entry.label}: ${formatConfigEnvFallback(entry.key)}`);
|
|
890
|
+
}
|
|
891
|
+
}
|
|
@@ -7,3 +7,4 @@ export { COMPOSITE_ANALYSIS_SYSTEM_PROMPT, buildCompositeAnalysisUserPrompt, } f
|
|
|
7
7
|
export { POST_CLOSE_REVIEW_SYSTEM_PROMPT, buildPostCloseReviewUserPrompt, } from "./post-close-review-user-prompt.js";
|
|
8
8
|
export { WATCHLIST_PROFILE_EXTRACTION_SYSTEM_PROMPT, buildWatchlistProfileExtractionUserPrompt, } from "./watchlist-profile-extraction-prompt.js";
|
|
9
9
|
export { FLASH_MONITOR_ALERT_SYSTEM_PROMPT, buildFlashMonitorAlertUserPrompt, } from "./flash-monitor-alert-prompt.js";
|
|
10
|
+
export { PRE_MARKET_BRIEF_SYSTEM_PROMPT, buildPreMarketBriefUserPrompt, } from "./pre-market-brief-prompt.js";
|