jasper-recall 0.5.5 → 0.5.7
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/cli/config.js +0 -0
- package/cli/digest-sessions.js +233 -0
- package/cli/doctor.js +0 -0
- package/cli/index-digests.js +47 -0
- package/cli/recall.js +47 -0
- package/cli/server.js +0 -0
- package/cli/update-check.js +0 -0
- package/extensions/jasper-recall/index.ts +85 -22
- package/package.json +5 -2
package/cli/config.js
CHANGED
|
File without changes
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* digest-sessions — Extract summaries from OpenClaw session logs
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* npx jasper-recall digest-sessions [--all] [--recent N] [--dry-run]
|
|
7
|
+
* digest-sessions [--all] [--recent N] [--dry-run]
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const os = require('os');
|
|
13
|
+
const readline = require('readline');
|
|
14
|
+
|
|
15
|
+
// Config with environment overrides
|
|
16
|
+
const WORKSPACE = process.env.RECALL_WORKSPACE || path.join(os.homedir(), '.openclaw', 'workspace');
|
|
17
|
+
const SESSIONS_DIR = process.env.RECALL_SESSIONS_DIR || path.join(os.homedir(), '.openclaw', 'agents', 'main', 'sessions');
|
|
18
|
+
const MEMORY_DIR = path.join(WORKSPACE, 'memory');
|
|
19
|
+
const DIGEST_DIR = path.join(MEMORY_DIR, 'session-digests');
|
|
20
|
+
const STATE_FILE = path.join(MEMORY_DIR, '.digest-state.json');
|
|
21
|
+
|
|
22
|
+
// Parse args
|
|
23
|
+
const args = process.argv.slice(2);
|
|
24
|
+
const DRY_RUN = args.includes('--dry-run');
|
|
25
|
+
const ALL = args.includes('--all');
|
|
26
|
+
const recentIdx = args.indexOf('--recent');
|
|
27
|
+
const RECENT = recentIdx !== -1 ? parseInt(args[recentIdx + 1], 10) : null;
|
|
28
|
+
|
|
29
|
+
// Patterns to filter out from topics
|
|
30
|
+
const SKIP_PATTERNS = [
|
|
31
|
+
/^\[message_id:/,
|
|
32
|
+
/^System:/,
|
|
33
|
+
/^\{/,
|
|
34
|
+
/^<session-init>/,
|
|
35
|
+
/^<session-identity>/,
|
|
36
|
+
/^<relevant-memories>/,
|
|
37
|
+
/^🔄 \*\*Fresh session/,
|
|
38
|
+
/^Read HEARTBEAT\.md/,
|
|
39
|
+
/^HEARTBEAT_OK/,
|
|
40
|
+
/^NO_REPLY/,
|
|
41
|
+
/^ANNOUNCE_SKIP/,
|
|
42
|
+
/^Agent-to-agent/,
|
|
43
|
+
/^📋 \*\*PR Review/,
|
|
44
|
+
/^🤖 Codex/,
|
|
45
|
+
/^✅ \*\*Hourly/,
|
|
46
|
+
/^The following memories/,
|
|
47
|
+
/^- \[memory\//,
|
|
48
|
+
/^###\s+(IDENTITY|SOUL|USER)\.md/,
|
|
49
|
+
/^cat ~/,
|
|
50
|
+
/^```/,
|
|
51
|
+
/^---$/,
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
function shouldSkip(line) {
|
|
55
|
+
const trimmed = line.trim();
|
|
56
|
+
if (!trimmed || trimmed.length < 5) return true;
|
|
57
|
+
return SKIP_PATTERNS.some(p => p.test(trimmed));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function readState() {
|
|
61
|
+
try {
|
|
62
|
+
if (fs.existsSync(STATE_FILE)) {
|
|
63
|
+
return JSON.parse(fs.readFileSync(STATE_FILE, 'utf8'));
|
|
64
|
+
}
|
|
65
|
+
} catch {}
|
|
66
|
+
return { processed: [], lastRun: 0 };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function saveState(state) {
|
|
70
|
+
state.lastRun = Date.now();
|
|
71
|
+
fs.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function parseSession(sessionFile) {
|
|
75
|
+
const topics = [];
|
|
76
|
+
const toolCounts = {};
|
|
77
|
+
let messageCount = 0;
|
|
78
|
+
|
|
79
|
+
const rl = readline.createInterface({
|
|
80
|
+
input: fs.createReadStream(sessionFile),
|
|
81
|
+
crlfDelay: Infinity
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
for await (const line of rl) {
|
|
85
|
+
try {
|
|
86
|
+
const entry = JSON.parse(line);
|
|
87
|
+
messageCount++;
|
|
88
|
+
|
|
89
|
+
// Extract user messages for topics
|
|
90
|
+
if (entry.message?.role === 'user') {
|
|
91
|
+
let content = entry.message.content;
|
|
92
|
+
|
|
93
|
+
// Handle array content (multi-part messages)
|
|
94
|
+
if (Array.isArray(content)) {
|
|
95
|
+
content = content
|
|
96
|
+
.filter(p => p.type === 'text')
|
|
97
|
+
.map(p => p.text)
|
|
98
|
+
.join(' ');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (typeof content === 'string') {
|
|
102
|
+
// Split into lines and filter
|
|
103
|
+
const lines = content.split('\n');
|
|
104
|
+
for (const l of lines) {
|
|
105
|
+
if (!shouldSkip(l) && topics.length < 20) {
|
|
106
|
+
topics.push(l.trim().slice(0, 200));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Count tool usage
|
|
113
|
+
if (entry.message?.role === 'assistant' && Array.isArray(entry.message.content)) {
|
|
114
|
+
for (const part of entry.message.content) {
|
|
115
|
+
if (part.type === 'toolCall' || part.type === 'tool_use') {
|
|
116
|
+
const name = part.name || part.toolName || 'unknown';
|
|
117
|
+
toolCounts[name] = (toolCounts[name] || 0) + 1;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
} catch {
|
|
122
|
+
// Skip malformed lines
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Sort tools by usage
|
|
127
|
+
const tools = Object.entries(toolCounts)
|
|
128
|
+
.sort((a, b) => b[1] - a[1])
|
|
129
|
+
.slice(0, 5)
|
|
130
|
+
.map(([name, count]) => `${name} (${count}x)`)
|
|
131
|
+
.join(', ');
|
|
132
|
+
|
|
133
|
+
return { topics: topics.slice(0, 10), tools: tools || 'none', messageCount };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async function main() {
|
|
137
|
+
// Ensure directories exist
|
|
138
|
+
fs.mkdirSync(DIGEST_DIR, { recursive: true });
|
|
139
|
+
|
|
140
|
+
// Check sessions dir
|
|
141
|
+
if (!fs.existsSync(SESSIONS_DIR)) {
|
|
142
|
+
console.log(`⚠ Sessions directory not found: ${SESSIONS_DIR}`);
|
|
143
|
+
process.exit(0);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Get session files
|
|
147
|
+
const sessionFiles = fs.readdirSync(SESSIONS_DIR)
|
|
148
|
+
.filter(f => f.endsWith('.jsonl'))
|
|
149
|
+
.map(f => f.replace('.jsonl', ''));
|
|
150
|
+
|
|
151
|
+
if (sessionFiles.length === 0) {
|
|
152
|
+
console.log('No session files found.');
|
|
153
|
+
process.exit(0);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Load state
|
|
157
|
+
const state = await readState();
|
|
158
|
+
const processed = new Set(state.processed);
|
|
159
|
+
|
|
160
|
+
// Filter to new sessions (unless --all)
|
|
161
|
+
let toProcess = ALL
|
|
162
|
+
? sessionFiles
|
|
163
|
+
: sessionFiles.filter(s => !processed.has(s));
|
|
164
|
+
|
|
165
|
+
// Apply --recent limit
|
|
166
|
+
if (RECENT && RECENT > 0) {
|
|
167
|
+
toProcess = toProcess.slice(-RECENT);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (toProcess.length === 0) {
|
|
171
|
+
console.log('✓ No new sessions to digest.');
|
|
172
|
+
process.exit(0);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
console.log('🦊 Jasper Recall — Session Digester');
|
|
176
|
+
console.log('='.repeat(40));
|
|
177
|
+
console.log(`Sessions to process: ${toProcess.length}\n`);
|
|
178
|
+
|
|
179
|
+
for (const sessionId of toProcess) {
|
|
180
|
+
const sessionFile = path.join(SESSIONS_DIR, `${sessionId}.jsonl`);
|
|
181
|
+
if (!fs.existsSync(sessionFile)) continue;
|
|
182
|
+
|
|
183
|
+
const stats = fs.statSync(sessionFile);
|
|
184
|
+
const size = (stats.size / 1024).toFixed(0) + 'K';
|
|
185
|
+
const date = stats.mtime.toISOString().split('T')[0];
|
|
186
|
+
|
|
187
|
+
console.log(`Processing: ${sessionId.slice(0, 8)}... (${size})`);
|
|
188
|
+
|
|
189
|
+
try {
|
|
190
|
+
const { topics, tools, messageCount } = await parseSession(sessionFile);
|
|
191
|
+
|
|
192
|
+
const digestFile = path.join(DIGEST_DIR, `${sessionId.slice(0, 8)}-${date}.md`);
|
|
193
|
+
|
|
194
|
+
const topicsFormatted = topics.length > 0
|
|
195
|
+
? topics.map(t => `- ${t}`).join('\n')
|
|
196
|
+
: '- (no topics extracted)';
|
|
197
|
+
|
|
198
|
+
const content = `# Session ${sessionId.slice(0, 8)} — ${date}
|
|
199
|
+
|
|
200
|
+
**Size:** ${size} | **Messages:** ${messageCount}
|
|
201
|
+
**Tools:** ${tools}
|
|
202
|
+
|
|
203
|
+
## Topics
|
|
204
|
+
|
|
205
|
+
${topicsFormatted}
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
*Full session: ${sessionFile}*
|
|
209
|
+
`;
|
|
210
|
+
|
|
211
|
+
if (!DRY_RUN) {
|
|
212
|
+
fs.writeFileSync(digestFile, content);
|
|
213
|
+
state.processed.push(sessionId);
|
|
214
|
+
console.log(` ✓ Created: ${path.basename(digestFile)}`);
|
|
215
|
+
} else {
|
|
216
|
+
console.log(` [dry-run] Would create: ${path.basename(digestFile)}`);
|
|
217
|
+
}
|
|
218
|
+
} catch (err) {
|
|
219
|
+
console.log(` ✗ Error: ${err.message}`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (!DRY_RUN) {
|
|
224
|
+
saveState(state);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
console.log(`\n✓ Digests saved to: ${DIGEST_DIR}`);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
main().catch(err => {
|
|
231
|
+
console.error('Error:', err.message);
|
|
232
|
+
process.exit(1);
|
|
233
|
+
});
|
package/cli/doctor.js
CHANGED
|
File without changes
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* index-digests — Index memory files into ChromaDB
|
|
4
|
+
* Wrapper for the Python script
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { execSync, spawn } = require('child_process');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const os = require('os');
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
|
|
12
|
+
const VENV_PATH = path.join(os.homedir(), '.openclaw', 'rag-env');
|
|
13
|
+
const PYTHON = path.join(VENV_PATH, 'bin', 'python');
|
|
14
|
+
|
|
15
|
+
// Find the Python script - check multiple locations
|
|
16
|
+
const SCRIPT_LOCATIONS = [
|
|
17
|
+
path.join(__dirname, '..', 'scripts', 'index-digests.py'),
|
|
18
|
+
path.join(os.homedir(), '.local', 'share', 'jasper-recall', 'scripts', 'index-digests.py'),
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
let scriptPath = null;
|
|
22
|
+
for (const loc of SCRIPT_LOCATIONS) {
|
|
23
|
+
if (fs.existsSync(loc)) {
|
|
24
|
+
scriptPath = loc;
|
|
25
|
+
break;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!scriptPath) {
|
|
30
|
+
console.error('❌ index-digests.py not found. Run: npx jasper-recall setup');
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (!fs.existsSync(PYTHON)) {
|
|
35
|
+
console.error('❌ Python venv not found. Run: npx jasper-recall setup');
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Run the Python script
|
|
40
|
+
const child = spawn(PYTHON, [scriptPath, ...process.argv.slice(2)], {
|
|
41
|
+
stdio: 'inherit',
|
|
42
|
+
env: { ...process.env }
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
child.on('exit', (code) => {
|
|
46
|
+
process.exit(code || 0);
|
|
47
|
+
});
|
package/cli/recall.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* recall — Semantic search over indexed memory
|
|
4
|
+
* Wrapper for the Python script
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { spawn } = require('child_process');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const os = require('os');
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
|
|
12
|
+
const VENV_PATH = path.join(os.homedir(), '.openclaw', 'rag-env');
|
|
13
|
+
const PYTHON = path.join(VENV_PATH, 'bin', 'python');
|
|
14
|
+
|
|
15
|
+
// Find the Python script - check multiple locations
|
|
16
|
+
const SCRIPT_LOCATIONS = [
|
|
17
|
+
path.join(__dirname, '..', 'scripts', 'recall.py'),
|
|
18
|
+
path.join(os.homedir(), '.local', 'share', 'jasper-recall', 'scripts', 'recall.py'),
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
let scriptPath = null;
|
|
22
|
+
for (const loc of SCRIPT_LOCATIONS) {
|
|
23
|
+
if (fs.existsSync(loc)) {
|
|
24
|
+
scriptPath = loc;
|
|
25
|
+
break;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!scriptPath) {
|
|
30
|
+
console.error('❌ recall.py not found. Run: npx jasper-recall setup');
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (!fs.existsSync(PYTHON)) {
|
|
35
|
+
console.error('❌ Python venv not found. Run: npx jasper-recall setup');
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Run the Python script
|
|
40
|
+
const child = spawn(PYTHON, [scriptPath, ...process.argv.slice(2)], {
|
|
41
|
+
stdio: 'inherit',
|
|
42
|
+
env: { ...process.env }
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
child.on('exit', (code) => {
|
|
46
|
+
process.exit(code || 0);
|
|
47
|
+
});
|
package/cli/server.js
CHANGED
|
File without changes
|
package/cli/update-check.js
CHANGED
|
File without changes
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { execSync } from 'child_process';
|
|
14
|
+
import { readFileSync, existsSync } from 'fs';
|
|
14
15
|
import * as path from 'path';
|
|
15
16
|
import * as os from 'os';
|
|
16
17
|
|
|
@@ -131,18 +132,33 @@ export default function register(api: PluginApi) {
|
|
|
131
132
|
try {
|
|
132
133
|
let prependParts: string[] = [];
|
|
133
134
|
|
|
134
|
-
// If fresh session,
|
|
135
|
+
// If fresh session, inject identity files directly into context
|
|
135
136
|
if (isFreshSession) {
|
|
136
|
-
api.logger.info('[jasper-recall] Fresh session detected -
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
137
|
+
api.logger.info('[jasper-recall] Fresh session detected - injecting identity context');
|
|
138
|
+
|
|
139
|
+
const workspace = path.join(os.homedir(), '.openclaw', 'workspace');
|
|
140
|
+
const identityFiles = ['IDENTITY.md', 'SOUL.md', 'USER.md'];
|
|
141
|
+
const identityParts: string[] = [];
|
|
142
|
+
|
|
143
|
+
for (const file of identityFiles) {
|
|
144
|
+
const filePath = path.join(workspace, file);
|
|
145
|
+
if (existsSync(filePath)) {
|
|
146
|
+
try {
|
|
147
|
+
const content = readFileSync(filePath, 'utf8');
|
|
148
|
+
identityParts.push(`### ${file}\n${content}`);
|
|
149
|
+
} catch (err: any) {
|
|
150
|
+
api.logger.warn(`[jasper-recall] Failed to read ${file}: ${err.message}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (identityParts.length > 0) {
|
|
156
|
+
prependParts.push(`<session-identity>
|
|
157
|
+
🔄 **Fresh session.** Your identity files:
|
|
158
|
+
|
|
159
|
+
${identityParts.join('\n\n---\n\n')}
|
|
160
|
+
</session-identity>`);
|
|
161
|
+
}
|
|
146
162
|
}
|
|
147
163
|
|
|
148
164
|
const results = runRecall(event.prompt, {
|
|
@@ -180,18 +196,33 @@ ${memoryContext}
|
|
|
180
196
|
} catch (err: any) {
|
|
181
197
|
api.logger.warn(`[jasper-recall] Auto-recall failed: ${err.message}`);
|
|
182
198
|
|
|
183
|
-
// Still inject identity
|
|
199
|
+
// Still inject identity context on fresh session even if recall fails
|
|
184
200
|
if (isFreshSession) {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
201
|
+
const workspace = path.join(os.homedir(), '.openclaw', 'workspace');
|
|
202
|
+
const identityFiles = ['IDENTITY.md', 'SOUL.md', 'USER.md'];
|
|
203
|
+
const identityParts: string[] = [];
|
|
204
|
+
|
|
205
|
+
for (const file of identityFiles) {
|
|
206
|
+
const filePath = path.join(workspace, file);
|
|
207
|
+
if (existsSync(filePath)) {
|
|
208
|
+
try {
|
|
209
|
+
const content = readFileSync(filePath, 'utf8');
|
|
210
|
+
identityParts.push(`### ${file}\n${content}`);
|
|
211
|
+
} catch {
|
|
212
|
+
// Skip unreadable files
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (identityParts.length > 0) {
|
|
218
|
+
return {
|
|
219
|
+
prependContext: `<session-identity>
|
|
220
|
+
🔄 **Fresh session.** Your identity files:
|
|
221
|
+
|
|
222
|
+
${identityParts.join('\n\n---\n\n')}
|
|
223
|
+
</session-identity>`,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
195
226
|
}
|
|
196
227
|
}
|
|
197
228
|
});
|
|
@@ -295,6 +326,38 @@ cat ~/.openclaw/workspace/USER.md
|
|
|
295
326
|
},
|
|
296
327
|
});
|
|
297
328
|
|
|
329
|
+
// ============================================================================
|
|
330
|
+
// Command: /digest-sessions
|
|
331
|
+
// ============================================================================
|
|
332
|
+
|
|
333
|
+
api.registerCommand({
|
|
334
|
+
name: 'digest-sessions',
|
|
335
|
+
description: 'Extract summaries from session logs into memory',
|
|
336
|
+
acceptsArgs: true,
|
|
337
|
+
requireAuth: true,
|
|
338
|
+
handler: async (ctx: { args?: string }) => {
|
|
339
|
+
try {
|
|
340
|
+
const args = ctx.args?.trim() || '';
|
|
341
|
+
const digestPath = path.join(BIN_PATH, 'digest-sessions');
|
|
342
|
+
|
|
343
|
+
// Check if digest-sessions exists in PATH, otherwise use npx
|
|
344
|
+
let cmd: string;
|
|
345
|
+
try {
|
|
346
|
+
execSync(`which ${digestPath}`, { encoding: 'utf8' });
|
|
347
|
+
cmd = `${digestPath} ${args}`;
|
|
348
|
+
} catch {
|
|
349
|
+
// Fall back to npx
|
|
350
|
+
cmd = `npx jasper-recall digest-sessions ${args}`;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const output = execSync(cmd, { encoding: 'utf8', timeout: 300000 });
|
|
354
|
+
return { text: `🗂️ **Session Digests**\n\n${output}` };
|
|
355
|
+
} catch (err: any) {
|
|
356
|
+
return { text: `❌ Digest failed: ${err.message}` };
|
|
357
|
+
}
|
|
358
|
+
},
|
|
359
|
+
});
|
|
360
|
+
|
|
298
361
|
// ============================================================================
|
|
299
362
|
// Command: /jasper-recall setup
|
|
300
363
|
// ============================================================================
|
package/package.json
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jasper-recall",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.7",
|
|
4
4
|
"description": "Local RAG system for AI agent memory using ChromaDB and sentence-transformers",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
7
|
-
"jasper-recall": "./cli/jasper-recall.js"
|
|
7
|
+
"jasper-recall": "./cli/jasper-recall.js",
|
|
8
|
+
"digest-sessions": "./cli/digest-sessions.js",
|
|
9
|
+
"index-digests": "./cli/index-digests.js",
|
|
10
|
+
"recall": "./cli/recall.js"
|
|
8
11
|
},
|
|
9
12
|
"openclaw": {
|
|
10
13
|
"extensions": [
|