shabti 2.3.0 → 2.4.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/package.json +1 -1
- package/src/commands/export.js +44 -0
- package/src/commands/import.js +66 -0
- package/src/index.js +4 -0
- package/src/mcp/server.js +34 -0
package/package.json
CHANGED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { writeFileSync } from "fs";
|
|
2
|
+
import { createEngine } from "../core/engine.js";
|
|
3
|
+
import { success, error } from "../utils/style.js";
|
|
4
|
+
|
|
5
|
+
export function registerExport(program) {
|
|
6
|
+
program
|
|
7
|
+
.command("export")
|
|
8
|
+
.description("Export memory entries as JSONL")
|
|
9
|
+
.option("-n, --namespace <ns>", "Filter by namespace")
|
|
10
|
+
.option("--no-embeddings", "Exclude embedding vectors from output")
|
|
11
|
+
.option("-o, --output <file>", "Write to file instead of stdout")
|
|
12
|
+
.option("--limit <n>", "Maximum number of entries to export")
|
|
13
|
+
.action(async (opts) => {
|
|
14
|
+
try {
|
|
15
|
+
const engine = createEngine();
|
|
16
|
+
const listOpts = {};
|
|
17
|
+
if (opts.namespace) listOpts.namespace = opts.namespace;
|
|
18
|
+
if (opts.limit) listOpts.limit = parseInt(opts.limit, 10);
|
|
19
|
+
listOpts.includeEmbeddings = opts.embeddings !== false ? false : false;
|
|
20
|
+
// --no-embeddings sets opts.embeddings = false (commander negatable)
|
|
21
|
+
// default: exclude embeddings for smaller output
|
|
22
|
+
listOpts.includeEmbeddings = opts.embeddings === true;
|
|
23
|
+
|
|
24
|
+
const entries = engine.listEntries(listOpts);
|
|
25
|
+
|
|
26
|
+
const lines = entries.map((e) => JSON.stringify(e));
|
|
27
|
+
const output = lines.join("\n");
|
|
28
|
+
|
|
29
|
+
if (opts.output) {
|
|
30
|
+
writeFileSync(opts.output, output + (lines.length ? "\n" : ""), "utf8");
|
|
31
|
+
success(`Exported ${entries.length} entries to ${opts.output}`);
|
|
32
|
+
} else {
|
|
33
|
+
if (output) console.log(output);
|
|
34
|
+
// Print summary to stderr so it doesn't pollute piped output
|
|
35
|
+
process.stderr.write(`\n${entries.length} entries exported\n`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
await engine.shutdown();
|
|
39
|
+
} catch (err) {
|
|
40
|
+
error(err.message);
|
|
41
|
+
process.exitCode = 1;
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { readFileSync } from "fs";
|
|
2
|
+
import { createEngine } from "../core/engine.js";
|
|
3
|
+
import { success, error, info, warn } from "../utils/style.js";
|
|
4
|
+
|
|
5
|
+
export function registerImport(program) {
|
|
6
|
+
program
|
|
7
|
+
.command("import")
|
|
8
|
+
.description("Import memory entries from a JSONL file")
|
|
9
|
+
.argument("<file>", "Path to JSONL file")
|
|
10
|
+
.option("-n, --namespace <ns>", "Override namespace for all imported entries")
|
|
11
|
+
.option("--dry-run", "Parse and validate without storing")
|
|
12
|
+
.action(async (file, opts) => {
|
|
13
|
+
try {
|
|
14
|
+
const raw = readFileSync(file, "utf8");
|
|
15
|
+
const lines = raw
|
|
16
|
+
.split("\n")
|
|
17
|
+
.map((l) => l.trim())
|
|
18
|
+
.filter((l) => l.length > 0);
|
|
19
|
+
|
|
20
|
+
if (lines.length === 0) {
|
|
21
|
+
info("No entries found in file.");
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Parse all lines first to validate
|
|
26
|
+
const entries = [];
|
|
27
|
+
for (let i = 0; i < lines.length; i++) {
|
|
28
|
+
try {
|
|
29
|
+
entries.push(JSON.parse(lines[i]));
|
|
30
|
+
} catch {
|
|
31
|
+
warn(`Skipping invalid JSON on line ${i + 1}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (opts.dryRun) {
|
|
36
|
+
info(`Dry run: ${entries.length} entries parsed, 0 stored.`);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const engine = createEngine();
|
|
41
|
+
let stored = 0;
|
|
42
|
+
let skipped = 0;
|
|
43
|
+
|
|
44
|
+
for (const entry of entries) {
|
|
45
|
+
const storeOpts = {};
|
|
46
|
+
storeOpts.namespace = opts.namespace || entry.namespace || undefined;
|
|
47
|
+
if (entry.tags && entry.tags.length) storeOpts.tags = entry.tags;
|
|
48
|
+
if (entry.sessionId || entry.session_id)
|
|
49
|
+
storeOpts.sessionId = entry.sessionId || entry.session_id;
|
|
50
|
+
|
|
51
|
+
const result = await engine.store(entry.content, storeOpts);
|
|
52
|
+
if (result.status === "stored") {
|
|
53
|
+
stored++;
|
|
54
|
+
} else {
|
|
55
|
+
skipped++;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
success(`Import complete: ${stored} stored, ${skipped} skipped (duplicates)`);
|
|
60
|
+
await engine.shutdown();
|
|
61
|
+
} catch (err) {
|
|
62
|
+
error(err.message);
|
|
63
|
+
process.exitCode = 1;
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
package/src/index.js
CHANGED
|
@@ -11,6 +11,8 @@ import { registerSearch } from "./commands/search.js";
|
|
|
11
11
|
import { registerSnapshot } from "./commands/snapshot.js";
|
|
12
12
|
import { registerSpin } from "./commands/spin.js";
|
|
13
13
|
import { registerStatus } from "./commands/status.js";
|
|
14
|
+
import { registerExport } from "./commands/export.js";
|
|
15
|
+
import { registerImport } from "./commands/import.js";
|
|
14
16
|
import { registerStore } from "./commands/store.js";
|
|
15
17
|
|
|
16
18
|
const { version } = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
|
|
@@ -77,6 +79,8 @@ function buildProgram() {
|
|
|
77
79
|
registerSnapshot(program);
|
|
78
80
|
registerSpin(program);
|
|
79
81
|
registerStatus(program);
|
|
82
|
+
registerExport(program);
|
|
83
|
+
registerImport(program);
|
|
80
84
|
registerStore(program);
|
|
81
85
|
|
|
82
86
|
program
|
package/src/mcp/server.js
CHANGED
|
@@ -73,6 +73,17 @@ const TOOLS = [
|
|
|
73
73
|
},
|
|
74
74
|
},
|
|
75
75
|
},
|
|
76
|
+
{
|
|
77
|
+
name: "memory_export",
|
|
78
|
+
description: "Export all memory entries as a JSONL array",
|
|
79
|
+
inputSchema: {
|
|
80
|
+
type: "object",
|
|
81
|
+
properties: {
|
|
82
|
+
namespace: { type: "string", description: "Filter by namespace" },
|
|
83
|
+
limit: { type: "integer", description: "Maximum entries to export" },
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
},
|
|
76
87
|
{
|
|
77
88
|
name: "memory_gc",
|
|
78
89
|
description: "Garbage collect expired memory entries (removes entries past their TTL)",
|
|
@@ -306,6 +317,29 @@ async function handleToolsCall(id, params) {
|
|
|
306
317
|
}
|
|
307
318
|
}
|
|
308
319
|
|
|
320
|
+
if (name === "memory_export") {
|
|
321
|
+
if (!eng) {
|
|
322
|
+
return respondError(id, -32603, "Engine not available");
|
|
323
|
+
}
|
|
324
|
+
try {
|
|
325
|
+
const listOpts = {};
|
|
326
|
+
if (args?.namespace) listOpts.namespace = args.namespace;
|
|
327
|
+
if (args?.limit) listOpts.limit = args.limit;
|
|
328
|
+
const entries = eng.listEntries(listOpts);
|
|
329
|
+
const lines = entries.map((e) => JSON.stringify(e));
|
|
330
|
+
return respond(id, {
|
|
331
|
+
content: [
|
|
332
|
+
{
|
|
333
|
+
type: "text",
|
|
334
|
+
text: JSON.stringify({ entries: entries.length, data: lines.join("\n") }, null, 2),
|
|
335
|
+
},
|
|
336
|
+
],
|
|
337
|
+
});
|
|
338
|
+
} catch (err) {
|
|
339
|
+
return respondError(id, -32603, err.message);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
309
343
|
if (name === "memory_gc") {
|
|
310
344
|
if (!eng) {
|
|
311
345
|
return respondError(id, -32603, "Engine not available");
|