jasper-recall 0.2.0 → 0.2.2
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 +49 -0
- package/SKILL.md +92 -2
- package/cli/jasper-recall.js +9 -1
- package/cli/server.js +234 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,6 +17,12 @@ Local RAG (Retrieval-Augmented Generation) system for AI agent memory. Gives you
|
|
|
17
17
|
- **Shared memory sync** — Bidirectional learning between main and sandboxed agents
|
|
18
18
|
- **Privacy checker** — Scan content for sensitive data before sharing
|
|
19
19
|
|
|
20
|
+
### New in v0.2.1: Recall Server
|
|
21
|
+
|
|
22
|
+
- **HTTP API server** — `npx jasper-recall serve` for Docker-isolated agents
|
|
23
|
+
- **Public-only by default** — Secure API access for untrusted callers
|
|
24
|
+
- **CORS enabled** — Works from browsers and agent containers
|
|
25
|
+
|
|
20
26
|
## Quick Start
|
|
21
27
|
|
|
22
28
|
```bash
|
|
@@ -154,6 +160,49 @@ index-digests
|
|
|
154
160
|
recall "product info" --public-only
|
|
155
161
|
```
|
|
156
162
|
|
|
163
|
+
## Recall Server (v0.2.1+)
|
|
164
|
+
|
|
165
|
+
For **Docker-isolated agents** that can't run the CLI directly, start an HTTP API server:
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
npx jasper-recall serve # Default: localhost:3458
|
|
169
|
+
npx jasper-recall serve --port 8080 # Custom port
|
|
170
|
+
npx jasper-recall serve --host 0.0.0.0 # Allow external access
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### API Endpoints
|
|
174
|
+
|
|
175
|
+
```
|
|
176
|
+
GET /recall?q=search+query&limit=5
|
|
177
|
+
GET /health
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Example
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
# Query from Docker container
|
|
184
|
+
curl "http://host.docker.internal:3458/recall?q=product+info"
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Response:
|
|
188
|
+
```json
|
|
189
|
+
{
|
|
190
|
+
"ok": true,
|
|
191
|
+
"query": "product info",
|
|
192
|
+
"public_only": true,
|
|
193
|
+
"count": 3,
|
|
194
|
+
"results": [
|
|
195
|
+
{ "content": "...", "file": "memory/shared/product-updates.md", "score": 0.85 }
|
|
196
|
+
]
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Security
|
|
201
|
+
|
|
202
|
+
- **`public_only=true` is enforced by default** — API callers only see public content
|
|
203
|
+
- To allow private queries (dangerous!), set `RECALL_ALLOW_PRIVATE=true`
|
|
204
|
+
- Bind to `127.0.0.1` (default) to prevent external access
|
|
205
|
+
|
|
157
206
|
## Configuration
|
|
158
207
|
|
|
159
208
|
Set environment variables to customize paths:
|
package/SKILL.md
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: jasper-recall
|
|
3
|
-
|
|
3
|
+
version: 0.2.1
|
|
4
|
+
description: Local RAG system for agent memory using ChromaDB and sentence-transformers. Provides semantic search over session logs, daily notes, and memory files. v0.2.1 adds HTTP server for Docker-isolated agents. Commands: recall, index-digests, digest-sessions, privacy-check, sync-shared, serve.
|
|
4
5
|
---
|
|
5
6
|
|
|
6
|
-
# Jasper Recall
|
|
7
|
+
# Jasper Recall v0.2.1
|
|
7
8
|
|
|
8
9
|
Local RAG (Retrieval-Augmented Generation) system for AI agent memory. Gives your agent the ability to remember and search past conversations.
|
|
9
10
|
|
|
11
|
+
**New in v0.2.1:** Recall Server — HTTP API for Docker-isolated agents that can't run CLI directly.
|
|
12
|
+
|
|
13
|
+
**New in v0.2.0:** Shared Agent Memory — bidirectional learning between main and sandboxed agents with privacy controls.
|
|
14
|
+
|
|
10
15
|
## When to Use
|
|
11
16
|
|
|
12
17
|
- **Memory recall**: Search past sessions for context before answering
|
|
@@ -106,6 +111,44 @@ Schedule regular indexing:
|
|
|
106
111
|
}
|
|
107
112
|
```
|
|
108
113
|
|
|
114
|
+
## Shared Agent Memory (v0.2.0)
|
|
115
|
+
|
|
116
|
+
For multi-agent setups where sandboxed agents need access to some memories:
|
|
117
|
+
|
|
118
|
+
### Memory Tagging
|
|
119
|
+
|
|
120
|
+
Tag entries in daily notes:
|
|
121
|
+
|
|
122
|
+
```markdown
|
|
123
|
+
## 2026-02-05 [public] - Feature shipped
|
|
124
|
+
This is visible to all agents.
|
|
125
|
+
|
|
126
|
+
## 2026-02-05 [private] - Personal note
|
|
127
|
+
This is main agent only (default if untagged).
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Sandboxed Agent Access
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
# Sandboxed agents use --public-only
|
|
134
|
+
recall "product info" --public-only
|
|
135
|
+
|
|
136
|
+
# Main agent can see everything
|
|
137
|
+
recall "product info"
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Privacy Workflow
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
# Check for sensitive data before sharing
|
|
144
|
+
privacy-check "text to scan"
|
|
145
|
+
privacy-check --file notes.md
|
|
146
|
+
|
|
147
|
+
# Extract [public] entries to shared directory
|
|
148
|
+
sync-shared
|
|
149
|
+
sync-shared --dry-run # Preview first
|
|
150
|
+
```
|
|
151
|
+
|
|
109
152
|
## CLI Reference
|
|
110
153
|
|
|
111
154
|
### recall
|
|
@@ -117,6 +160,53 @@ Options:
|
|
|
117
160
|
-n, --limit N Number of results (default: 5)
|
|
118
161
|
--json Output as JSON
|
|
119
162
|
-v, --verbose Show similarity scores
|
|
163
|
+
--public-only Only search shared/public content (v0.2.0+)
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### serve (v0.2.1+)
|
|
167
|
+
|
|
168
|
+
```
|
|
169
|
+
npx jasper-recall serve [OPTIONS]
|
|
170
|
+
|
|
171
|
+
Options:
|
|
172
|
+
--port, -p N Port to listen on (default: 3458)
|
|
173
|
+
--host, -h H Host to bind (default: 127.0.0.1)
|
|
174
|
+
|
|
175
|
+
Starts HTTP API server for Docker-isolated agents.
|
|
176
|
+
|
|
177
|
+
Endpoints:
|
|
178
|
+
GET /recall?q=query&limit=5 Search memories
|
|
179
|
+
GET /health Health check
|
|
180
|
+
|
|
181
|
+
Security: public_only=true enforced by default.
|
|
182
|
+
Set RECALL_ALLOW_PRIVATE=true to allow private queries.
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
**Example (from Docker container):**
|
|
186
|
+
```bash
|
|
187
|
+
curl "http://host.docker.internal:3458/recall?q=product+info"
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### privacy-check (v0.2.0+)
|
|
191
|
+
|
|
192
|
+
```
|
|
193
|
+
privacy-check "text" # Scan inline text
|
|
194
|
+
privacy-check --file X # Scan a file
|
|
195
|
+
|
|
196
|
+
Detects: emails, API keys, internal IPs, home paths, credentials.
|
|
197
|
+
Returns: CLEAN or list of violations.
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### sync-shared (v0.2.0+)
|
|
201
|
+
|
|
202
|
+
```
|
|
203
|
+
sync-shared [OPTIONS]
|
|
204
|
+
|
|
205
|
+
Options:
|
|
206
|
+
--dry-run Preview without writing
|
|
207
|
+
--all Process all daily notes
|
|
208
|
+
|
|
209
|
+
Extracts [public] tagged entries to memory/shared/.
|
|
120
210
|
```
|
|
121
211
|
|
|
122
212
|
### index-digests
|
package/cli/jasper-recall.js
CHANGED
|
@@ -15,7 +15,7 @@ const fs = require('fs');
|
|
|
15
15
|
const path = require('path');
|
|
16
16
|
const os = require('os');
|
|
17
17
|
|
|
18
|
-
const VERSION = '0.
|
|
18
|
+
const VERSION = '0.2.2';
|
|
19
19
|
const VENV_PATH = path.join(os.homedir(), '.openclaw', 'rag-env');
|
|
20
20
|
const CHROMA_PATH = path.join(os.homedir(), '.openclaw', 'chroma-db');
|
|
21
21
|
const BIN_PATH = path.join(os.homedir(), '.local', 'bin');
|
|
@@ -134,6 +134,7 @@ COMMANDS:
|
|
|
134
134
|
recall Search your memory (alias for the recall command)
|
|
135
135
|
index Index memory files (alias for index-digests)
|
|
136
136
|
digest Process session logs (alias for digest-sessions)
|
|
137
|
+
serve Start HTTP API server (for sandboxed agents)
|
|
137
138
|
help Show this help message
|
|
138
139
|
|
|
139
140
|
EXAMPLES:
|
|
@@ -141,6 +142,7 @@ EXAMPLES:
|
|
|
141
142
|
recall "what did we discuss yesterday"
|
|
142
143
|
index-digests
|
|
143
144
|
digest-sessions --dry-run
|
|
145
|
+
npx jasper-recall serve --port 3458
|
|
144
146
|
`);
|
|
145
147
|
}
|
|
146
148
|
|
|
@@ -178,6 +180,12 @@ switch (command) {
|
|
|
178
180
|
error('Run "npx jasper-recall setup" first');
|
|
179
181
|
}
|
|
180
182
|
break;
|
|
183
|
+
case 'serve':
|
|
184
|
+
case 'server':
|
|
185
|
+
// Start the HTTP server for sandboxed agents
|
|
186
|
+
const { runCLI } = require('./server');
|
|
187
|
+
runCLI(process.argv.slice(3));
|
|
188
|
+
break;
|
|
181
189
|
case '--version':
|
|
182
190
|
case '-v':
|
|
183
191
|
console.log(VERSION);
|
package/cli/server.js
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Jasper Recall Server
|
|
3
|
+
* HTTP API for memory search - designed for sandboxed agents
|
|
4
|
+
*
|
|
5
|
+
* Security: public_only is enforced by default
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const http = require('http');
|
|
9
|
+
const { execSync } = require('child_process');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const os = require('os');
|
|
12
|
+
const url = require('url');
|
|
13
|
+
|
|
14
|
+
const BIN_PATH = path.join(os.homedir(), '.local', 'bin');
|
|
15
|
+
const RECALL_SCRIPT = path.join(BIN_PATH, 'recall');
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Execute recall query
|
|
19
|
+
*/
|
|
20
|
+
function executeRecall(query, options = {}) {
|
|
21
|
+
const { publicOnly = true, limit = 5 } = options;
|
|
22
|
+
|
|
23
|
+
let cmd = `${RECALL_SCRIPT} "${query.replace(/"/g, '\\"')}"`;
|
|
24
|
+
|
|
25
|
+
// Security: always add --public-only unless explicitly disabled
|
|
26
|
+
if (publicOnly) {
|
|
27
|
+
cmd += ' --public-only';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
cmd += ` --limit ${parseInt(limit) || 5}`;
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
const output = execSync(cmd, {
|
|
34
|
+
encoding: 'utf8',
|
|
35
|
+
timeout: 30000,
|
|
36
|
+
env: { ...process.env, HOME: os.homedir() }
|
|
37
|
+
});
|
|
38
|
+
return { ok: true, output };
|
|
39
|
+
} catch (err) {
|
|
40
|
+
// Check if it's just "no results"
|
|
41
|
+
if (err.stdout?.includes('No results') || err.status === 0) {
|
|
42
|
+
return { ok: true, output: err.stdout || 'No results found' };
|
|
43
|
+
}
|
|
44
|
+
return { ok: false, error: err.message, stderr: err.stderr };
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Parse recall output into structured results
|
|
50
|
+
*/
|
|
51
|
+
function parseResults(output) {
|
|
52
|
+
const results = [];
|
|
53
|
+
|
|
54
|
+
// Try to parse structured output
|
|
55
|
+
const blocks = output.split(/={3,}\s*(?:Result\s+\d+|---)/i);
|
|
56
|
+
|
|
57
|
+
for (const block of blocks) {
|
|
58
|
+
if (!block.trim()) continue;
|
|
59
|
+
|
|
60
|
+
const result = {};
|
|
61
|
+
|
|
62
|
+
const scoreMatch = block.match(/score:\s*([\d.]+)/i);
|
|
63
|
+
if (scoreMatch) result.score = parseFloat(scoreMatch[1]);
|
|
64
|
+
|
|
65
|
+
const fileMatch = block.match(/File:\s*(.+)/i);
|
|
66
|
+
if (fileMatch) result.file = fileMatch[1].trim();
|
|
67
|
+
|
|
68
|
+
const linesMatch = block.match(/Lines?:\s*(\d+(?:-\d+)?)/i);
|
|
69
|
+
if (linesMatch) result.lines = linesMatch[1];
|
|
70
|
+
|
|
71
|
+
// Content is everything else
|
|
72
|
+
let content = block
|
|
73
|
+
.replace(/score:\s*[\d.]+/gi, '')
|
|
74
|
+
.replace(/File:\s*.+/gi, '')
|
|
75
|
+
.replace(/Lines?:\s*\d+(?:-\d+)?/gi, '')
|
|
76
|
+
.trim();
|
|
77
|
+
|
|
78
|
+
if (content) {
|
|
79
|
+
result.content = content.substring(0, 1000);
|
|
80
|
+
results.push(result);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Fallback for unparseable output
|
|
85
|
+
if (results.length === 0 && output.trim()) {
|
|
86
|
+
results.push({ content: output.trim().substring(0, 2000), raw: true });
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return results;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Handle HTTP request
|
|
94
|
+
*/
|
|
95
|
+
function handleRequest(req, res) {
|
|
96
|
+
// CORS headers for browser/agent access
|
|
97
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
98
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
|
|
99
|
+
res.setHeader('Content-Type', 'application/json');
|
|
100
|
+
|
|
101
|
+
if (req.method === 'OPTIONS') {
|
|
102
|
+
res.writeHead(200);
|
|
103
|
+
res.end();
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const parsedUrl = url.parse(req.url, true);
|
|
108
|
+
const pathname = parsedUrl.pathname;
|
|
109
|
+
const query = parsedUrl.query;
|
|
110
|
+
|
|
111
|
+
// Health check
|
|
112
|
+
if (pathname === '/health' || pathname === '/') {
|
|
113
|
+
res.writeHead(200);
|
|
114
|
+
res.end(JSON.stringify({ ok: true, service: 'jasper-recall', version: '0.2.1' }));
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Recall endpoint
|
|
119
|
+
if (pathname === '/recall' || pathname === '/api/recall') {
|
|
120
|
+
const searchQuery = query.q || query.query;
|
|
121
|
+
|
|
122
|
+
if (!searchQuery) {
|
|
123
|
+
res.writeHead(400);
|
|
124
|
+
res.end(JSON.stringify({ ok: false, error: 'q or query parameter required' }));
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Security: public_only defaults to true
|
|
129
|
+
// Only allow disabling if explicitly set AND RECALL_ALLOW_PRIVATE=true
|
|
130
|
+
let publicOnly = true;
|
|
131
|
+
if (query.public_only === 'false' && process.env.RECALL_ALLOW_PRIVATE === 'true') {
|
|
132
|
+
publicOnly = false;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const result = executeRecall(searchQuery, {
|
|
136
|
+
publicOnly,
|
|
137
|
+
limit: query.limit || 5
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
if (result.ok) {
|
|
141
|
+
const parsed = parseResults(result.output);
|
|
142
|
+
res.writeHead(200);
|
|
143
|
+
res.end(JSON.stringify({
|
|
144
|
+
ok: true,
|
|
145
|
+
query: searchQuery,
|
|
146
|
+
public_only: publicOnly,
|
|
147
|
+
count: parsed.length,
|
|
148
|
+
results: parsed,
|
|
149
|
+
raw: result.output
|
|
150
|
+
}));
|
|
151
|
+
} else {
|
|
152
|
+
res.writeHead(500);
|
|
153
|
+
res.end(JSON.stringify({
|
|
154
|
+
ok: false,
|
|
155
|
+
error: result.error,
|
|
156
|
+
stderr: result.stderr?.substring(0, 500)
|
|
157
|
+
}));
|
|
158
|
+
}
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// 404
|
|
163
|
+
res.writeHead(404);
|
|
164
|
+
res.end(JSON.stringify({ ok: false, error: 'Not found' }));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Start the server
|
|
169
|
+
*/
|
|
170
|
+
function startServer(port = 3458, host = '127.0.0.1') {
|
|
171
|
+
const server = http.createServer(handleRequest);
|
|
172
|
+
|
|
173
|
+
server.listen(port, host, () => {
|
|
174
|
+
console.log(`🦊 Jasper Recall Server running on http://${host}:${port}`);
|
|
175
|
+
console.log('');
|
|
176
|
+
console.log('Endpoints:');
|
|
177
|
+
console.log(` GET /recall?q=query Search memories (public-only by default)`);
|
|
178
|
+
console.log(` GET /health Health check`);
|
|
179
|
+
console.log('');
|
|
180
|
+
console.log('Security: public_only=true is enforced by default');
|
|
181
|
+
console.log('Press Ctrl+C to stop');
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
return server;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Parse CLI args and start server
|
|
189
|
+
*/
|
|
190
|
+
function runCLI(args) {
|
|
191
|
+
let port = 3458;
|
|
192
|
+
let host = '127.0.0.1';
|
|
193
|
+
|
|
194
|
+
for (let i = 0; i < args.length; i++) {
|
|
195
|
+
if (args[i] === '--port' || args[i] === '-p') {
|
|
196
|
+
port = parseInt(args[++i]) || 3458;
|
|
197
|
+
}
|
|
198
|
+
if (args[i] === '--host' || args[i] === '-h') {
|
|
199
|
+
host = args[++i] || '127.0.0.1';
|
|
200
|
+
}
|
|
201
|
+
if (args[i] === '--help') {
|
|
202
|
+
console.log(`
|
|
203
|
+
Jasper Recall Server
|
|
204
|
+
HTTP API for memory search
|
|
205
|
+
|
|
206
|
+
Usage: npx jasper-recall serve [options]
|
|
207
|
+
|
|
208
|
+
Options:
|
|
209
|
+
--port, -p Port to listen on (default: 3458)
|
|
210
|
+
--host, -h Host to bind to (default: 127.0.0.1)
|
|
211
|
+
--help Show this help
|
|
212
|
+
|
|
213
|
+
Environment:
|
|
214
|
+
RECALL_ALLOW_PRIVATE=true Allow public_only=false queries (dangerous!)
|
|
215
|
+
|
|
216
|
+
Examples:
|
|
217
|
+
npx jasper-recall serve
|
|
218
|
+
npx jasper-recall serve --port 8080
|
|
219
|
+
npx jasper-recall serve --host 0.0.0.0
|
|
220
|
+
`);
|
|
221
|
+
process.exit(0);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
startServer(port, host);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Export for programmatic use
|
|
229
|
+
module.exports = { startServer, executeRecall, parseResults, runCLI };
|
|
230
|
+
|
|
231
|
+
// CLI entry point
|
|
232
|
+
if (require.main === module) {
|
|
233
|
+
runCLI(process.argv.slice(2));
|
|
234
|
+
}
|