@akashabot/openclaw-mem 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,125 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import fs from 'node:fs';
4
+ import { v4 as uuidv4 } from 'uuid';
5
+ import { addItem, hybridSearch, initSchema, openDb, searchItems } from '@akasha/openclaw-memory-offline-core';
6
+ const program = new Command();
7
+ program
8
+ .name('openclaw-mem')
9
+ .description('Offline memory (SQLite FTS + optional Ollama embeddings)')
10
+ .option('--db <path>', 'SQLite db path', 'memory.sqlite');
11
+ function withDb(fn) {
12
+ const opts = program.opts();
13
+ return fn(opts.db);
14
+ }
15
+ program
16
+ .command('init')
17
+ .description('Initialize the SQLite database schema')
18
+ .action(() => {
19
+ withDb((dbPath) => {
20
+ const db = openDb(dbPath);
21
+ initSchema(db);
22
+ console.log(JSON.stringify({ ok: true, db: dbPath }));
23
+ });
24
+ });
25
+ function registerAddCommand(name) {
26
+ program
27
+ .command(name)
28
+ .description('Add an item (and update FTS index)')
29
+ .argument('[text]', 'Text content. If omitted, reads from stdin.')
30
+ .option('--id <id>', 'Item id (default: uuid)')
31
+ .option('--title <title>', 'Title')
32
+ .option('--tags <tags>', 'Tags (freeform, e.g. "a,b,c")')
33
+ .option('--source <source>', 'Source (e.g. "whatsapp")')
34
+ .option('--source-id <sourceId>', 'Source id / external id')
35
+ .option('--meta <json>', 'JSON metadata (string)')
36
+ .action((textArg, cmdOpts) => {
37
+ withDb((dbPath) => {
38
+ const db = openDb(dbPath);
39
+ initSchema(db);
40
+ const text = (textArg ?? fs.readFileSync(0, 'utf8')).trim();
41
+ if (!text) {
42
+ process.exitCode = 2;
43
+ console.log(JSON.stringify({ ok: false, error: 'Missing text (arg or stdin)' }));
44
+ return;
45
+ }
46
+ let meta = undefined;
47
+ if (cmdOpts.meta) {
48
+ try {
49
+ meta = JSON.parse(String(cmdOpts.meta));
50
+ }
51
+ catch (e) {
52
+ process.exitCode = 2;
53
+ console.log(JSON.stringify({
54
+ ok: false,
55
+ error: 'Invalid JSON for --meta',
56
+ details: String(e?.message ?? e),
57
+ }));
58
+ return;
59
+ }
60
+ }
61
+ const id = String(cmdOpts.id ?? uuidv4());
62
+ const item = addItem(db, {
63
+ id,
64
+ title: cmdOpts.title ? String(cmdOpts.title) : null,
65
+ text,
66
+ tags: cmdOpts.tags ? String(cmdOpts.tags) : null,
67
+ source: cmdOpts.source ? String(cmdOpts.source) : null,
68
+ source_id: cmdOpts.sourceId ? String(cmdOpts.sourceId) : null,
69
+ meta,
70
+ });
71
+ console.log(JSON.stringify({ ok: true, item }));
72
+ });
73
+ });
74
+ }
75
+ registerAddCommand('add');
76
+ // Back-compat with README/skill naming.
77
+ registerAddCommand('remember');
78
+ function registerSearchCommand(name) {
79
+ program
80
+ .command(name)
81
+ .description('Search items using SQLite FTS5 (bm25), optionally reranked with Ollama embeddings')
82
+ .argument('<query>', 'FTS query (will be minimally escaped)')
83
+ .option('--limit <n>', 'Max results (default 10, max 200)', '10')
84
+ .option('--hybrid', 'Enable semantic rerank using Ollama embeddings', false)
85
+ .option('--candidates <n>', 'How many lexical candidates to rerank in hybrid mode (default max(50,limit))')
86
+ .option('--semantic-weight <w>', 'Hybrid weight for semantic score (0..1, default 0.7)', '0.7')
87
+ .option('--ollama-base-url <url>', 'Ollama baseUrl (OpenAI-compatible). e.g. http://127.0.0.1:11434')
88
+ .option('--embedding-model <id>', 'Embedding model id (default bge-m3)')
89
+ .option('--ollama-timeout-ms <n>', 'Ollama timeout in ms (default 3000)')
90
+ .action(async (query, cmdOpts) => {
91
+ await withDb(async (dbPath) => {
92
+ const db = openDb(dbPath);
93
+ initSchema(db);
94
+ const limit = Math.max(1, Math.min(200, Number(cmdOpts.limit ?? 10)));
95
+ if (!cmdOpts.hybrid) {
96
+ const out = searchItems(db, query, limit);
97
+ console.log(JSON.stringify({ ok: true, mode: 'lexical', ...out }));
98
+ return;
99
+ }
100
+ const candidates = cmdOpts.candidates ? Math.max(limit, Number(cmdOpts.candidates)) : undefined;
101
+ const semanticWeight = Math.max(0, Math.min(1, Number(cmdOpts.semanticWeight ?? 0.7)));
102
+ const results = await hybridSearch(db, {
103
+ dbPath,
104
+ ollamaBaseUrl: cmdOpts.ollamaBaseUrl,
105
+ embeddingModel: cmdOpts.embeddingModel,
106
+ ollamaTimeoutMs: cmdOpts.ollamaTimeoutMs ? Number(cmdOpts.ollamaTimeoutMs) : undefined,
107
+ },
108
+ // IMPORTANT: hybridSearch expects an already-escaped FTS query.
109
+ // We can reuse searchItems() to get the escapedQuery.
110
+ searchItems(db, query, 1).escapedQuery, { topK: limit, candidates, semanticWeight });
111
+ console.log(JSON.stringify({
112
+ ok: true,
113
+ mode: 'hybrid',
114
+ query,
115
+ results,
116
+ embeddingModel: cmdOpts.embeddingModel ?? 'bge-m3',
117
+ ollamaBaseUrl: cmdOpts.ollamaBaseUrl ?? 'http://127.0.0.1:11434',
118
+ }));
119
+ });
120
+ });
121
+ }
122
+ registerSearchCommand('search');
123
+ // Back-compat with README/skill naming.
124
+ registerSearchCommand('recall');
125
+ program.parse();
package/package.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "@akashabot/openclaw-mem",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "bin": {
6
+ "openclaw-mem": "./dist/cli.js"
7
+ },
8
+ "main": "./dist/cli.js",
9
+ "dependencies": {
10
+ "commander": "^12.1.0",
11
+ "uuid": "^10.0.0",
12
+ "@akashabot/openclaw-memory-offline-core": "0.1.0"
13
+ },
14
+ "devDependencies": {
15
+ "@types/uuid": "^10.0.0"
16
+ }
17
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,150 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import fs from 'node:fs';
4
+ import { v4 as uuidv4 } from 'uuid';
5
+ import { addItem, hybridSearch, initSchema, openDb, searchItems } from '@akasha/openclaw-memory-offline-core';
6
+
7
+ const program = new Command();
8
+
9
+ program
10
+ .name('openclaw-mem')
11
+ .description('Offline memory (SQLite FTS + optional Ollama embeddings)')
12
+ .option('--db <path>', 'SQLite db path', 'memory.sqlite');
13
+
14
+ function withDb<T>(fn: (dbPath: string) => T): T {
15
+ const opts = program.opts<{ db: string }>();
16
+ return fn(opts.db);
17
+ }
18
+
19
+ program
20
+ .command('init')
21
+ .description('Initialize the SQLite database schema')
22
+ .action(() => {
23
+ withDb((dbPath) => {
24
+ const db = openDb(dbPath);
25
+ initSchema(db);
26
+ console.log(JSON.stringify({ ok: true, db: dbPath }));
27
+ });
28
+ });
29
+
30
+ function registerAddCommand(name: string) {
31
+ program
32
+ .command(name)
33
+ .description('Add an item (and update FTS index)')
34
+ .argument('[text]', 'Text content. If omitted, reads from stdin.')
35
+ .option('--id <id>', 'Item id (default: uuid)')
36
+ .option('--title <title>', 'Title')
37
+ .option('--tags <tags>', 'Tags (freeform, e.g. "a,b,c")')
38
+ .option('--source <source>', 'Source (e.g. "whatsapp")')
39
+ .option('--source-id <sourceId>', 'Source id / external id')
40
+ .option('--meta <json>', 'JSON metadata (string)')
41
+ .action((textArg: string | undefined, cmdOpts) => {
42
+ withDb((dbPath) => {
43
+ const db = openDb(dbPath);
44
+ initSchema(db);
45
+
46
+ const text = (textArg ?? fs.readFileSync(0, 'utf8')).trim();
47
+ if (!text) {
48
+ process.exitCode = 2;
49
+ console.log(JSON.stringify({ ok: false, error: 'Missing text (arg or stdin)' }));
50
+ return;
51
+ }
52
+
53
+ let meta: unknown = undefined;
54
+ if (cmdOpts.meta) {
55
+ try {
56
+ meta = JSON.parse(String(cmdOpts.meta));
57
+ } catch (e: any) {
58
+ process.exitCode = 2;
59
+ console.log(
60
+ JSON.stringify({
61
+ ok: false,
62
+ error: 'Invalid JSON for --meta',
63
+ details: String(e?.message ?? e),
64
+ })
65
+ );
66
+ return;
67
+ }
68
+ }
69
+
70
+ const id = String(cmdOpts.id ?? uuidv4());
71
+ const item = addItem(db, {
72
+ id,
73
+ title: cmdOpts.title ? String(cmdOpts.title) : null,
74
+ text,
75
+ tags: cmdOpts.tags ? String(cmdOpts.tags) : null,
76
+ source: cmdOpts.source ? String(cmdOpts.source) : null,
77
+ source_id: cmdOpts.sourceId ? String(cmdOpts.sourceId) : null,
78
+ meta,
79
+ });
80
+
81
+ console.log(JSON.stringify({ ok: true, item }));
82
+ });
83
+ });
84
+ }
85
+
86
+ registerAddCommand('add');
87
+ // Back-compat with README/skill naming.
88
+ registerAddCommand('remember');
89
+
90
+ function registerSearchCommand(name: string) {
91
+ program
92
+ .command(name)
93
+ .description('Search items using SQLite FTS5 (bm25), optionally reranked with Ollama embeddings')
94
+ .argument('<query>', 'FTS query (will be minimally escaped)')
95
+ .option('--limit <n>', 'Max results (default 10, max 200)', '10')
96
+ .option('--hybrid', 'Enable semantic rerank using Ollama embeddings', false)
97
+ .option('--candidates <n>', 'How many lexical candidates to rerank in hybrid mode (default max(50,limit))')
98
+ .option('--semantic-weight <w>', 'Hybrid weight for semantic score (0..1, default 0.7)', '0.7')
99
+ .option('--ollama-base-url <url>', 'Ollama baseUrl (OpenAI-compatible). e.g. http://127.0.0.1:11434')
100
+ .option('--embedding-model <id>', 'Embedding model id (default bge-m3)')
101
+ .option('--ollama-timeout-ms <n>', 'Ollama timeout in ms (default 3000)')
102
+ .action(async (query: string, cmdOpts) => {
103
+ await withDb(async (dbPath) => {
104
+ const db = openDb(dbPath);
105
+ initSchema(db);
106
+
107
+ const limit = Math.max(1, Math.min(200, Number(cmdOpts.limit ?? 10)));
108
+
109
+ if (!cmdOpts.hybrid) {
110
+ const out = searchItems(db, query, limit);
111
+ console.log(JSON.stringify({ ok: true, mode: 'lexical', ...out }));
112
+ return;
113
+ }
114
+
115
+ const candidates = cmdOpts.candidates ? Math.max(limit, Number(cmdOpts.candidates)) : undefined;
116
+ const semanticWeight = Math.max(0, Math.min(1, Number(cmdOpts.semanticWeight ?? 0.7)));
117
+
118
+ const results = await hybridSearch(
119
+ db,
120
+ {
121
+ dbPath,
122
+ ollamaBaseUrl: cmdOpts.ollamaBaseUrl,
123
+ embeddingModel: cmdOpts.embeddingModel,
124
+ ollamaTimeoutMs: cmdOpts.ollamaTimeoutMs ? Number(cmdOpts.ollamaTimeoutMs) : undefined,
125
+ },
126
+ // IMPORTANT: hybridSearch expects an already-escaped FTS query.
127
+ // We can reuse searchItems() to get the escapedQuery.
128
+ searchItems(db, query, 1).escapedQuery,
129
+ { topK: limit, candidates, semanticWeight }
130
+ );
131
+
132
+ console.log(
133
+ JSON.stringify({
134
+ ok: true,
135
+ mode: 'hybrid',
136
+ query,
137
+ results,
138
+ embeddingModel: cmdOpts.embeddingModel ?? 'bge-m3',
139
+ ollamaBaseUrl: cmdOpts.ollamaBaseUrl ?? 'http://127.0.0.1:11434',
140
+ })
141
+ );
142
+ });
143
+ });
144
+ }
145
+
146
+ registerSearchCommand('search');
147
+ // Back-compat with README/skill naming.
148
+ registerSearchCommand('recall');
149
+
150
+ program.parse();
package/tsconfig.json ADDED
@@ -0,0 +1,13 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "composite": true,
5
+ "declaration": true,
6
+ "rootDir": "src",
7
+ "outDir": "dist"
8
+ },
9
+ "references": [
10
+ { "path": "../core" }
11
+ ],
12
+ "include": ["src/**/*.ts"]
13
+ }