shabti 1.13.0 → 1.14.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/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # Shabti
2
2
 
3
- Demo CLI toolshowcasing npm-publishable CLI structure.
3
+ Agent Memory OSsemantic memory for AI agents.
4
+
5
+ A Rust-powered memory engine with Node.js CLI that provides semantic search, deduplication, time-decay scoring, and graph-based memory linking for AI agents.
4
6
 
5
7
  ## Install
6
8
 
@@ -8,75 +10,181 @@ Demo CLI tool — showcasing npm-publishable CLI structure.
8
10
  npm install -g shabti
9
11
  ```
10
12
 
11
- ## Usage
13
+ ### Prerequisites
14
+
15
+ Shabti requires a running Qdrant instance for vector storage:
12
16
 
13
17
  ```bash
14
- shabti [command] [options]
18
+ docker run -d --name qdrant -p 6333:6333 -p 6334:6334 qdrant/qdrant
15
19
  ```
16
20
 
17
- ## Commands
21
+ Verify the connection:
18
22
 
19
- | Command | Description |
20
- | -------------- | --------------------------------- |
21
- | `hello <name>` | Greet someone |
22
- | `list` | Show sample task list |
23
- | `spin` | Demo async operation with spinner |
23
+ ```bash
24
+ shabti config setup --check
25
+ ```
24
26
 
25
- ### hello
27
+ ## Quick Start
26
28
 
27
29
  ```bash
28
- shabti hello World
29
- # Hello, World!
30
+ # Store a memory
31
+ shabti store "Rust is a systems programming language"
32
+
33
+ # Search memories
34
+ shabti search "systems programming"
35
+
36
+ # Search with score explanation
37
+ shabti search "programming" --explain
30
38
 
31
- shabti hello World --shout
32
- # HELLO, WORLD!
39
+ # Check engine status
40
+ shabti status
33
41
  ```
34
42
 
35
- ### list
43
+ ## Commands
36
44
 
37
- ```bash
38
- # Table output
39
- shabti list
45
+ | Command | Description |
46
+ | ----------------- | ---------------------------- |
47
+ | `store <content>` | Store a memory entry |
48
+ | `search <query>` | Search memory entries |
49
+ | `status` | Show engine status |
50
+ | `snapshot` | Manage storage snapshots |
51
+ | `config` | Manage configuration |
52
+ | `chat` | Interactive chat with OpenAI |
40
53
 
41
- # JSON output
42
- shabti list --json
54
+ ### store
43
55
 
44
- # Filter by status
45
- shabti list --filter done
56
+ ```bash
57
+ shabti store "Tokyo is the capital of Japan"
58
+ shabti store "meeting at 3pm" --namespace work
59
+ shabti store "buy groceries" --tags "todo,personal"
46
60
  ```
47
61
 
48
- ### spin
62
+ ### search
49
63
 
50
64
  ```bash
51
- # Default 2000ms spinner
52
- shabti spin
65
+ shabti search "capital of Japan"
66
+ shabti search "meeting" --namespace work --limit 5
67
+ shabti search "programming" --explain # show score breakdown
68
+ shabti search "AI" --follow-links 2 # expand via graph links
69
+ shabti search "recent events" --min-score 0.5
70
+ shabti search "query" --json # JSON output
71
+ ```
53
72
 
54
- # Custom duration
55
- shabti spin --duration 500
73
+ ### snapshot
74
+
75
+ ```bash
76
+ shabti snapshot create
77
+ shabti snapshot list
78
+ shabti snapshot restore <id>
56
79
  ```
57
80
 
58
- ## Development
81
+ ### config
59
82
 
60
83
  ```bash
61
- # Install dependencies
62
- npm install
84
+ shabti config show
85
+ shabti config show --json
86
+ shabti config set qdrant_url http://localhost:6334
87
+ shabti config setup # show Qdrant setup instructions
88
+ shabti config setup --check # test Qdrant connection
89
+ ```
63
90
 
64
- # Run tests
65
- npm test
91
+ ## Interactive Mode (REPL)
66
92
 
67
- # Run linter
68
- npm run lint
93
+ Run `shabti` with no arguments in a terminal to enter interactive mode:
69
94
 
70
- # Format code
71
- npm run format
95
+ ```
96
+ $ shabti
97
+ shabti v2.0.0
98
+
99
+ [info] Memory engine connected (/remember, /recall available)
100
+ [info] Interactive mode — model: gpt-4o-mini
101
+
102
+ you> /remember Tokyo is the capital of Japan
103
+ [ok] Remembered: a1b2c3d4-...
104
+
105
+ you> /recall capital
106
+ 0.9234 Tokyo is the capital of Japan
107
+
108
+ you> /help
109
+ /help Show this help message
110
+ /exit Exit the REPL
111
+ /clear Clear conversation history
112
+ /model Show or switch the model
113
+ /history Show conversation history
114
+ /remember Store a memory
115
+ /recall Search memories
116
+ ```
72
117
 
