@betterdb/memory 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 BetterDB Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,70 @@
1
+ # BetterDB Memory for Claude Code
2
+
3
+ Persistent, semantic memory for Claude Code sessions — powered by Valkey.
4
+
5
+ Every time you start a new Claude Code session, context is lost. BetterDB Memory
6
+ automatically captures what you did, embeds it as vectors in Valkey, and retrieves
7
+ relevant history at the start of each new session.
8
+
9
+ ## Quick Start
10
+
11
+ ### Prerequisites
12
+ - [Bun](https://bun.sh) runtime — **required** (the CLI and all hooks run on Bun, not Node)
13
+ - [Claude Code](https://docs.anthropic.com/en/docs/claude-code) installed
14
+ - [Valkey](https://valkey.io) 8.0+ with the Search module
15
+
16
+ ### Install
17
+
18
+ ```bash
19
+ # Bun must be installed first — npx delegates to it
20
+ npx @betterdb/memory install
21
+ ```
22
+
23
+ This will:
24
+ 1. Compile native hook binaries to `~/.betterdb/bin/`
25
+ 2. Register 4 lifecycle hooks with Claude Code
26
+ 3. Register the MCP server for mid-conversation tools
27
+ 4. Create the Valkey search index
28
+
29
+ ### How It Works
30
+
31
+ | Hook | What it does |
32
+ |------|-------------|
33
+ | **SessionStart** | Retrieves relevant memories via vector search, injects as context |
34
+ | **PostToolUse** | Records every tool call to a temp JSONL file |
35
+ | **Stop** | Summarizes the session, embeds it, stores in Valkey |
36
+ | **PreToolUse** | Surfaces file-specific history when accessing known files |
37
+
38
+ ### MCP Tools
39
+
40
+ Claude can use these mid-conversation:
41
+ - `search_context` — Semantic search over past sessions
42
+ - `store_insight` — Save a decision, pattern, or warning
43
+ - `list_open_threads` — Show unresolved items
44
+ - `forget` — Delete a specific memory
45
+
46
+ ### CLI Commands
47
+
48
+ ```bash
49
+ npx @betterdb/memory install # Set up hooks + MCP server
50
+ npx @betterdb/memory status # Check health
51
+ npx @betterdb/memory uninstall # Remove everything
52
+ npx @betterdb/memory maintain # Run aging/compression manually
53
+ ```
54
+
55
+ ### Configuration
56
+
57
+ Via environment variables or `~/.betterdb/memory.json`:
58
+
59
+ | Variable | Default | Description |
60
+ |----------|---------|-------------|
61
+ | `BETTERDB_VALKEY_URL` | `redis://localhost:6379` | Valkey connection URL |
62
+ | `BETTERDB_EMBED_MODEL` | auto-detect | Embedding provider |
63
+ | `BETTERDB_SUMMARIZE_MODEL` | auto-detect | Summarization provider |
64
+ | `BETTERDB_EMBED_DIM` | `1024` | Embedding dimensions |
65
+ | `BETTERDB_MAX_CONTEXT_MEMORIES` | `5` | Memories injected per session |
66
+ | `BETTERDB_CONTEXT_FILE` | `.betterdb_context.md` | Context injection file |
67
+
68
+ ## License
69
+
70
+ MIT
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@betterdb/memory",
3
+ "version": "0.1.0",
4
+ "description": "BetterDB Memory for Claude Code — Valkey-powered persistent memory across sessions",
5
+ "license": "MIT",
6
+ "author": "BetterDB Inc. <hello@betterdb.com>",
7
+ "homepage": "https://github.com/BetterDB-inc/memory",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/BetterDB-inc/memory.git"
11
+ },
12
+ "keywords": [
13
+ "claude-code",
14
+ "memory",
15
+ "valkey",
16
+ "mcp",
17
+ "ai-memory",
18
+ "vector-search",
19
+ "betterdb"
20
+ ],
21
+ "type": "module",
22
+ "bin": {
23
+ "betterdb-memory": "./src/index.ts"
24
+ },
25
+ "files": [
26
+ "src/",
27
+ "scripts/",
28
+ "README.md",
29
+ "LICENSE",
30
+ "package.json",
31
+ "tsconfig.json"
32
+ ],
33
+ "engines": {
34
+ "node": ">=20.0.0"
35
+ },
36
+ "scripts": {
37
+ "dev": "bun run src/index.ts",
38
+ "test": "bun test",
39
+ "test:unit": "bun test tests/unit",
40
+ "test:integration": "bun test tests/integration",
41
+ "validate": "bash scripts/validate-pack.sh",
42
+ "prepublishOnly": "bash scripts/validate-pack.sh",
43
+ "setup-index": "bun run scripts/setup-index.ts",
44
+ "migrate-embeddings": "bun run scripts/migrate-embeddings.ts",
45
+ "check-providers": "bun run scripts/check-providers.ts",
46
+ "typecheck": "tsc --noEmit"
47
+ },
48
+ "dependencies": {
49
+ "iovalkey": "^0.2.1",
50
+ "ollama": "^0.5.14",
51
+ "@modelcontextprotocol/sdk": "^1.12.1",
52
+ "zod": "^3.24.4",
53
+ "zod-to-json-schema": "^3.24.5",
54
+ "@anthropic-ai/sdk": "latest"
55
+ },
56
+ "devDependencies": {
57
+ "@types/bun": "latest",
58
+ "typescript": "^5.8.3"
59
+ }
60
+ }
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Standalone aging pipeline worker.
4
+ * Runs decay, compression, and distillation on all stored memories.
5
+ *
6
+ * Can be run via cron, docker compose, or manually:
7
+ * bun run scripts/aging-worker.ts
8
+ */
9
+ import { getValkeyClient } from "../src/client/valkey.js";
10
+ import { createModelClient } from "../src/client/model.js";
11
+ import { AgingPipeline } from "../src/memory/aging.js";
12
+
13
+ try {
14
+ const valkeyClient = await getValkeyClient();
15
+ const modelClient = await createModelClient();
16
+
17
+ const pipeline = new AgingPipeline(valkeyClient, modelClient);
18
+ await pipeline.runFullPipeline();
19
+
20
+ await valkeyClient.quit();
21
+ } catch (err) {
22
+ console.error("[betterdb] Aging worker failed:", err);
23
+ process.exit(1);
24
+ }
@@ -0,0 +1,103 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Checks which providers are available and prints a summary.
4
+ *
5
+ * Usage:
6
+ * bun run check-providers
7
+ * bun run check-providers -- --test (makes live API calls)
8
+ */
9
+ import { Ollama } from "ollama";
10
+ import { config } from "../src/config.js";
11
+ import { createModelClient } from "../src/client/model.js";
12
+ import { PRESET_CLEAN, PRESET_ATTRIBUTION, PRESET_LIGHTWEIGHT } from "../src/client/model.js";
13
+
14
+ const testMode = process.argv.includes("--test");
15
+
16
+ console.log("BetterDB Provider Check");
17
+ console.log("─────────────────────────────");
18
+
19
+ // --- Ollama ---
20
+ let ollamaModels: Set<string> = new Set();
21
+ try {
22
+ const ollama = new Ollama({ host: config.ollama.url });
23
+ const list = await ollama.list();
24
+ ollamaModels = new Set(list.models.map((m) => m.name.split(":")[0]!));
25
+
26
+ const presets = [PRESET_CLEAN, PRESET_ATTRIBUTION, PRESET_LIGHTWEIGHT];
27
+ const modelChecks: string[] = [];
28
+ for (const preset of presets) {
29
+ const embedBase = preset.embedModel.split(":")[0]!;
30
+ const summarizeBase = preset.summarizeModel.split(":")[0]!;
31
+ if (ollamaModels.has(embedBase)) modelChecks.push(`${preset.embedModel} ✓`);
32
+ if (ollamaModels.has(summarizeBase)) modelChecks.push(`${preset.summarizeModel} ✓`);
33
+ }
34
+
35
+ const modelStr = modelChecks.length > 0 ? ` (${modelChecks.join(", ")})` : "";
36
+ console.log(` Ollama ✓ running${modelStr}`);
37
+ } catch {
38
+ console.log(" Ollama ✗ not running");
39
+ }
40
+
41
+ // --- API Key Providers ---
42
+ const providers = [
43
+ { name: "OpenAI", key: config.providers.openaiKey },
44
+ { name: "Anthropic", key: config.providers.anthropicKey },
45
+ { name: "Voyage", key: config.providers.voyageKey },
46
+ { name: "Groq", key: config.providers.groqKey },
47
+ { name: "Together", key: config.providers.togetherKey },
48
+ ] as const;
49
+
50
+ for (const p of providers) {
51
+ const status = p.key ? "✓ key set" : "✗ no key";
52
+ console.log(` ${p.name.padEnd(14)}${status}`);
53
+ }
54
+
55
+ // --- Resolved Configuration ---
56
+ console.log("");
57
+ try {
58
+ const client = await createModelClient();
59
+ console.log("Resolved configuration:");
60
+ console.log(` Embed: ${client.preset.embedModel} (dim=${client.embedDim})`);
61
+ console.log(` Summarize: ${client.preset.summarizeModel}`);
62
+ } catch (err) {
63
+ console.log("Resolution failed:");
64
+ console.log(` ${err instanceof Error ? err.message : String(err)}`);
65
+ }
66
+
67
+ // --- Live Test ---
68
+ if (testMode) {
69
+ console.log("");
70
+ console.log("Live API tests:");
71
+ console.log("─────────────────────────────");
72
+
73
+ try {
74
+ const client = await createModelClient();
75
+
76
+ // Embed test
77
+ const embedStart = performance.now();
78
+ try {
79
+ const embedding = await client.embed("BetterDB provider test");
80
+ const embedMs = (performance.now() - embedStart).toFixed(0);
81
+ console.log(` Embed: ✓ ${embedding.length} dims, ${embedMs}ms`);
82
+ } catch (err) {
83
+ const embedMs = (performance.now() - embedStart).toFixed(0);
84
+ console.log(` Embed: ✗ ${err instanceof Error ? err.message : String(err)} (${embedMs}ms)`);
85
+ }
86
+
87
+ // Summarize test
88
+ const sumStart = performance.now();
89
+ try {
90
+ const summary = await client.summarize("User asked to add a login button. Added onClick handler to LoginForm component.");
91
+ const sumMs = (performance.now() - sumStart).toFixed(0);
92
+ console.log(` Summarize: ✓ "${summary.oneLineSummary}" (${sumMs}ms)`);
93
+ } catch (err) {
94
+ const sumMs = (performance.now() - sumStart).toFixed(0);
95
+ console.log(` Summarize: ✗ ${err instanceof Error ? err.message : String(err)} (${sumMs}ms)`);
96
+ }
97
+ } catch (err) {
98
+ console.log(` ✗ Cannot test — provider resolution failed: ${err instanceof Error ? err.message : String(err)}`);
99
+ }
100
+ } else {
101
+ console.log("");
102
+ console.log("Run 'bun run check-providers --test' to make a live API call to each available provider.");
103
+ }
@@ -0,0 +1,103 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # BetterDB Memory for Claude Code — Hook & MCP Installer
5
+ # Compiles hook binaries and registers them with Claude Code
6
+
7
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8
+ PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
9
+
10
+ # Check prerequisites
11
+ if ! command -v claude &>/dev/null; then
12
+ echo "ERROR: 'claude' CLI not found on PATH."
13
+ echo "Install Claude Code first: https://docs.anthropic.com/en/docs/claude-code"
14
+ exit 1
15
+ fi
16
+
17
+ if ! command -v bun &>/dev/null; then
18
+ echo "ERROR: 'bun' not found on PATH."
19
+ echo "Install Bun: https://bun.sh"
20
+ exit 1
21
+ fi
22
+
23
+ # Detect platform
24
+ PLATFORM="$(uname -s)-$(uname -m)"
25
+ echo "Platform: $PLATFORM"
26
+ echo "Note: Hook binaries are platform-specific. Rebuild if deploying elsewhere."
27
+ echo ""
28
+
29
+ # Build hooks
30
+ echo "Building hook binaries..."
31
+ cd "$PROJECT_DIR"
32
+ bun run build:hooks
33
+ echo "Hook binaries compiled to dist/hooks/"
34
+ echo ""
35
+
36
+ # Write hooks to global settings
37
+ GLOBAL_SETTINGS="$HOME/.claude/settings.json"
38
+ mkdir -p "$HOME/.claude"
39
+
40
+ # Create file with empty object if it doesn't exist
41
+ if [ ! -f "$GLOBAL_SETTINGS" ]; then
42
+ echo '{}' > "$GLOBAL_SETTINGS"
43
+ fi
44
+
45
+ # Merge hooks into existing settings using Bun (preserves other fields, overwrites hooks block)
46
+ # Each hook command sources the .env file first so compiled binaries get the right env vars
47
+ # (bun build --compile binaries don't auto-load .env like `bun run` does)
48
+ DIST_DIR="$PROJECT_DIR/dist/hooks"
49
+ ENV_FILE="$PROJECT_DIR/.env"
50
+
51
+ bun -e "
52
+ const fs = require('fs');
53
+ const settingsPath = '$GLOBAL_SETTINGS';
54
+ const envFile = '$ENV_FILE';
55
+ const distDir = '$DIST_DIR';
56
+ const existing = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
57
+
58
+ // Wrap each binary so it loads .env at runtime
59
+ const wrap = (bin) =>
60
+ 'bash -c ' + JSON.stringify('set -a; [ -f ' + envFile + ' ] && . ' + envFile + '; set +a; ' + distDir + '/' + bin);
61
+
62
+ const hooks = {
63
+ SessionStart: [{ hooks: [{ type: 'command', command: wrap('session-start') }] }],
64
+ PreToolUse: [{ matcher: '', hooks: [{ type: 'command', command: wrap('pre-tool') }] }],
65
+ PostToolUse: [{ matcher: '', hooks: [{ type: 'command', command: wrap('post-tool') }] }],
66
+ Stop: [{ hooks: [{ type: 'command', command: wrap('session-end') }] }],
67
+ };
68
+ fs.writeFileSync(settingsPath, JSON.stringify({ ...existing, hooks }, null, 2));
69
+ console.log('Hook configuration written to: ' + settingsPath);
70
+ "
71
+
72
+ # Register MCP server
73
+ echo ""
74
+ echo "Registering MCP server..."
75
+ claude mcp add-json betterdb-memory "{\"type\":\"stdio\",\"command\":\"bun\",\"args\":[\"run\",\"$PROJECT_DIR/src/mcp/server.ts\"]}" 2>/dev/null || true
76
+ echo "MCP server registered: betterdb-memory"
77
+
78
+ # Verify hooks
79
+ echo ""
80
+ echo "Verifying global settings..."
81
+ bun -e "
82
+ const fs = require('fs');
83
+ const settings = JSON.parse(fs.readFileSync('$HOME/.claude/settings.json', 'utf8'));
84
+ const hookCount = Object.keys(settings.hooks || {}).length;
85
+ console.log('Hooks registered: ' + hookCount + ' lifecycle events');
86
+ "
87
+
88
+ # Summary
89
+ echo ""
90
+ echo "=== Installation Complete ==="
91
+ echo ""
92
+ echo "Hooks written to: ~/.claude/settings.json"
93
+ echo " SessionStart → $DIST_DIR/session-start"
94
+ echo " Stop → $DIST_DIR/session-end"
95
+ echo " PreToolUse → $DIST_DIR/pre-tool"
96
+ echo " PostToolUse → $DIST_DIR/post-tool"
97
+ echo ""
98
+ echo "MCP server: betterdb-memory (stdio)"
99
+ echo ""
100
+ echo "Next steps:"
101
+ echo " 1. Start infrastructure: docker compose up -d"
102
+ echo " 2. Create search index: bun run setup-index"
103
+ echo " 3. Start a new Claude Code session — memories will be captured automatically"
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Re-embeds all stored memories using the currently configured model.
4
+ * Required after changing BETTERDB_EMBED_MODEL or BETTERDB_EMBED_DIM.
5
+ *
6
+ * Usage:
7
+ * bun run migrate-embeddings
8
+ * bun run migrate-embeddings -- --dry-run
9
+ */
10
+ import { getValkeyClient } from "../src/client/valkey.js";
11
+ import { createModelClient } from "../src/client/model.js";
12
+ import { config } from "../src/config.js";
13
+
14
+ const dryRun = process.argv.includes("--dry-run");
15
+
16
+ const valkeyClient = await getValkeyClient();
17
+ const modelClient = await createModelClient();
18
+
19
+ const memoryIds = await valkeyClient.listMemoryIds();
20
+ console.log(`Found ${memoryIds.length} memories to migrate.`);
21
+ console.log(`Target model: ${modelClient.preset.embedModel} (dim=${modelClient.embedDim})`);
22
+
23
+ if (dryRun) {
24
+ console.log("Dry run — no changes will be made.");
25
+ await valkeyClient.quit();
26
+ process.exit(0);
27
+ }
28
+
29
+ // Step 1: Drop existing index
30
+ console.log("Dropping existing index...");
31
+ await valkeyClient.dropIndex();
32
+
33
+ // Step 2: Update embed dimension metadata
34
+ const redis = await getValkeyClient();
35
+
36
+ // Step 3: Re-embed each memory
37
+ let processed = 0;
38
+ let failed = 0;
39
+
40
+ for (const id of memoryIds) {
41
+ const memory = await valkeyClient.getMemory(id);
42
+ if (!memory) {
43
+ failed++;
44
+ continue;
45
+ }
46
+
47
+ try {
48
+ const newEmbedding = await modelClient.embed(memory.summary.oneLineSummary);
49
+ await valkeyClient.storeMemory(memory, newEmbedding);
50
+ processed++;
51
+ if (processed % 10 === 0) {
52
+ console.log(`Processed ${processed}/${memoryIds.length}...`);
53
+ }
54
+ } catch (err) {
55
+ console.error(`Failed to re-embed memory ${id}:`, err);
56
+ failed++;
57
+ }
58
+ }
59
+
60
+ // Step 4: Recreate index with new dimension
61
+ console.log("Recreating index...");
62
+ await valkeyClient.ensureIndex(modelClient.embedDim);
63
+
64
+ console.log(`\nMigration complete:`);
65
+ console.log(` Processed: ${processed}`);
66
+ console.log(` Failed: ${failed}`);
67
+ console.log(` Index: ${config.valkey.indexName} (dim=${modelClient.embedDim})`);
68
+
69
+ await valkeyClient.quit();
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env bun
2
+ import { getValkeyClient } from "../src/client/valkey.js";
3
+ import { createModelClient } from "../src/client/model.js";
4
+ import { config } from "../src/config.js";
5
+
6
+ const client = await getValkeyClient();
7
+ const modelClient = await createModelClient();
8
+
9
+ await client.ensureIndex(modelClient.embedDim, modelClient.preset.embedModel);
10
+ console.log("Index ready:", config.valkey.indexName);
11
+ console.log("Embedding dimension:", modelClient.embedDim);
12
+ console.log("Preset:", modelClient.preset.embedModel, "/", modelClient.preset.summarizeModel);
13
+
14
+ await client.quit();
@@ -0,0 +1,67 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+
4
+ echo "=== Validating @betterdb/memory package ==="
5
+ FAIL=0
6
+
7
+ # Package name
8
+ NAME=$(node -e "console.log(require('./package.json').name)")
9
+ [ "$NAME" = "@betterdb/memory" ] && echo "✅ Name: $NAME" || { echo "❌ Name: $NAME (expected @betterdb/memory)"; FAIL=1; }
10
+
11
+ # files field
12
+ node -e "const p=require('./package.json'); if(!p.files) process.exit(1)" 2>/dev/null \
13
+ && echo "✅ Has files field" || { echo "❌ Missing files field"; FAIL=1; }
14
+
15
+ # src/ in files
16
+ node -e "const p=require('./package.json'); if(!p.files.includes('src/')) process.exit(1)" 2>/dev/null \
17
+ && echo "✅ src/ in files" || { echo "❌ src/ not in files"; FAIL=1; }
18
+
19
+ # dist/ NOT in files
20
+ node -e "const p=require('./package.json'); if(p.files && p.files.includes('dist/')) process.exit(1)" 2>/dev/null \
21
+ || { echo "❌ dist/ in files — remove it"; FAIL=1; }
22
+ echo "✅ dist/ not in files"
23
+
24
+ # bin entry
25
+ node -e "const p=require('./package.json'); if(!p.bin?.['betterdb-memory']) process.exit(1)" 2>/dev/null \
26
+ && echo "✅ bin: betterdb-memory" || { echo "❌ Missing bin entry"; FAIL=1; }
27
+
28
+ # bin target exists
29
+ BIN=$(node -e "console.log(require('./package.json').bin['betterdb-memory'])")
30
+ [ -f "$BIN" ] && echo "✅ bin target exists: $BIN" || { echo "❌ bin target missing: $BIN"; FAIL=1; }
31
+
32
+ # No monorepo artifacts
33
+ [ ! -f pnpm-workspace.yaml ] && echo "✅ No pnpm-workspace.yaml" || { echo "❌ pnpm-workspace.yaml exists"; FAIL=1; }
34
+ [ ! -f turbo.json ] && echo "✅ No turbo.json" || { echo "❌ turbo.json exists"; FAIL=1; }
35
+ [ ! -d packages ] && echo "✅ No packages/" || { echo "❌ packages/ directory exists"; FAIL=1; }
36
+
37
+ # No Docker artifacts to ship
38
+ PACK=$(npm pack --dry-run 2>&1)
39
+ echo "$PACK" | grep -q "Dockerfile" && { echo "❌ Dockerfile in package"; FAIL=1; } || echo "✅ No Dockerfile in package"
40
+
41
+ # Leaks
42
+ for leak in "node_modules" ".env" ".git/"; do
43
+ echo "$PACK" | grep -q "$leak" && { echo "❌ $leak leaking"; FAIL=1; }
44
+ done
45
+ echo "✅ No leaks"
46
+
47
+ # Standalone install
48
+ echo ""
49
+ echo "--- Standalone install ---"
50
+ TMPDIR=$(mktemp -d)
51
+ TARBALL=$(npm pack 2>/dev/null)
52
+ (
53
+ cd "$TMPDIR"
54
+ npm init -y > /dev/null 2>&1
55
+ if npm install "$OLDPWD/$TARBALL" > /dev/null 2>&1; then
56
+ echo "✅ Installs standalone"
57
+ [ -d "node_modules/@betterdb/memory/src" ] && echo "✅ src/ present" || { echo "❌ src/ missing"; FAIL=1; }
58
+ [ -f node_modules/.bin/betterdb-memory ] && echo "✅ bin linked" || echo "⚠️ bin not linked"
59
+ else
60
+ echo "❌ Install failed"; FAIL=1
61
+ fi
62
+ )
63
+ rm -rf "$TMPDIR"
64
+ rm -f "$TARBALL"
65
+
66
+ echo ""
67
+ [ $FAIL -eq 0 ] && echo "=== ✅ All checks passed ===" || { echo "=== ❌ FAILED ==="; exit 1; }