shabti 2.8.0 → 2.10.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
@@ -12,13 +12,24 @@ npm install -g shabti
12
12
 
13
13
  ### Prerequisites
14
14
 
15
- Shabti requires a running Qdrant instance for vector storage:
15
+ Shabti requires a running Qdrant instance for vector storage.
16
+
17
+ **Option 1: Docker**
16
18
 
17
19
  ```bash
18
20
  docker run -d --name qdrant -p 6333:6333 -p 6334:6334 qdrant/qdrant
19
21
  ```
20
22
 
21
- Verify the connection:
23
+ **Option 2: Native binary (no Docker)**
24
+
25
+ Download the binary for your platform from [Qdrant releases](https://github.com/qdrant/qdrant/releases) and run it directly.
26
+
27
+ ```bash
28
+ # Check for locally installed qdrant
29
+ shabti config setup --detect
30
+ ```
31
+
32
+ **Verify the connection:**
22
33
 
23
34
  ```bash
24
35
  shabti config setup --check
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shabti",
3
- "version": "2.8.0",
3
+ "version": "2.10.0",
4
4
  "description": "Agent Memory OS — semantic memory for AI agents",
5
5
  "type": "module",
6
6
  "main": "native.cjs",
@@ -49,22 +49,66 @@ export function registerConfig(program) {
49
49
  .command("setup")
50
50
  .description("Show Qdrant setup instructions")
51
51
  .option("--check", "Test connection to Qdrant")
52
+ .option("--detect", "Detect locally installed qdrant binary")
52
53
  .action(async (opts) => {
53
54
  const config = loadConfig();
54
55
  heading("Qdrant Setup");
55
56
  console.log();
56
57
  console.log(" shabti requires a running Qdrant instance for vector storage.");
57
58
  console.log();
58
- console.log(chalk.cyan(" Quick start with Docker:"));
59
+
60
+ // Option 1: Docker
61
+ console.log(chalk.cyan(" Option 1: Docker"));
59
62
  console.log();
60
63
  console.log(` docker run -d --name qdrant -p 6333:6333 -p 6334:6334 qdrant/qdrant`);
61
64
  console.log();
65
+
66
+ // Option 2: Native binary
67
+ console.log(chalk.cyan(" Option 2: Native binary (no Docker required)"));
68
+ console.log();
69
+ console.log(
70
+ ` Download from: ${chalk.underline("https://github.com/qdrant/qdrant/releases")}`,
71
+ );
72
+ console.log();
73
+ const plat = process.platform;
74
+ if (plat === "linux") {
75
+ console.log(" Linux:");
76
+ console.log(
77
+ " wget https://github.com/qdrant/qdrant/releases/latest/download/qdrant-x86_64-unknown-linux-gnu.tar.gz",
78
+ );
79
+ console.log(" tar xzf qdrant-x86_64-unknown-linux-gnu.tar.gz");
80
+ console.log(" ./qdrant");
81
+ } else if (plat === "darwin") {
82
+ console.log(" macOS:");
83
+ console.log(" Download the macOS binary from the releases page,");
84
+ console.log(" or build from source: cargo install qdrant");
85
+ } else if (plat === "win32") {
86
+ console.log(" Windows:");
87
+ console.log(" Download qdrant.exe from the releases page and run it.");
88
+ }
89
+ console.log();
90
+
62
91
  console.log(` Current Qdrant URL: ${chalk.yellow(config.qdrant_url)}`);
63
92
  console.log();
64
93
  console.log(" To change the URL:");
65
94
  console.log(` shabti config set qdrant_url ${chalk.dim("<url>")}`);
66
95
  console.log();
67
96
 
97
+ if (opts.detect) {
98
+ const { execFileSync } = await import("node:child_process");
99
+ const cmd = plat === "win32" ? "where" : "which";
100
+ try {
101
+ const result = execFileSync(cmd, ["qdrant"], {
102
+ encoding: "utf8",
103
+ timeout: 5000,
104
+ }).trim();
105
+ success(`Qdrant binary found at: ${result}`);
106
+ } catch (_) {
107
+ console.log(chalk.yellow(" Qdrant binary not found in PATH."));
108
+ console.log(" Download it from https://github.com/qdrant/qdrant/releases");
109
+ }
110
+ }
111
+
68
112
  if (opts.check) {
69
113
  const restUrl = config.qdrant_url.replace(":6334", ":6333");
70
114
  try {
@@ -3,6 +3,7 @@ import Table from "cli-table3";
3
3
  import { createEngine } from "../core/engine.js";
4
4
  import { error, heading } from "../utils/style.js";
5
5
  import { parsePositiveInt, parseScore } from "../utils/validate.js";
6
+ import { normalizeText } from "../utils/normalize.js";
6
7
 
7
8
  export function registerSearch(program) {
8
9
  program
@@ -23,7 +24,7 @@ export function registerSearch(program) {
23
24
  try {
24
25
  const engine = createEngine();
25
26
 
26
- const query = { text: queryText };
27
+ const query = { text: normalizeText(queryText) };
27
28
  query.limit = parsePositiveInt(opts.limit, "--limit");
28
29
  if (opts.namespace) query.namespace = opts.namespace;
29
30
  if (opts.timeStart) query.timeStart = parsePositiveInt(opts.timeStart, "--time-start");
@@ -1,6 +1,7 @@
1
1
  import { createEngine, loadConfig, saveConfig } from "../core/engine.js";
2
2
  import { success, error, info, warn } from "../utils/style.js";
3
3
  import { parsePositiveInt } from "../utils/validate.js";
4
+ import { normalizeText } from "../utils/normalize.js";
4
5
 
5
6
  export function registerStore(program) {
6
7
  program
@@ -31,7 +32,7 @@ export function registerStore(program) {
31
32
  }
32
33
  }
33
34
 
34
- const result = await engine.store(content, options);
35
+ const result = await engine.store(normalizeText(content), options);
35
36
 
36
37
  if (result.status === "stored") {
37
38
  success(`Stored: ${result.id}`);
package/src/mcp/server.js CHANGED
@@ -3,6 +3,7 @@ import { createInterface } from "readline";
3
3
  import { createEngine, loadConfig } from "../core/engine.js";
4
4
  import { createEngineWithRetry } from "../core/retry.js";
5
5
  import { logger } from "../utils/logger.js";
6
+ import { normalizeText } from "../utils/normalize.js";
6
7
 
7
8
  const SERVER_INFO = {
8
9
  name: "shabti-memory",
@@ -228,7 +229,7 @@ async function handleToolsCall(id, params) {
228
229
  if (args.namespace) opts.namespace = args.namespace;
229
230
  if (args.tags) opts.tags = args.tags;
230
231
  if (args.ttl) opts.ttlSeconds = args.ttl;
231
- const result = await eng.store(content, opts);
232
+ const result = await eng.store(normalizeText(content), opts);
232
233
  return respond(id, {
233
234
  content: [
234
235
  {
@@ -256,7 +257,7 @@ async function handleToolsCall(id, params) {
256
257
  }
257
258
  try {
258
259
  const limit = args.limit || 10;
259
- const queryObj = { text: query, limit };
260
+ const queryObj = { text: normalizeText(query), limit };
260
261
  if (args.namespace) queryObj.namespace = args.namespace;
261
262
  const results = await eng.executeQuery(queryObj);
262
263
  const formatted = results.map((r) => ({
@@ -1,5 +1,6 @@
1
1
  import chalk from "chalk";
2
2
  import { success, info, warn, error } from "../utils/style.js";
3
+ import { normalizeText } from "../utils/normalize.js";
3
4
 
4
5
  const COMMANDS = {
5
6
  "/help": "Show this help message",
@@ -98,7 +99,7 @@ async function handleRemember(text, engine) {
98
99
  return true;
99
100
  }
100
101
  try {
101
- const result = await engine.store(text, {});
102
+ const result = await engine.store(normalizeText(text), {});
102
103
  if (result.status === "stored") {
103
104
  success(`Remembered: ${result.id}`);
104
105
  } else {
@@ -125,7 +126,7 @@ async function handleRecall(query, engine) {
125
126
  return true;
126
127
  }
127
128
  try {
128
- const results = await engine.executeQuery({ text: query, limit: 5 });
129
+ const results = await engine.executeQuery({ text: normalizeText(query), limit: 5 });
129
130
  console.log();
130
131
  if (results.length === 0) {
131
132
  console.log(chalk.dim(" No memories found."));
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Normalize text for consistent storage and search.
3
+ * Applies NFKC Unicode normalization, trims, and collapses whitespace.
4
+ *
5
+ * NFKC normalizes:
6
+ * - Full-width ASCII → half-width (A → A, 0 → 0)
7
+ * - Half-width katakana → full-width (カ → カ)
8
+ * - Compatibility characters (① → 1, ㍻ → 平成)
9
+ */
10
+ export function normalizeText(text) {
11
+ if (!text) return "";
12
+ return text.normalize("NFKC").replace(/\s+/g, " ").trim();
13
+ }