73
- # Run full CI pipeline
74
- npm run ci
118
+ Requires `OPENAI_API_KEY` in `.env` for chat functionality. Memory commands (`/remember`, `/recall`) work with the local Qdrant engine.
119
+
120
+ ## Node.js API
121
+
122
+ ```javascript
123
+ import { ShabtiEngine } from "shabti";
124
+
125
+ const engine = new ShabtiEngine({
126
+ qdrantUrl: "http://localhost:6334",
127
+ collectionName: "my-app",
128
+ dataDir: "./data",
129
+ });
130
+
131
+ // Store
132
+ await engine.store("Rust is a systems programming language", {});
133
+
134
+ // Search
135
+ const results = await engine.executeQuery({
136
+ text: "systems programming",
137
+ limit: 10,
138
+ withExplanation: true,
139
+ });
140
+
141
+ for (const r of results) {
142
+ console.log(`${r.score.toFixed(4)} ${r.content}`);
143
+ if (r.explanation) {
144
+ console.log(
145
+ ` sim=${r.explanation.semanticSimilarity.toFixed(3)} ` +
146
+ `decay=${r.explanation.timeDecayFactor.toFixed(3)} ` +
147
+ `boost=${r.explanation.accessBoostFactor.toFixed(3)}`,
148
+ );
149
+ }
150
+ }
151
+
152
+ await engine.shutdown();
153
+ ```
154
+
155
+ ## Architecture
156
+
157
+ ```
158
+ shabti (npm CLI + Node.js API)
159
+ └── shabti-napi (Rust ↔ Node.js FFI via NAPI-RS)
160
+ └── shabti-engine (orchestration layer)
161
+ ├── shabti-embedding (fastembed-rs, MultilingualE5Small 384-dim)
162
+ ├── shabti-index (Qdrant vector DB client)
163
+ ├── shabti-storage (append-only log, event store, snapshots)
164
+ ├── shabti-graph (k-NN memory link graph)
165
+ └── shabti-core (data models, scoring, dedup, query DSL)
75
166
  ```
76
167
 
77
- ## Contributing
168
+ ## Benchmarks
78
169
 
79
- See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
170
+ See [BENCHMARKS.md](BENCHMARKS.md) for detailed results.
171
+
172
+ | Metric | Target | Measured |
173
+ | ---------- | -------- | ----------- |
174
+ | Recall@10 | >= 0.85 | **1.000** |
175
+ | Insert p99 | <= 100ms | **60.22ms** |
176
+ | Search p99 | <= 100ms | **58.71ms** |
177
+
178
+ ## Development
179
+
180
+ ```bash
181
+ npm install # install JS dependencies
182
+ cargo build # build Rust workspace
183
+ npm test # run JS tests (vitest)
184
+ cargo test # run Rust tests
185
+ npm run lint # eslint
186
+ cargo clippy # Rust linter
187
+ ```
80
188
 
81
189
  ## License
82
190
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shabti",
3
- "version": "1.13.0",
3
+ "version": "1.14.0",
4
4
  "description": "Demo CLI tool — showcasing npm-publishable CLI structure",
5
5
  "type": "module",
6
6
  "main": "native.cjs",
