@akashabot/openclaw-mem 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +93 -11
- package/package.json +25 -17
- package/src/cli.ts +257 -150
- package/tsconfig.json +13 -13
package/dist/cli.js
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
import { Command } from 'commander';
|
|
3
3
|
import fs from 'node:fs';
|
|
4
4
|
import { v4 as uuidv4 } from 'uuid';
|
|
5
|
-
import
|
|
5
|
+
import * as core from '@akashabot/openclaw-memory-offline-core';
|
|
6
|
+
const { addItem, hybridSearch, hybridSearchFiltered, initSchema, openDb, runMigrations, searchItems, getMemoriesByEntity, getMemoriesBySession, getMemoriesByProcess, listEntities, listSessions, } = core;
|
|
6
7
|
const program = new Command();
|
|
7
8
|
program
|
|
8
9
|
.name('openclaw-mem')
|
|
@@ -19,6 +20,7 @@ program
|
|
|
19
20
|
withDb((dbPath) => {
|
|
20
21
|
const db = openDb(dbPath);
|
|
21
22
|
initSchema(db);
|
|
23
|
+
runMigrations(db);
|
|
22
24
|
console.log(JSON.stringify({ ok: true, db: dbPath }));
|
|
23
25
|
});
|
|
24
26
|
});
|
|
@@ -33,10 +35,15 @@ function registerAddCommand(name) {
|
|
|
33
35
|
.option('--source <source>', 'Source (e.g. "whatsapp")')
|
|
34
36
|
.option('--source-id <sourceId>', 'Source id / external id')
|
|
35
37
|
.option('--meta <json>', 'JSON metadata (string)')
|
|
38
|
+
// Phase 1: Attribution & Session
|
|
39
|
+
.option('--entity-id <entityId>', 'Who said/wrote this (e.g. "loic", "system")')
|
|
40
|
+
.option('--process-id <processId>', 'Which agent/process captured this (e.g. "akasha")')
|
|
41
|
+
.option('--session-id <sessionId>', 'Session/conversation grouping')
|
|
36
42
|
.action((textArg, cmdOpts) => {
|
|
37
43
|
withDb((dbPath) => {
|
|
38
44
|
const db = openDb(dbPath);
|
|
39
45
|
initSchema(db);
|
|
46
|
+
runMigrations(db);
|
|
40
47
|
const text = (textArg ?? fs.readFileSync(0, 'utf8')).trim();
|
|
41
48
|
if (!text) {
|
|
42
49
|
process.exitCode = 2;
|
|
@@ -67,6 +74,10 @@ function registerAddCommand(name) {
|
|
|
67
74
|
source: cmdOpts.source ? String(cmdOpts.source) : null,
|
|
68
75
|
source_id: cmdOpts.sourceId ? String(cmdOpts.sourceId) : null,
|
|
69
76
|
meta,
|
|
77
|
+
// Phase 1: Attribution & Session
|
|
78
|
+
entity_id: cmdOpts.entityId ? String(cmdOpts.entityId) : null,
|
|
79
|
+
process_id: cmdOpts.processId ? String(cmdOpts.processId) : null,
|
|
80
|
+
session_id: cmdOpts.sessionId ? String(cmdOpts.sessionId) : null,
|
|
70
81
|
});
|
|
71
82
|
console.log(JSON.stringify({ ok: true, item }));
|
|
72
83
|
});
|
|
@@ -87,11 +98,23 @@ function registerSearchCommand(name) {
|
|
|
87
98
|
.option('--ollama-base-url <url>', 'Ollama baseUrl (OpenAI-compatible). e.g. http://127.0.0.1:11434')
|
|
88
99
|
.option('--embedding-model <id>', 'Embedding model id (default bge-m3)')
|
|
89
100
|
.option('--ollama-timeout-ms <n>', 'Ollama timeout in ms (default 3000)')
|
|
101
|
+
// Phase 1: Filtering
|
|
102
|
+
.option('--entity-id <entityId>', 'Filter by entity (who said/wrote)')
|
|
103
|
+
.option('--process-id <processId>', 'Filter by process (which agent captured)')
|
|
104
|
+
.option('--session-id <sessionId>', 'Filter by session/conversation')
|
|
90
105
|
.action(async (query, cmdOpts) => {
|
|
91
106
|
await withDb(async (dbPath) => {
|
|
92
107
|
const db = openDb(dbPath);
|
|
93
108
|
initSchema(db);
|
|
109
|
+
runMigrations(db);
|
|
94
110
|
const limit = Math.max(1, Math.min(200, Number(cmdOpts.limit ?? 10)));
|
|
111
|
+
// Check if any filter is specified
|
|
112
|
+
const hasFilter = cmdOpts.entityId || cmdOpts.processId || cmdOpts.sessionId;
|
|
113
|
+
const filter = hasFilter ? {
|
|
114
|
+
entity_id: cmdOpts.entityId || undefined,
|
|
115
|
+
process_id: cmdOpts.processId || undefined,
|
|
116
|
+
session_id: cmdOpts.sessionId || undefined,
|
|
117
|
+
} : undefined;
|
|
95
118
|
if (!cmdOpts.hybrid) {
|
|
96
119
|
const out = searchItems(db, query, limit);
|
|
97
120
|
console.log(JSON.stringify({ ok: true, mode: 'lexical', ...out }));
|
|
@@ -99,19 +122,25 @@ function registerSearchCommand(name) {
|
|
|
99
122
|
}
|
|
100
123
|
const candidates = cmdOpts.candidates ? Math.max(limit, Number(cmdOpts.candidates)) : undefined;
|
|
101
124
|
const semanticWeight = Math.max(0, Math.min(1, Number(cmdOpts.semanticWeight ?? 0.7)));
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
125
|
+
// Use filtered search if filter options are specified
|
|
126
|
+
const results = await (hasFilter
|
|
127
|
+
? hybridSearchFiltered(db, {
|
|
128
|
+
dbPath,
|
|
129
|
+
ollamaBaseUrl: cmdOpts.ollamaBaseUrl,
|
|
130
|
+
embeddingModel: cmdOpts.embeddingModel,
|
|
131
|
+
ollamaTimeoutMs: cmdOpts.ollamaTimeoutMs ? Number(cmdOpts.ollamaTimeoutMs) : undefined,
|
|
132
|
+
}, searchItems(db, query, 1).escapedQuery, { topK: limit, candidates, semanticWeight, filter })
|
|
133
|
+
: hybridSearch(db, {
|
|
134
|
+
dbPath,
|
|
135
|
+
ollamaBaseUrl: cmdOpts.ollamaBaseUrl,
|
|
136
|
+
embeddingModel: cmdOpts.embeddingModel,
|
|
137
|
+
ollamaTimeoutMs: cmdOpts.ollamaTimeoutMs ? Number(cmdOpts.ollamaTimeoutMs) : undefined,
|
|
138
|
+
}, searchItems(db, query, 1).escapedQuery, { topK: limit, candidates, semanticWeight }));
|
|
111
139
|
console.log(JSON.stringify({
|
|
112
140
|
ok: true,
|
|
113
|
-
mode: 'hybrid',
|
|
141
|
+
mode: hasFilter ? 'hybrid-filtered' : 'hybrid',
|
|
114
142
|
query,
|
|
143
|
+
filter: hasFilter ? filter : undefined,
|
|
115
144
|
results,
|
|
116
145
|
embeddingModel: cmdOpts.embeddingModel ?? 'bge-m3',
|
|
117
146
|
ollamaBaseUrl: cmdOpts.ollamaBaseUrl ?? 'http://127.0.0.1:11434',
|
|
@@ -122,4 +151,57 @@ function registerSearchCommand(name) {
|
|
|
122
151
|
registerSearchCommand('search');
|
|
123
152
|
// Back-compat with README/skill naming.
|
|
124
153
|
registerSearchCommand('recall');
|
|
154
|
+
// Phase 1: Utility commands for attribution & session
|
|
155
|
+
program
|
|
156
|
+
.command('list-entities')
|
|
157
|
+
.description('List all distinct entity_ids in the database')
|
|
158
|
+
.action(() => {
|
|
159
|
+
withDb((dbPath) => {
|
|
160
|
+
const db = openDb(dbPath);
|
|
161
|
+
initSchema(db);
|
|
162
|
+
runMigrations(db);
|
|
163
|
+
const entities = listEntities(db);
|
|
164
|
+
console.log(JSON.stringify({ ok: true, entities, count: entities.length }));
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
program
|
|
168
|
+
.command('list-sessions')
|
|
169
|
+
.description('List all distinct session_ids in the database')
|
|
170
|
+
.action(() => {
|
|
171
|
+
withDb((dbPath) => {
|
|
172
|
+
const db = openDb(dbPath);
|
|
173
|
+
initSchema(db);
|
|
174
|
+
runMigrations(db);
|
|
175
|
+
const sessions = listSessions(db);
|
|
176
|
+
console.log(JSON.stringify({ ok: true, sessions, count: sessions.length }));
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
program
|
|
180
|
+
.command('get-by-entity <entityId>')
|
|
181
|
+
.description('Get all memories from a specific entity')
|
|
182
|
+
.option('--limit <n>', 'Max results (default 50)', '50')
|
|
183
|
+
.action((entityId, cmdOpts) => {
|
|
184
|
+
withDb((dbPath) => {
|
|
185
|
+
const db = openDb(dbPath);
|
|
186
|
+
initSchema(db);
|
|
187
|
+
runMigrations(db);
|
|
188
|
+
const limit = Math.max(1, Math.min(500, Number(cmdOpts.limit ?? 50)));
|
|
189
|
+
const items = getMemoriesByEntity(db, entityId, limit);
|
|
190
|
+
console.log(JSON.stringify({ ok: true, entityId, count: items.length, items }));
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
program
|
|
194
|
+
.command('get-by-session <sessionId>')
|
|
195
|
+
.description('Get all memories from a specific session/conversation')
|
|
196
|
+
.option('--limit <n>', 'Max results (default 100)', '100')
|
|
197
|
+
.action((sessionId, cmdOpts) => {
|
|
198
|
+
withDb((dbPath) => {
|
|
199
|
+
const db = openDb(dbPath);
|
|
200
|
+
initSchema(db);
|
|
201
|
+
runMigrations(db);
|
|
202
|
+
const limit = Math.max(1, Math.min(500, Number(cmdOpts.limit ?? 100)));
|
|
203
|
+
const items = getMemoriesBySession(db, sessionId, limit);
|
|
204
|
+
console.log(JSON.stringify({ ok: true, sessionId, count: items.length, items }));
|
|
205
|
+
});
|
|
206
|
+
});
|
|
125
207
|
program.parse();
|
package/package.json
CHANGED
|
@@ -1,17 +1,25 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@akashabot/openclaw-mem",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"type": "module",
|
|
5
|
-
"
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
"
|
|
10
|
-
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "@akashabot/openclaw-mem",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "CLI for OpenClaw Offline Memory (SQLite FTS + optional embeddings)",
|
|
6
|
+
"bin": {
|
|
7
|
+
"openclaw-mem": "./dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "./dist/cli.js",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/AkashaBot/openclaw-memory-offline-sqlite.git",
|
|
13
|
+
"directory": "packages/cli"
|
|
14
|
+
},
|
|
15
|
+
"author": "AkashaBot",
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"commander": "^12.1.0",
|
|
19
|
+
"uuid": "^10.0.0",
|
|
20
|
+
"@akashabot/openclaw-memory-offline-core": "^0.2.0"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@types/uuid": "^10.0.0"
|
|
24
|
+
}
|
|
25
|
+
}
|
package/src/cli.ts
CHANGED
|
@@ -1,150 +1,257 @@
|
|
|
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
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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 * as core from '@akashabot/openclaw-memory-offline-core';
|
|
6
|
+
|
|
7
|
+
const {
|
|
8
|
+
addItem,
|
|
9
|
+
hybridSearch,
|
|
10
|
+
hybridSearchFiltered,
|
|
11
|
+
initSchema,
|
|
12
|
+
openDb,
|
|
13
|
+
runMigrations,
|
|
14
|
+
searchItems,
|
|
15
|
+
getMemoriesByEntity,
|
|
16
|
+
getMemoriesBySession,
|
|
17
|
+
getMemoriesByProcess,
|
|
18
|
+
listEntities,
|
|
19
|
+
listSessions,
|
|
20
|
+
} = core;
|
|
21
|
+
|
|
22
|
+
const program = new Command();
|
|
23
|
+
|
|
24
|
+
program
|
|
25
|
+
.name('openclaw-mem')
|
|
26
|
+
.description('Offline memory (SQLite FTS + optional Ollama embeddings)')
|
|
27
|
+
.option('--db <path>', 'SQLite db path', 'memory.sqlite');
|
|
28
|
+
|
|
29
|
+
function withDb<T>(fn: (dbPath: string) => T): T {
|
|
30
|
+
const opts = program.opts<{ db: string }>();
|
|
31
|
+
return fn(opts.db);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
program
|
|
35
|
+
.command('init')
|
|
36
|
+
.description('Initialize the SQLite database schema')
|
|
37
|
+
.action(() => {
|
|
38
|
+
withDb((dbPath) => {
|
|
39
|
+
const db = openDb(dbPath);
|
|
40
|
+
initSchema(db);
|
|
41
|
+
runMigrations(db);
|
|
42
|
+
console.log(JSON.stringify({ ok: true, db: dbPath }));
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
function registerAddCommand(name: string) {
|
|
47
|
+
program
|
|
48
|
+
.command(name)
|
|
49
|
+
.description('Add an item (and update FTS index)')
|
|
50
|
+
.argument('[text]', 'Text content. If omitted, reads from stdin.')
|
|
51
|
+
.option('--id <id>', 'Item id (default: uuid)')
|
|
52
|
+
.option('--title <title>', 'Title')
|
|
53
|
+
.option('--tags <tags>', 'Tags (freeform, e.g. "a,b,c")')
|
|
54
|
+
.option('--source <source>', 'Source (e.g. "whatsapp")')
|
|
55
|
+
.option('--source-id <sourceId>', 'Source id / external id')
|
|
56
|
+
.option('--meta <json>', 'JSON metadata (string)')
|
|
57
|
+
// Phase 1: Attribution & Session
|
|
58
|
+
.option('--entity-id <entityId>', 'Who said/wrote this (e.g. "loic", "system")')
|
|
59
|
+
.option('--process-id <processId>', 'Which agent/process captured this (e.g. "akasha")')
|
|
60
|
+
.option('--session-id <sessionId>', 'Session/conversation grouping')
|
|
61
|
+
.action((textArg: string | undefined, cmdOpts) => {
|
|
62
|
+
withDb((dbPath) => {
|
|
63
|
+
const db = openDb(dbPath);
|
|
64
|
+
initSchema(db);
|
|
65
|
+
runMigrations(db);
|
|
66
|
+
|
|
67
|
+
const text = (textArg ?? fs.readFileSync(0, 'utf8')).trim();
|
|
68
|
+
if (!text) {
|
|
69
|
+
process.exitCode = 2;
|
|
70
|
+
console.log(JSON.stringify({ ok: false, error: 'Missing text (arg or stdin)' }));
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
let meta: unknown = undefined;
|
|
75
|
+
if (cmdOpts.meta) {
|
|
76
|
+
try {
|
|
77
|
+
meta = JSON.parse(String(cmdOpts.meta));
|
|
78
|
+
} catch (e: any) {
|
|
79
|
+
process.exitCode = 2;
|
|
80
|
+
console.log(
|
|
81
|
+
JSON.stringify({
|
|
82
|
+
ok: false,
|
|
83
|
+
error: 'Invalid JSON for --meta',
|
|
84
|
+
details: String(e?.message ?? e),
|
|
85
|
+
})
|
|
86
|
+
);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const id = String(cmdOpts.id ?? uuidv4());
|
|
92
|
+
const item = addItem(db, {
|
|
93
|
+
id,
|
|
94
|
+
title: cmdOpts.title ? String(cmdOpts.title) : null,
|
|
95
|
+
text,
|
|
96
|
+
tags: cmdOpts.tags ? String(cmdOpts.tags) : null,
|
|
97
|
+
source: cmdOpts.source ? String(cmdOpts.source) : null,
|
|
98
|
+
source_id: cmdOpts.sourceId ? String(cmdOpts.sourceId) : null,
|
|
99
|
+
meta,
|
|
100
|
+
// Phase 1: Attribution & Session
|
|
101
|
+
entity_id: cmdOpts.entityId ? String(cmdOpts.entityId) : null,
|
|
102
|
+
process_id: cmdOpts.processId ? String(cmdOpts.processId) : null,
|
|
103
|
+
session_id: cmdOpts.sessionId ? String(cmdOpts.sessionId) : null,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
console.log(JSON.stringify({ ok: true, item }));
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
registerAddCommand('add');
|
|
112
|
+
// Back-compat with README/skill naming.
|
|
113
|
+
registerAddCommand('remember');
|
|
114
|
+
|
|
115
|
+
function registerSearchCommand(name: string) {
|
|
116
|
+
program
|
|
117
|
+
.command(name)
|
|
118
|
+
.description('Search items using SQLite FTS5 (bm25), optionally reranked with Ollama embeddings')
|
|
119
|
+
.argument('<query>', 'FTS query (will be minimally escaped)')
|
|
120
|
+
.option('--limit <n>', 'Max results (default 10, max 200)', '10')
|
|
121
|
+
.option('--hybrid', 'Enable semantic rerank using Ollama embeddings', false)
|
|
122
|
+
.option('--candidates <n>', 'How many lexical candidates to rerank in hybrid mode (default max(50,limit))')
|
|
123
|
+
.option('--semantic-weight <w>', 'Hybrid weight for semantic score (0..1, default 0.7)', '0.7')
|
|
124
|
+
.option('--ollama-base-url <url>', 'Ollama baseUrl (OpenAI-compatible). e.g. http://127.0.0.1:11434')
|
|
125
|
+
.option('--embedding-model <id>', 'Embedding model id (default bge-m3)')
|
|
126
|
+
.option('--ollama-timeout-ms <n>', 'Ollama timeout in ms (default 3000)')
|
|
127
|
+
// Phase 1: Filtering
|
|
128
|
+
.option('--entity-id <entityId>', 'Filter by entity (who said/wrote)')
|
|
129
|
+
.option('--process-id <processId>', 'Filter by process (which agent captured)')
|
|
130
|
+
.option('--session-id <sessionId>', 'Filter by session/conversation')
|
|
131
|
+
.action(async (query: string, cmdOpts) => {
|
|
132
|
+
await withDb(async (dbPath) => {
|
|
133
|
+
const db = openDb(dbPath);
|
|
134
|
+
initSchema(db);
|
|
135
|
+
runMigrations(db);
|
|
136
|
+
|
|
137
|
+
const limit = Math.max(1, Math.min(200, Number(cmdOpts.limit ?? 10)));
|
|
138
|
+
|
|
139
|
+
// Check if any filter is specified
|
|
140
|
+
const hasFilter = cmdOpts.entityId || cmdOpts.processId || cmdOpts.sessionId;
|
|
141
|
+
const filter = hasFilter ? {
|
|
142
|
+
entity_id: cmdOpts.entityId || undefined,
|
|
143
|
+
process_id: cmdOpts.processId || undefined,
|
|
144
|
+
session_id: cmdOpts.sessionId || undefined,
|
|
145
|
+
} : undefined;
|
|
146
|
+
|
|
147
|
+
if (!cmdOpts.hybrid) {
|
|
148
|
+
const out = searchItems(db, query, limit);
|
|
149
|
+
console.log(JSON.stringify({ ok: true, mode: 'lexical', ...out }));
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const candidates = cmdOpts.candidates ? Math.max(limit, Number(cmdOpts.candidates)) : undefined;
|
|
154
|
+
const semanticWeight = Math.max(0, Math.min(1, Number(cmdOpts.semanticWeight ?? 0.7)));
|
|
155
|
+
|
|
156
|
+
// Use filtered search if filter options are specified
|
|
157
|
+
const results = await (hasFilter
|
|
158
|
+
? hybridSearchFiltered(
|
|
159
|
+
db,
|
|
160
|
+
{
|
|
161
|
+
dbPath,
|
|
162
|
+
ollamaBaseUrl: cmdOpts.ollamaBaseUrl,
|
|
163
|
+
embeddingModel: cmdOpts.embeddingModel,
|
|
164
|
+
ollamaTimeoutMs: cmdOpts.ollamaTimeoutMs ? Number(cmdOpts.ollamaTimeoutMs) : undefined,
|
|
165
|
+
},
|
|
166
|
+
searchItems(db, query, 1).escapedQuery,
|
|
167
|
+
{ topK: limit, candidates, semanticWeight, filter }
|
|
168
|
+
)
|
|
169
|
+
: hybridSearch(
|
|
170
|
+
db,
|
|
171
|
+
{
|
|
172
|
+
dbPath,
|
|
173
|
+
ollamaBaseUrl: cmdOpts.ollamaBaseUrl,
|
|
174
|
+
embeddingModel: cmdOpts.embeddingModel,
|
|
175
|
+
ollamaTimeoutMs: cmdOpts.ollamaTimeoutMs ? Number(cmdOpts.ollamaTimeoutMs) : undefined,
|
|
176
|
+
},
|
|
177
|
+
searchItems(db, query, 1).escapedQuery,
|
|
178
|
+
{ topK: limit, candidates, semanticWeight }
|
|
179
|
+
));
|
|
180
|
+
|
|
181
|
+
console.log(
|
|
182
|
+
JSON.stringify({
|
|
183
|
+
ok: true,
|
|
184
|
+
mode: hasFilter ? 'hybrid-filtered' : 'hybrid',
|
|
185
|
+
query,
|
|
186
|
+
filter: hasFilter ? filter : undefined,
|
|
187
|
+
results,
|
|
188
|
+
embeddingModel: cmdOpts.embeddingModel ?? 'bge-m3',
|
|
189
|
+
ollamaBaseUrl: cmdOpts.ollamaBaseUrl ?? 'http://127.0.0.1:11434',
|
|
190
|
+
})
|
|
191
|
+
);
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
registerSearchCommand('search');
|
|
197
|
+
// Back-compat with README/skill naming.
|
|
198
|
+
registerSearchCommand('recall');
|
|
199
|
+
|
|
200
|
+
// Phase 1: Utility commands for attribution & session
|
|
201
|
+
program
|
|
202
|
+
.command('list-entities')
|
|
203
|
+
.description('List all distinct entity_ids in the database')
|
|
204
|
+
.action(() => {
|
|
205
|
+
withDb((dbPath) => {
|
|
206
|
+
const db = openDb(dbPath);
|
|
207
|
+
initSchema(db);
|
|
208
|
+
runMigrations(db);
|
|
209
|
+
const entities = listEntities(db);
|
|
210
|
+
console.log(JSON.stringify({ ok: true, entities, count: entities.length }));
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
program
|
|
215
|
+
.command('list-sessions')
|
|
216
|
+
.description('List all distinct session_ids in the database')
|
|
217
|
+
.action(() => {
|
|
218
|
+
withDb((dbPath) => {
|
|
219
|
+
const db = openDb(dbPath);
|
|
220
|
+
initSchema(db);
|
|
221
|
+
runMigrations(db);
|
|
222
|
+
const sessions = listSessions(db);
|
|
223
|
+
console.log(JSON.stringify({ ok: true, sessions, count: sessions.length }));
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
program
|
|
228
|
+
.command('get-by-entity <entityId>')
|
|
229
|
+
.description('Get all memories from a specific entity')
|
|
230
|
+
.option('--limit <n>', 'Max results (default 50)', '50')
|
|
231
|
+
.action((entityId: string, cmdOpts) => {
|
|
232
|
+
withDb((dbPath) => {
|
|
233
|
+
const db = openDb(dbPath);
|
|
234
|
+
initSchema(db);
|
|
235
|
+
runMigrations(db);
|
|
236
|
+
const limit = Math.max(1, Math.min(500, Number(cmdOpts.limit ?? 50)));
|
|
237
|
+
const items = getMemoriesByEntity(db, entityId, limit);
|
|
238
|
+
console.log(JSON.stringify({ ok: true, entityId, count: items.length, items }));
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
program
|
|
243
|
+
.command('get-by-session <sessionId>')
|
|
244
|
+
.description('Get all memories from a specific session/conversation')
|
|
245
|
+
.option('--limit <n>', 'Max results (default 100)', '100')
|
|
246
|
+
.action((sessionId: string, cmdOpts) => {
|
|
247
|
+
withDb((dbPath) => {
|
|
248
|
+
const db = openDb(dbPath);
|
|
249
|
+
initSchema(db);
|
|
250
|
+
runMigrations(db);
|
|
251
|
+
const limit = Math.max(1, Math.min(500, Number(cmdOpts.limit ?? 100)));
|
|
252
|
+
const items = getMemoriesBySession(db, sessionId, limit);
|
|
253
|
+
console.log(JSON.stringify({ ok: true, sessionId, count: items.length, items }));
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
program.parse();
|
package/tsconfig.json
CHANGED
|
@@ -1,13 +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
|
-
}
|
|
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
|
+
}
|