@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 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 "openfinclaw/plugin-sdk";
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
 
@@ -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": ["./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.26",
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": "^0.34.0",
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 "openfinclaw/plugin-sdk";
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
  }
@@ -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 "openfinclaw/plugin-sdk";
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(api: OpenClawPluginApi, config: UnifiedPluginConfig): void {
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
- async execute(_id: string, params: Record<string, unknown>) {
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
- async execute(_id: string, params: Record<string, unknown>) {
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
- async execute(_id: string, params: Record<string, unknown>) {
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
- async execute(_id: string, params: Record<string, unknown>) {
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
- async execute(_id: string, params: Record<string, unknown>) {
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
+ }