@@ -48,7 +48,8 @@ export function registerConfig(program) {
48
48
  cmd
49
49
  .command("setup")
50
50
  .description("Show Qdrant setup instructions")
51
- .action(() => {
51
+ .option("--check", "Test connection to Qdrant")
52
+ .action(async (opts) => {
52
53
  const config = loadConfig();
53
54
  heading("Qdrant Setup");
54
55
  console.log();
@@ -63,5 +64,21 @@ export function registerConfig(program) {
63
64
  console.log(" To change the URL:");
64
65
  console.log(` shabti config set qdrant_url ${chalk.dim("<url>")}`);
65
66
  console.log();
67
+
68
+ if (opts.check) {
69
+ const restUrl = config.qdrant_url.replace(":6334", ":6333");
70
+ try {
71
+ const res = await fetch(`${restUrl}/healthz`);
72
+ if (res.ok) {
73
+ success("Qdrant is reachable and healthy.");
74
+ } else {
75
+ error(`Qdrant responded with status ${res.status}`);
76
+ process.exitCode = 1;
77
+ }
78
+ } catch (err) {
79
+ error(`Cannot reach Qdrant at ${restUrl}: ${err.message}`);
80
+ process.exitCode = 1;
81
+ }
82
+ }
66
83
  });
67
84
  }
package/src/repl/index.js CHANGED
@@ -18,6 +18,18 @@ export async function launchRepl() {
18
18
 
19
19
  const model = "gpt-4o-mini";
20
20
 
21
+ // Try to connect memory engine (non-blocking)
22
+ let engine = null;
23
+ try {
24
+ const { createEngine } = await import("../core/engine.js");
25
+ engine = createEngine();
26
+ info(
27
+ `Memory engine connected (${chalk.cyan("/remember")}, ${chalk.cyan("/recall")} available)`,
28
+ );
29
+ } catch {
30
+ warn("Memory engine not available. Chat-only mode (start Qdrant to enable memory).");
31
+ }
32
+
21
33
  console.log();
22
34
  info(`Interactive mode — model: ${chalk.cyan(model)}`);
23
35
  console.log(chalk.dim(" Type a message, or /help for commands. Ctrl+C or /exit to quit.\n"));
@@ -26,7 +38,7 @@ export async function launchRepl() {
26
38
  apiKey,
27
39
  model,
28
40
  promptPrefix: "you> ",
29
- onSlashCommand: handleSlashCommand,
41
+ onSlashCommand: (cmd, args, sess, rl) => handleSlashCommand(cmd, args, sess, rl, engine),
30
42
  legacyExitWord: false,
31
43
  });
32
44
 
@@ -1,5 +1,5 @@
1
1
  import chalk from "chalk";
2
- import { success, info } from "../utils/style.js";
2
+ import { success, info, warn, error } from "../utils/style.js";
3
3
 
4
4
  const COMMANDS = {
5
5
  "/help": "Show this help message",
@@ -7,6 +7,8 @@ const COMMANDS = {
7
7
  "/clear": "Clear conversation history",
8
8
  "/model": "Show or switch the model (e.g. /model gpt-4o)",
9
9
  "/history": "Show conversation history",
10
+ "/remember": "Store a memory (e.g. /remember Tokyo is the capital of Japan)",
11
+ "/recall": "Search memories (e.g. /recall capital of Japan)",
10
12
  };
11
13
 
12
14
  /**
@@ -17,9 +19,10 @@ const COMMANDS = {
17
19
  * @param {string} args - Arguments after the command
18
20
  * @param {import("../core/session.js").ChatSession} session
19
21
  * @param {import("readline").Interface} rl
20
- * @returns {boolean}
22
+ * @param {object|null} [engine] - ShabtiEngine instance (nullable)
23
+ * @returns {boolean|Promise<boolean>}
21
24
  */
22
- export function handleSlashCommand(cmd, args, session, rl) {
25
+ export function handleSlashCommand(cmd, args, session, rl, engine = null) {
23
26
  switch (cmd) {
24
27
  case "/help":
25
28
  console.log();
@@ -66,7 +69,70 @@ export function handleSlashCommand(cmd, args, session, rl) {
66
69
  return true;
67
70
  }
68
71
 
72
+ case "/remember":
73
+ return handleRemember(args, engine);
74
+
75
+ case "/recall":
76
+ return handleRecall(args, engine);
77
+
69
78
  default:
70
79
  return false;
71
80
  }
72
81
  }
82
+
83
+ async function handleRemember(text, engine) {
84
+ if (!engine) {
85
+ console.log();
86
+ warn("Memory engine not available. Start Qdrant to enable memory features.");
87
+ console.log();
88
+ return true;
89
+ }
90
+ if (!text) {
91
+ console.log();
92
+ info("Usage: /remember <text to store>");
93
+ console.log();
94
+ return true;
95
+ }
96
+ try {
97
+ const result = await engine.store(text, {});
98
+ if (result.status === "stored") {
99
+ success(`Remembered: ${result.id}`);
100
+ } else {
101
+ info(`Already remembered (duplicate)`);
102
+ }
103
+ } catch (err) {
104
+ error(`Failed to store: ${err.message}`);
105
+ }
106
+ console.log();
107
+ return true;
108
+ }
109
+
110
+ async function handleRecall(query, engine) {
111
+ if (!engine) {
112
+ console.log();
113
+ warn("Memory engine not available. Start Qdrant to enable memory features.");
114
+ console.log();
115
+ return true;
116
+ }
117
+ if (!query) {
118
+ console.log();
119
+ info("Usage: /recall <search query>");
120
+ console.log();
121
+ return true;
122
+ }
123
+ try {
124
+ const results = await engine.executeQuery({ text: query, limit: 5 });
125
+ console.log();
126
+ if (results.length === 0) {
127
+ console.log(chalk.dim(" No memories found."));
128
+ } else {
129
+ for (const r of results) {
130
+ console.log(` ${chalk.yellow(r.score.toFixed(4))} ${r.content}`);
131
+ }
132
+ }
133
+ } catch (err) {
134
+ error(`Failed to search: ${err.message}`);
135
+ }
136
+ console.log();
137
+ return true;
138
+ }