@openfinclaw/openfinclaw-strategy 2026.3.26 → 2026.3.271
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 +1 -1
- package/index.test.ts +3 -3
- package/index.ts +15 -4
- package/openclaw.plugin.json +9 -2
- package/package.json +2 -2
- package/skills/price-check/SKILL.md +1 -1
- package/skills/skill-publish/SKILL.md +1 -1
- package/skills/strategy-fork/SKILL.md +1 -1
- package/skills/strategy-pack/SKILL.md +1 -1
- package/src/cli.ts +49 -1
- package/src/config.ts +9 -1
- package/src/datahub/tools.ts +19 -12
- package/src/db/db.ts +44 -0
- package/src/db/repositories.ts +315 -0
- package/src/db/schema.ts +110 -0
- package/src/http/routes.ts +107 -0
- package/src/http/server.ts +36 -0
- package/src/middleware/with-logging.ts +72 -0
- package/src/strategy/tools.ts +214 -81
- package/src/types.ts +2 -0
- package/skills/openfinclaw/SKILL.md +0 -301
package/README.md
CHANGED
|
@@ -80,7 +80,7 @@ openclaw strategy remove <name-or-id> --force
|
|
|
80
80
|
从 https://hub.openfinclaw.ai 获取 API Key(以 `fch_` 开头),一个 Key 即可使用所有功能:
|
|
81
81
|
|
|
82
82
|
```bash
|
|
83
|
-
openclaw config set plugins.entries.openfinclaw.config.apiKey YOUR_API_KEY
|
|
83
|
+
openclaw config set plugins.entries.openfinclaw-strategy.config.apiKey YOUR_API_KEY
|
|
84
84
|
```
|
|
85
85
|
|
|
86
86
|
或使用环境变量:
|
package/index.test.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Unit tests for openfinclaw plugin: registration, config resolution, and tool execution with mocked HTTP.
|
|
3
3
|
*/
|
|
4
|
-
import type { OpenClawPluginApi } from "
|
|
4
|
+
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
|
5
5
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
6
6
|
import plugin from "./index.js";
|
|
7
7
|
|
|
@@ -32,7 +32,7 @@ function createFakeApi(pluginConfig: Record<string, unknown>): {
|
|
|
32
32
|
} {
|
|
33
33
|
const tools = new Map<string, Tool>();
|
|
34
34
|
const api = {
|
|
35
|
-
id: "openfinclaw",
|
|
35
|
+
id: "openfinclaw-strategy",
|
|
36
36
|
name: "OpenFinClaw",
|
|
37
37
|
source: "test",
|
|
38
38
|
config: {},
|
|
@@ -66,7 +66,7 @@ describe("openfinclaw plugin", () => {
|
|
|
66
66
|
});
|
|
67
67
|
|
|
68
68
|
it("has correct plugin metadata", () => {
|
|
69
|
-
expect(plugin.id).toBe("openfinclaw");
|
|
69
|
+
expect(plugin.id).toBe("openfinclaw-strategy");
|
|
70
70
|
expect(plugin.name).toBe("OpenFinClaw");
|
|
71
71
|
});
|
|
72
72
|
|
package/index.ts
CHANGED
|
@@ -4,16 +4,20 @@ import type { Command } from "commander";
|
|
|
4
4
|
* Features:
|
|
5
5
|
* - Strategy tools: publish, validate, fork, leaderboard
|
|
6
6
|
* - Market data tools: price, K-line, crypto data, compare, search
|
|
7
|
+
* - SQLite persistence: all tool executions logged; domain tables for strategies and backtests
|
|
8
|
+
* - Dashboard: embedded HTTP server at http://127.0.0.1:<httpPort> (default 18792)
|
|
7
9
|
* Supports FEP v2.0 protocol for strategy packages.
|
|
8
10
|
*/
|
|
9
|
-
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
11
|
+
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
|
10
12
|
import { registerStrategyCli } from "./src/cli.js";
|
|
11
13
|
import { resolvePluginConfig } from "./src/config.js";
|
|
12
14
|
import { registerDatahubTools } from "./src/datahub/tools.js";
|
|
15
|
+
import { getDb } from "./src/db/db.js";
|
|
16
|
+
import { startHttpServer } from "./src/http/server.js";
|
|
13
17
|
import { registerStrategyTools } from "./src/strategy/tools.js";
|
|
14
18
|
|
|
15
19
|
const openfinclawPlugin = {
|
|
16
|
-
id: "openfinclaw",
|
|
20
|
+
id: "openfinclaw-strategy",
|
|
17
21
|
name: "OpenFinClaw",
|
|
18
22
|
description:
|
|
19
23
|
"Unified financial tools: strategy publishing/fork/validation, market data (price/K-line/crypto/compare/search). Single API key for Hub and DataHub.",
|
|
@@ -22,11 +26,14 @@ const openfinclawPlugin = {
|
|
|
22
26
|
register(api: OpenClawPluginApi) {
|
|
23
27
|
const config = resolvePluginConfig(api);
|
|
24
28
|
|
|
29
|
+
// Initialise SQLite database (creates tables on first run)
|
|
30
|
+
const db = getDb();
|
|
31
|
+
|
|
25
32
|
// Register DataHub market data tools (fin_price, fin_kline, fin_crypto, fin_compare, fin_slim_search)
|
|
26
|
-
registerDatahubTools(api, config);
|
|
33
|
+
registerDatahubTools(api, config, db);
|
|
27
34
|
|
|
28
35
|
// Register strategy tools (skill_publish, skill_validate, skill_fork, skill_leaderboard, etc.)
|
|
29
|
-
registerStrategyTools(api, config);
|
|
36
|
+
registerStrategyTools(api, config, db);
|
|
30
37
|
|
|
31
38
|
// Register CLI commands
|
|
32
39
|
api.registerCli(
|
|
@@ -35,9 +42,13 @@ const openfinclawPlugin = {
|
|
|
35
42
|
program,
|
|
36
43
|
config,
|
|
37
44
|
logger: api.logger,
|
|
45
|
+
db,
|
|
38
46
|
}),
|
|
39
47
|
{ commands: ["strategy"] },
|
|
40
48
|
);
|
|
49
|
+
|
|
50
|
+
// Start embedded dashboard HTTP server (loopback only, configurable port)
|
|
51
|
+
startHttpServer(db, config.httpPort, api.logger);
|
|
41
52
|
},
|
|
42
53
|
};
|
|
43
54
|
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
|
-
"id": "openfinclaw",
|
|
2
|
+
"id": "openfinclaw-strategy",
|
|
3
3
|
"name": "OpenFinClaw",
|
|
4
4
|
"description": "Unified financial tools: market data (price/K-line/crypto/compare/search), strategy publishing, fork, and validation. Single API key for Hub and DataHub.",
|
|
5
5
|
"kind": "financial",
|
|
6
6
|
"version": "2026.3.25",
|
|
7
|
-
"skills": ["
|
|
7
|
+
"skills": ["."],
|
|
8
8
|
"configSchema": {
|
|
9
9
|
"type": "object",
|
|
10
10
|
"properties": {
|
|
@@ -30,6 +30,13 @@
|
|
|
30
30
|
"minimum": 5000,
|
|
31
31
|
"maximum": 300000,
|
|
32
32
|
"description": "HTTP request timeout in milliseconds"
|
|
33
|
+
},
|
|
34
|
+
"httpPort": {
|
|
35
|
+
"type": "number",
|
|
36
|
+
"default": 18792,
|
|
37
|
+
"minimum": 1024,
|
|
38
|
+
"maximum": 65535,
|
|
39
|
+
"description": "Dashboard HTTP server port (loopback only)"
|
|
33
40
|
}
|
|
34
41
|
}
|
|
35
42
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openfinclaw/openfinclaw-strategy",
|
|
3
|
-
"version": "2026.3.
|
|
3
|
+
"version": "2026.3.271",
|
|
4
4
|
"description": "OpenFinClaw - Unified financial tools: market data (price/K-line/crypto/compare/search), strategy publishing, fork, and validation. Single API key for Hub and DataHub.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"backtest",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"registry": "https://registry.npmjs.org"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@sinclair/typebox": "
|
|
37
|
+
"@sinclair/typebox": "0.34.48",
|
|
38
38
|
"adm-zip": "^0.5.16"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: fin-price-check
|
|
3
3
|
description: "Quick price lookup for any asset — stocks, crypto, indices. Use when user asks '什么价格', 'how much is', 'current price of', or any simple price query. Returns latest price, volume, and date."
|
|
4
|
-
metadata: { "openclaw": { "emoji": "💰", "requires": { "extensions": ["openfinclaw"] } } }
|
|
4
|
+
metadata: { "openclaw": { "emoji": "💰", "requires": { "extensions": ["openfinclaw-strategy"] } } }
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Price Check
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: skill-publish
|
|
3
3
|
description: "Skill Publishing Agent. Use when the user wants to publish a strategy to the server, check publish/backtest status, or view backtest report. Flow: validate → zip → publish → poll verify → get report. Supports FEP v2.0 protocol."
|
|
4
|
-
metadata: { "openclaw": { "requires": { "extensions": ["openfinclaw"] } } }
|
|
4
|
+
metadata: { "openclaw": { "requires": { "extensions": ["openfinclaw-strategy"] } } }
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Skill Publishing
|
|
@@ -149,7 +149,7 @@ Agent:
|
|
|
149
149
|
1. **API Key**: Fork 操作需要配置 API Key
|
|
150
150
|
|
|
151
151
|
```bash
|
|
152
|
-
openclaw config set plugins.entries.openfinclaw.config.apiKey YOUR_KEY
|
|
152
|
+
openclaw config set plugins.entries.openfinclaw-strategy.config.apiKey YOUR_KEY
|
|
153
153
|
```
|
|
154
154
|
|
|
155
155
|
2. **同名冲突**: 如果两个策略名称相同,会自动添加短 ID 后缀区分
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: strategy-pack
|
|
3
3
|
description: "Create and validate FEP v2.0 strategy packages. Use when the user wants to create a strategy pack, generate fep.yaml and strategy.py, or prepare a folder for publishing. Always validate with skill_validate before zipping and publishing."
|
|
4
|
-
metadata: { "openclaw": { "requires": { "extensions": ["openfinclaw"] } } }
|
|
4
|
+
metadata: { "openclaw": { "requires": { "extensions": ["openfinclaw-strategy"] } } }
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# 策略包生成与校验 (FEP v2.0)
|
package/src/cli.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* CLI commands for strategy management.
|
|
3
3
|
*/
|
|
4
|
+
import { randomUUID } from "node:crypto";
|
|
5
|
+
import type { DatabaseSync } from "node:sqlite";
|
|
4
6
|
import type { Command } from "commander";
|
|
7
|
+
import { insertActivityLog } from "./db/repositories.js";
|
|
5
8
|
import { forkStrategy, fetchStrategyInfo } from "./strategy/fork.js";
|
|
6
9
|
import { listLocalStrategies, findLocalStrategy, removeLocalStrategy } from "./strategy/storage.js";
|
|
7
10
|
import type { UnifiedPluginConfig, LeaderboardResponse, BoardType } from "./types.js";
|
|
@@ -12,12 +15,33 @@ type Logger = {
|
|
|
12
15
|
error: (message: string) => void;
|
|
13
16
|
};
|
|
14
17
|
|
|
18
|
+
/** Log a CLI command execution to agent_activity_log. */
|
|
19
|
+
function logCli(
|
|
20
|
+
db: DatabaseSync,
|
|
21
|
+
action: string,
|
|
22
|
+
params: Record<string, unknown>,
|
|
23
|
+
startMs: number,
|
|
24
|
+
error?: string,
|
|
25
|
+
): void {
|
|
26
|
+
insertActivityLog(db, {
|
|
27
|
+
id: randomUUID(),
|
|
28
|
+
timestamp: new Date().toISOString(),
|
|
29
|
+
category: "strategy",
|
|
30
|
+
action: `cli:${action}`,
|
|
31
|
+
detail: error
|
|
32
|
+
? `ERROR (${Date.now() - startMs}ms): ${error}`
|
|
33
|
+
: `OK (${Date.now() - startMs}ms)`,
|
|
34
|
+
metadata_json: JSON.stringify({ source: "cli", params }),
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
15
38
|
export function registerStrategyCli(params: {
|
|
16
39
|
program: Command;
|
|
17
40
|
config: UnifiedPluginConfig;
|
|
18
41
|
logger: Logger;
|
|
42
|
+
db: DatabaseSync;
|
|
19
43
|
}) {
|
|
20
|
-
const { program, config } = params;
|
|
44
|
+
const { program, config, db } = params;
|
|
21
45
|
|
|
22
46
|
const root = program
|
|
23
47
|
.command("strategy")
|
|
@@ -31,6 +55,7 @@ export function registerStrategyCli(params: {
|
|
|
31
55
|
.option("-o, --offset <number>", "Offset for pagination", "0")
|
|
32
56
|
.action(
|
|
33
57
|
async (boardType: BoardType = "composite", options: { limit?: string; offset?: string }) => {
|
|
58
|
+
const startMs = Date.now();
|
|
34
59
|
const limit = Math.min(Math.max(Number(options.limit) || 20, 1), 100);
|
|
35
60
|
const offset = Math.max(Number(options.offset) || 0, 0);
|
|
36
61
|
|
|
@@ -46,6 +71,13 @@ export function registerStrategyCli(params: {
|
|
|
46
71
|
});
|
|
47
72
|
|
|
48
73
|
if (!response.ok) {
|
|
74
|
+
logCli(
|
|
75
|
+
db,
|
|
76
|
+
"leaderboard",
|
|
77
|
+
{ boardType, limit, offset },
|
|
78
|
+
startMs,
|
|
79
|
+
`HTTP ${response.status}`,
|
|
80
|
+
);
|
|
49
81
|
console.error(`✗ 请求失败: HTTP ${response.status}`);
|
|
50
82
|
process.exitCode = 1;
|
|
51
83
|
return;
|
|
@@ -92,7 +124,9 @@ export function registerStrategyCli(params: {
|
|
|
92
124
|
console.log("");
|
|
93
125
|
console.log("使用 openclaw strategy show <id> --remote 查看详情");
|
|
94
126
|
console.log("使用 openclaw strategy fork <id> 下载策略(需要 API Key)");
|
|
127
|
+
logCli(db, "leaderboard", { boardType, limit, offset }, startMs);
|
|
95
128
|
} catch (err) {
|
|
129
|
+
logCli(db, "leaderboard", { boardType, limit, offset }, startMs, String(err));
|
|
96
130
|
console.error(`✗ 请求失败: ${err instanceof Error ? err.message : String(err)}`);
|
|
97
131
|
process.exitCode = 1;
|
|
98
132
|
}
|
|
@@ -107,6 +141,7 @@ export function registerStrategyCli(params: {
|
|
|
107
141
|
.option("--date <date>", "Date directory (YYYY-MM-DD, default: today)")
|
|
108
142
|
.option("-y, --yes", "Skip confirmation", false)
|
|
109
143
|
.action(async (strategyId: string, options: { dir?: string; date?: string; yes?: boolean }) => {
|
|
144
|
+
const startMs = Date.now();
|
|
110
145
|
const result = await forkStrategy(config, strategyId, {
|
|
111
146
|
targetDir: options.dir,
|
|
112
147
|
dateDir: options.date,
|
|
@@ -114,6 +149,7 @@ export function registerStrategyCli(params: {
|
|
|
114
149
|
});
|
|
115
150
|
|
|
116
151
|
if (result.success) {
|
|
152
|
+
logCli(db, "fork", { strategyId }, startMs);
|
|
117
153
|
console.log("✓ 策略 Fork 成功!");
|
|
118
154
|
console.log("");
|
|
119
155
|
console.log(` 名称: ${result.sourceName}`);
|
|
@@ -124,6 +160,7 @@ export function registerStrategyCli(params: {
|
|
|
124
160
|
console.log(` 验证: openfinclaw strategy validate ${result.localPath}`);
|
|
125
161
|
console.log(` 发布: openfinclaw strategy publish ${result.localPath}`);
|
|
126
162
|
} else {
|
|
163
|
+
logCli(db, "fork", { strategyId }, startMs, result.error ?? "unknown");
|
|
127
164
|
console.error(`✗ Fork 失败: ${result.error}`);
|
|
128
165
|
process.exitCode = 1;
|
|
129
166
|
}
|
|
@@ -135,6 +172,7 @@ export function registerStrategyCli(params: {
|
|
|
135
172
|
.description("List all local strategies")
|
|
136
173
|
.option("--json", "Output as JSON", false)
|
|
137
174
|
.action(async (options: { json?: boolean }) => {
|
|
175
|
+
const startMs = Date.now();
|
|
138
176
|
const strategies = await listLocalStrategies();
|
|
139
177
|
|
|
140
178
|
if (options.json) {
|
|
@@ -164,6 +202,7 @@ export function registerStrategyCli(params: {
|
|
|
164
202
|
s.displayName.length > 20 ? s.displayName.slice(0, 17) + "..." : s.displayName;
|
|
165
203
|
console.log(` ${name.padEnd(40)} ${displayName.padEnd(20)} ${typeLabel}`);
|
|
166
204
|
}
|
|
205
|
+
logCli(db, "list", { count: strategies.length }, startMs);
|
|
167
206
|
});
|
|
168
207
|
|
|
169
208
|
// ── strategy show ──
|
|
@@ -173,9 +212,11 @@ export function registerStrategyCli(params: {
|
|
|
173
212
|
.option("--remote", "Fetch latest info from Hub", false)
|
|
174
213
|
.option("--json", "Output as JSON", false)
|
|
175
214
|
.action(async (nameOrId: string, options: { remote?: boolean; json?: boolean }) => {
|
|
215
|
+
const startMs = Date.now();
|
|
176
216
|
const local = await findLocalStrategy(nameOrId);
|
|
177
217
|
|
|
178
218
|
if (!local && !options.remote) {
|
|
219
|
+
logCli(db, "show", { nameOrId }, startMs, "not found");
|
|
179
220
|
console.error(`✗ 本地策略未找到: ${nameOrId}`);
|
|
180
221
|
console.error(" 使用 --remote 从 Hub 获取信息");
|
|
181
222
|
process.exitCode = 1;
|
|
@@ -186,6 +227,7 @@ export function registerStrategyCli(params: {
|
|
|
186
227
|
const infoResult = await fetchStrategyInfo(config, local.sourceId);
|
|
187
228
|
if (infoResult.success && infoResult.data) {
|
|
188
229
|
const info = infoResult.data;
|
|
230
|
+
logCli(db, "show", { nameOrId, remote: true }, startMs);
|
|
189
231
|
if (options.json) {
|
|
190
232
|
console.log(JSON.stringify({ local, hub: info }, null, 2));
|
|
191
233
|
return;
|
|
@@ -196,6 +238,7 @@ export function registerStrategyCli(params: {
|
|
|
196
238
|
}
|
|
197
239
|
|
|
198
240
|
if (local) {
|
|
241
|
+
logCli(db, "show", { nameOrId }, startMs);
|
|
199
242
|
if (options.json) {
|
|
200
243
|
console.log(JSON.stringify(local, null, 2));
|
|
201
244
|
return;
|
|
@@ -204,6 +247,7 @@ export function registerStrategyCli(params: {
|
|
|
204
247
|
return;
|
|
205
248
|
}
|
|
206
249
|
|
|
250
|
+
logCli(db, "show", { nameOrId }, startMs, "not found");
|
|
207
251
|
console.error(`✗ 策略未找到: ${nameOrId}`);
|
|
208
252
|
process.exitCode = 1;
|
|
209
253
|
});
|
|
@@ -215,8 +259,10 @@ export function registerStrategyCli(params: {
|
|
|
215
259
|
.description("Remove a local strategy")
|
|
216
260
|
.option("-f, --force", "Force removal without confirmation", false)
|
|
217
261
|
.action(async (nameOrId: string, options: { force?: boolean }) => {
|
|
262
|
+
const startMs = Date.now();
|
|
218
263
|
const local = await findLocalStrategy(nameOrId);
|
|
219
264
|
if (!local) {
|
|
265
|
+
logCli(db, "remove", { nameOrId }, startMs, "not found");
|
|
220
266
|
console.error(`✗ 策略未找到: ${nameOrId}`);
|
|
221
267
|
process.exitCode = 1;
|
|
222
268
|
return;
|
|
@@ -232,8 +278,10 @@ export function registerStrategyCli(params: {
|
|
|
232
278
|
|
|
233
279
|
const result = await removeLocalStrategy(nameOrId);
|
|
234
280
|
if (result.success) {
|
|
281
|
+
logCli(db, "remove", { nameOrId }, startMs);
|
|
235
282
|
console.log("✓ 策略已删除");
|
|
236
283
|
} else {
|
|
284
|
+
logCli(db, "remove", { nameOrId }, startMs, result.error ?? "unknown");
|
|
237
285
|
console.error(`✗ 删除失败: ${result.error}`);
|
|
238
286
|
process.exitCode = 1;
|
|
239
287
|
}
|
package/src/config.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Unified configuration resolver for OpenFinClaw plugin.
|
|
3
3
|
* Supports single API key for both Hub and DataHub services.
|
|
4
4
|
*/
|
|
5
|
-
import type { OpenClawPluginApi } from "
|
|
5
|
+
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
|
6
6
|
import type { UnifiedPluginConfig } from "./types.js";
|
|
7
7
|
|
|
8
8
|
const DEFAULT_HUB_API_URL = "https://hub.openfinclaw.ai";
|
|
@@ -48,10 +48,18 @@ export function resolvePluginConfig(api: OpenClawPluginApi): UnifiedPluginConfig
|
|
|
48
48
|
? Math.floor(Number(timeoutRaw))
|
|
49
49
|
: DEFAULT_TIMEOUT_MS;
|
|
50
50
|
|
|
51
|
+
const httpPortRaw = raw?.httpPort ?? readEnv(["OPENFINCLAW_HTTP_PORT"]);
|
|
52
|
+
const httpPortNum = Number(httpPortRaw);
|
|
53
|
+
const httpPort =
|
|
54
|
+
Number.isFinite(httpPortNum) && httpPortNum >= 1024 && httpPortNum <= 65535
|
|
55
|
+
? Math.floor(httpPortNum)
|
|
56
|
+
: 18792;
|
|
57
|
+
|
|
51
58
|
return {
|
|
52
59
|
apiKey: apiKey && apiKey.length > 0 ? apiKey : undefined,
|
|
53
60
|
hubApiUrl: hubApiUrl.replace(/\/$/, ""),
|
|
54
61
|
datahubGatewayUrl: datahubGatewayUrl.replace(/\/+$/, ""),
|
|
55
62
|
requestTimeoutMs,
|
|
63
|
+
httpPort,
|
|
56
64
|
};
|
|
57
65
|
}
|
package/src/datahub/tools.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import type { DatabaseSync } from "node:sqlite";
|
|
1
2
|
/**
|
|
2
3
|
* DataHub market data tools registration.
|
|
3
4
|
* Tools: fin_price, fin_kline, fin_crypto, fin_compare, fin_slim_search
|
|
4
5
|
*/
|
|
5
6
|
import { Type } from "@sinclair/typebox";
|
|
6
|
-
import type { OpenClawPluginApi } from "
|
|
7
|
+
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
|
8
|
+
import { withLogging } from "../middleware/with-logging.js";
|
|
7
9
|
import type { UnifiedPluginConfig, MarketType } from "../types.js";
|
|
8
10
|
import { DataHubClient, guessMarket } from "./client.js";
|
|
9
11
|
|
|
@@ -29,8 +31,13 @@ const NO_KEY =
|
|
|
29
31
|
|
|
30
32
|
/**
|
|
31
33
|
* Register DataHub market data tools.
|
|
34
|
+
* @param db - SQLite database for activity logging.
|
|
32
35
|
*/
|
|
33
|
-
export function registerDatahubTools(
|
|
36
|
+
export function registerDatahubTools(
|
|
37
|
+
api: OpenClawPluginApi,
|
|
38
|
+
config: UnifiedPluginConfig,
|
|
39
|
+
db: DatabaseSync,
|
|
40
|
+
): void {
|
|
34
41
|
const datahubClient = config.apiKey
|
|
35
42
|
? new DataHubClient(config.datahubGatewayUrl, config.apiKey, config.requestTimeoutMs)
|
|
36
43
|
: null;
|
|
@@ -84,7 +91,7 @@ export function registerDatahubTools(api: OpenClawPluginApi, config: UnifiedPlug
|
|
|
84
91
|
}),
|
|
85
92
|
),
|
|
86
93
|
}),
|
|
87
|
-
|
|
94
|
+
execute: withLogging(db, "fin_price", "market-data", async (_id, params) => {
|
|
88
95
|
try {
|
|
89
96
|
if (!datahubClient) return json({ error: NO_KEY });
|
|
90
97
|
const symbol = String(params.symbol);
|
|
@@ -100,7 +107,7 @@ export function registerDatahubTools(api: OpenClawPluginApi, config: UnifiedPlug
|
|
|
100
107
|
} catch (err) {
|
|
101
108
|
return json({ error: err instanceof Error ? err.message : String(err) });
|
|
102
109
|
}
|
|
103
|
-
},
|
|
110
|
+
}),
|
|
104
111
|
},
|
|
105
112
|
{ names: ["fin_price"] },
|
|
106
113
|
);
|
|
@@ -128,7 +135,7 @@ export function registerDatahubTools(api: OpenClawPluginApi, config: UnifiedPlug
|
|
|
128
135
|
Type.Number({ description: "Number of bars to return (default: 30)" }),
|
|
129
136
|
),
|
|
130
137
|
}),
|
|
131
|
-
|
|
138
|
+
execute: withLogging(db, "fin_kline", "market-data", async (_id, params) => {
|
|
132
139
|
try {
|
|
133
140
|
if (!datahubClient) return json({ error: NO_KEY });
|
|
134
141
|
const symbol = String(params.symbol);
|
|
@@ -151,7 +158,7 @@ export function registerDatahubTools(api: OpenClawPluginApi, config: UnifiedPlug
|
|
|
151
158
|
} catch (err) {
|
|
152
159
|
return json({ error: err instanceof Error ? err.message : String(err) });
|
|
153
160
|
}
|
|
154
|
-
},
|
|
161
|
+
}),
|
|
155
162
|
},
|
|
156
163
|
{ names: ["fin_kline"] },
|
|
157
164
|
);
|
|
@@ -202,7 +209,7 @@ export function registerDatahubTools(api: OpenClawPluginApi, config: UnifiedPlug
|
|
|
202
209
|
end_date: Type.Optional(Type.String({ description: "End date (YYYY-MM-DD)" })),
|
|
203
210
|
limit: Type.Optional(Type.Number({ description: "Max results (default: 20)" })),
|
|
204
211
|
}),
|
|
205
|
-
|
|
212
|
+
execute: withLogging(db, "fin_crypto", "market-data", async (_id, params) => {
|
|
206
213
|
try {
|
|
207
214
|
if (!datahubClient) return json({ error: NO_KEY });
|
|
208
215
|
const endpoint = String(params.endpoint ?? "coin/market");
|
|
@@ -231,7 +238,7 @@ export function registerDatahubTools(api: OpenClawPluginApi, config: UnifiedPlug
|
|
|
231
238
|
} catch (err) {
|
|
232
239
|
return json({ error: err instanceof Error ? err.message : String(err) });
|
|
233
240
|
}
|
|
234
|
-
},
|
|
241
|
+
}),
|
|
235
242
|
},
|
|
236
243
|
{ names: ["fin_crypto"] },
|
|
237
244
|
);
|
|
@@ -249,7 +256,7 @@ export function registerDatahubTools(api: OpenClawPluginApi, config: UnifiedPlug
|
|
|
249
256
|
description: "Comma-separated symbols (2-5). Example: BTC/USDT,ETH/USDT,600519.SH",
|
|
250
257
|
}),
|
|
251
258
|
}),
|
|
252
|
-
|
|
259
|
+
execute: withLogging(db, "fin_compare", "market-data", async (_id, params) => {
|
|
253
260
|
try {
|
|
254
261
|
if (!datahubClient) return json({ error: NO_KEY });
|
|
255
262
|
const raw = String(params.symbols);
|
|
@@ -287,7 +294,7 @@ export function registerDatahubTools(api: OpenClawPluginApi, config: UnifiedPlug
|
|
|
287
294
|
} catch (err) {
|
|
288
295
|
return json({ error: err instanceof Error ? err.message : String(err) });
|
|
289
296
|
}
|
|
290
|
-
},
|
|
297
|
+
}),
|
|
291
298
|
},
|
|
292
299
|
{ names: ["fin_compare"] },
|
|
293
300
|
);
|
|
@@ -310,7 +317,7 @@ export function registerDatahubTools(api: OpenClawPluginApi, config: UnifiedPlug
|
|
|
310
317
|
}),
|
|
311
318
|
),
|
|
312
319
|
}),
|
|
313
|
-
|
|
320
|
+
execute: withLogging(db, "fin_slim_search", "market-data", async (_id, params) => {
|
|
314
321
|
try {
|
|
315
322
|
if (!datahubClient) return json({ error: NO_KEY });
|
|
316
323
|
const q = String(params.query);
|
|
@@ -340,7 +347,7 @@ export function registerDatahubTools(api: OpenClawPluginApi, config: UnifiedPlug
|
|
|
340
347
|
} catch (err) {
|
|
341
348
|
return json({ error: err instanceof Error ? err.message : String(err) });
|
|
342
349
|
}
|
|
343
|
-
},
|
|
350
|
+
}),
|
|
344
351
|
},
|
|
345
352
|
{ names: ["fin_slim_search"] },
|
|
346
353
|
);
|
package/src/db/db.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { mkdirSync } from "node:fs";
|
|
2
|
+
/**
|
|
3
|
+
* SQLite database singleton for OpenFinClaw plugin.
|
|
4
|
+
* Database path: ~/.openfinclaw/workspace/openfinclaw-plugin.db
|
|
5
|
+
*/
|
|
6
|
+
import { createRequire } from "node:module";
|
|
7
|
+
import { homedir } from "node:os";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import type { DatabaseSync } from "node:sqlite";
|
|
10
|
+
import { ensureSchema } from "./schema.js";
|
|
11
|
+
|
|
12
|
+
// Use createRequire to load the built-in node:sqlite in an ESM context.
|
|
13
|
+
const _require = createRequire(import.meta.url);
|
|
14
|
+
|
|
15
|
+
let _db: DatabaseSync | null = null;
|
|
16
|
+
|
|
17
|
+
/** Resolve the database file path under ~/.openfinclaw/workspace/. */
|
|
18
|
+
function resolveDbPath(): string {
|
|
19
|
+
const base = join(homedir(), ".openfinclaw", "workspace");
|
|
20
|
+
mkdirSync(base, { recursive: true });
|
|
21
|
+
return join(base, "openfinclaw-plugin.db");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Get (or lazily initialise) the SQLite database singleton.
|
|
26
|
+
* Calls ensureSchema on first access to create missing tables.
|
|
27
|
+
*/
|
|
28
|
+
export function getDb(): DatabaseSync {
|
|
29
|
+
if (_db) return _db;
|
|
30
|
+
// node:sqlite is available in Node 22+
|
|
31
|
+
const { DatabaseSync } = _require("node:sqlite") as typeof import("node:sqlite");
|
|
32
|
+
const dbPath = resolveDbPath();
|
|
33
|
+
_db = new DatabaseSync(dbPath);
|
|
34
|
+
ensureSchema(_db);
|
|
35
|
+
return _db;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Close the database (used in tests / graceful shutdown). */
|
|
39
|
+
export function closeDb(): void {
|
|
40
|
+
if (_db) {
|
|
41
|
+
_db.close();
|
|
42
|
+
_db = null;
|
|
43
|
+
}
|
|
44
|
+
}
|