Skip to content
This repository was archived by the owner on Jun 3, 2026. It is now read-only.

Commit 9bf030d

Browse files
Merge pull request #200 from XortexAI/feature/Local-Setup
Adding local XMem setup
2 parents 04ad1a0 + f7ee0af commit 9bf030d

23 files changed

Lines changed: 2312 additions & 51 deletions

.gitattributes

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
* text=auto
2+
3+
*.js text eol=lf
4+
*.json text eol=lf
5+
*.py text eol=lf
6+
*.md text eol=lf
7+
*.yml text eol=lf
8+
*.yaml text eol=lf
9+
*.toml text eol=lf
10+
*.env text eol=lf
11+
*.env.* text eol=lf
12+
13+
*.png binary
14+
*.jpg binary
15+
*.jpeg binary
16+
*.gif binary
17+
*.ico binary

.github/workflows/security-scan.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ jobs:
2929
cache: pip
3030

3131
- name: Install bandit
32-
run: pip install bandit[toml]
32+
run: pip install "bandit[toml,sarif]"
3333

3434
- name: Run Bandit
3535
run: |
@@ -41,6 +41,7 @@ jobs:
4141
continue-on-error: true
4242

4343
- name: Upload Bandit SARIF
44+
if: always() && hashFiles('bandit-results.sarif') != ''
4445
uses: github/codeql-action/upload-sarif@v3
4546
with:
4647
sarif_file: bandit-results.sarif

.gitignore

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,6 @@ logs/
5252

5353
# Dev-only folders
5454
frontend/
55-
scripts/
56-
!scripts/
57-
!scripts/benchmark_v2_ingest.py
5855
tests/
5956
!tests/
6057
!tests/**/*.py
@@ -65,7 +62,6 @@ rust/
6562

6663
# Empty root files
6764
Makefile
68-
CHANGELOG.md
6965
ruff.toml
7066

7167
# GitHub templates (empty)
@@ -96,3 +92,8 @@ src/api/routes/search.py
9692
# Misc
9793
error.json
9894
MIGRATION_PLAN.md
95+
node_modules/
96+
repos/
97+
exports/
98+
tmp/
99+
.xmem/

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Changelog
2+
3+
## Unreleased
4+
5+
- Add local XMem setup through `npx create-xmem@latest` and `npm run dev`.
6+
- Add local Docker storage, Chrome extension build patching, diagnostics, verification, and context export/import/sync commands.

README.md

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -309,36 +309,54 @@ The industry standard benchmark for long-term conversational memory. Tests wheth
309309

310310
## Quickstart
311311

312-
### 1. Start the XMem Server
312+
### Local XMem
313313

314314
```bash
315-
git clone https://github.com/XortexLabs/xmem.git
315+
npx create-xmem@latest
316316
cd xmem
317+
npm run dev
318+
```
319+
320+
This works on Windows, macOS, and Linux. It creates a local XMem workspace, installs the backend, starts local storage, builds the Chrome extension, and launches the API at `http://localhost:8000`.
321+
322+
Local prerequisites:
317323

318-
# Install (requires Python 3.11+)
319-
pip install -e .
324+
- Git
325+
- Node.js 20+
326+
- Python 3.11+
327+
- Docker Desktop
328+
- Ollama, unless you add a cloud LLM key to `.env`
320329

321-
# Configure environment
322-
cp .env.example .env # Add your API keys
330+
After setup, load the extension from:
323331

324-
# Start
325-
uvicorn src.api.app:create_app --factory --host 0.0.0.0 --port 8000
332+
```text
333+
repos/xmem-extension/dist
326334
```
327335

328-
### 2. Install the Chrome Extension
336+
Chrome path: `chrome://extensions` -> enable Developer mode -> Load unpacked.
337+
338+
### Local Commands
329339

330340
```bash
331-
git clone https://github.com/XortexAI/xmem-extension.git
332-
npm install && npm run build
341+
npm run setup
342+
npm run start
343+
npm run verify
344+
npm run doctor
333345
```
334346

335-
Load `dist/` in Chrome via `chrome://extensions` → "Load unpacked". Point it to your server URL.
347+
If `.env` contains a real cloud LLM key, XMem uses that provider and keeps embeddings local with FastEmbed. If no cloud key is configured, XMem falls back to local Ollama and pulls the required local models during setup.
336348

349+
### Context Portability
337350

338-
https://github.com/user-attachments/assets/605985c3-ef27-4096-a28c-b0b4cc6f8b8d
351+
```bash
352+
npm run context:export
353+
npm run context:import -- --file ./exports/xmem-context.json
354+
npm run context:sync -- --file ./exports/xmem-context.json --server https://api.xmem.in --api-key <key>
355+
```
339356

357+
`context:export` writes a local context bundle that can be imported later or synced to an XMem server.
340358

341-
### 3. Index a Repository (Optional)
359+
### Index a Repository
342360

343361
```bash
344362
python -m src.scanner.runner \
@@ -352,8 +370,8 @@ python -m src.scanner.runner \
352370
> For a fully local setup with no cloud dependencies:
353371
> ```ini
354372
> FALLBACK_ORDER='["ollama"]'
355-
> EMBEDDING_PROVIDER=fastembed
356-
> VECTOR_STORE_PROVIDER=chroma
373+
> EMBEDDING_PROVIDER=ollama
374+
> VECTOR_STORE_PROVIDER=pgvector
357375
> ```
358376
> Then install local extras: `pip install -e ".[local]"`
359377

docker-compose.local.yml

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
name: xmem
2+
3+
services:
4+
postgres:
5+
image: pgvector/pgvector:pg16
6+
container_name: xmem-postgres
7+
environment:
8+
POSTGRES_DB: xmem
9+
POSTGRES_USER: xmem
10+
POSTGRES_PASSWORD: xmem
11+
ports:
12+
- "15432:5432"
13+
volumes:
14+
- postgres_data:/var/lib/postgresql/data
15+
healthcheck:
16+
test: ["CMD-SHELL", "pg_isready -U xmem -d xmem"]
17+
interval: 10s
18+
timeout: 5s
19+
retries: 10
20+
21+
mongo:
22+
image: mongo:7
23+
container_name: xmem-mongo
24+
ports:
25+
- "27018:27017"
26+
volumes:
27+
- mongo_data:/data/db
28+
healthcheck:
29+
test:
30+
[
31+
"CMD-SHELL",
32+
"mongosh --quiet --eval \"db.adminCommand('ping').ok\" || exit 1"
33+
]
34+
interval: 10s
35+
timeout: 5s
36+
retries: 12
37+
38+
neo4j:
39+
image: neo4j:5-community
40+
container_name: xmem-neo4j
41+
environment:
42+
NEO4J_AUTH: neo4j/local-password
43+
NEO4J_server_memory_heap_initial__size: 512m
44+
NEO4J_server_memory_heap_max__size: 2G
45+
NEO4J_server_memory_pagecache_size: 1G
46+
ports:
47+
- "17474:7474"
48+
- "17687:7687"
49+
volumes:
50+
- neo4j_data:/data
51+
- neo4j_logs:/logs
52+
healthcheck:
53+
test:
54+
[
55+
"CMD-SHELL",
56+
"cypher-shell -u neo4j -p local-password 'RETURN 1' || exit 1"
57+
]
58+
interval: 10s
59+
timeout: 5s
60+
retries: 12
61+
62+
volumes:
63+
postgres_data:
64+
mongo_data:
65+
neo4j_data:
66+
neo4j_logs:

package.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"name": "xmem",
3+
"version": "0.1.0",
4+
"private": true,
5+
"description": "XMem local-first memory layer.",
6+
"scripts": {
7+
"setup": "node ./scripts/xmem.js setup",
8+
"dev": "node ./scripts/xmem.js dev",
9+
"start": "node ./scripts/xmem.js start",
10+
"verify": "node ./scripts/xmem.js verify",
11+
"doctor": "node ./scripts/xmem.js doctor",
12+
"context:export": "node ./scripts/xmem.js context:export",
13+
"context:import": "node ./scripts/xmem.js context:import",
14+
"context:sync": "node ./scripts/xmem.js context:sync",
15+
"check:npm": "node ./scripts/check-npm-publish.js",
16+
"pack:create": "npm pack ./packages/create-xmem --dry-run",
17+
"publish:create": "npm publish ./packages/create-xmem --access public"
18+
},
19+
"engines": {
20+
"node": ">=20"
21+
}
22+
}
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
#!/usr/bin/env node
2+
3+
const fs = require("node:fs");
4+
const path = require("node:path");
5+
const { spawnSync } = require("node:child_process");
6+
7+
const DEFAULT_REPO = "https://github.com/XortexAI/XMem.git";
8+
const DEFAULT_BRANCH = "main";
9+
10+
function usage(exitCode = 0) {
11+
console.log(`Create a local XMem workspace
12+
13+
Usage:
14+
npx create-xmem@latest
15+
npx create-xmem@latest my-xmem
16+
17+
Options:
18+
--repo <url> XMem git repository URL
19+
--branch <name> XMem branch to use
20+
--help Show this message
21+
22+
After creation:
23+
cd xmem
24+
npm run dev
25+
`);
26+
process.exit(exitCode);
27+
}
28+
29+
function parseArgs(argv) {
30+
const options = {
31+
target: "xmem",
32+
repo: process.env.XMEM_REPO || DEFAULT_REPO,
33+
branch: process.env.XMEM_BRANCH || DEFAULT_BRANCH,
34+
};
35+
let targetSet = false;
36+
37+
function readOptionValue(index, name) {
38+
const value = argv[index + 1];
39+
if (!value || value.startsWith("-")) {
40+
console.error(`[create-xmem] ${name} requires a value.`);
41+
usage(1);
42+
}
43+
return value;
44+
}
45+
46+
for (let index = 0; index < argv.length; index += 1) {
47+
const arg = argv[index];
48+
49+
if (arg === "--help" || arg === "-h") {
50+
usage(0);
51+
}
52+
53+
if (arg === "--repo") {
54+
options.repo = readOptionValue(index, arg);
55+
index += 1;
56+
continue;
57+
}
58+
59+
if (arg === "--branch") {
60+
options.branch = readOptionValue(index, arg);
61+
index += 1;
62+
continue;
63+
}
64+
65+
if (arg.startsWith("-")) {
66+
console.error(`[create-xmem] Unknown option: ${arg}`);
67+
usage(1);
68+
}
69+
70+
if (!targetSet) {
71+
options.target = arg;
72+
targetSet = true;
73+
continue;
74+
}
75+
76+
console.error(`[create-xmem] Unexpected extra argument: ${arg}`);
77+
usage(1);
78+
}
79+
80+
if (!options.repo || !options.branch) {
81+
console.error("[create-xmem] --repo and --branch require values.");
82+
usage(1);
83+
}
84+
85+
return options;
86+
}
87+
88+
function runGit(args, cwd) {
89+
const result = spawnSync("git", args, {
90+
cwd,
91+
stdio: "inherit",
92+
shell: false,
93+
});
94+
95+
if (result.error) {
96+
console.error(`[create-xmem] Git is required: ${result.error.message}`);
97+
console.error("[create-xmem] Install Git, reopen your terminal, and run the command again.");
98+
process.exit(1);
99+
}
100+
101+
if (result.status !== 0) {
102+
process.exit(result.status || 1);
103+
}
104+
}
105+
106+
function assertCleanTarget(targetPath) {
107+
if (!fs.existsSync(targetPath)) {
108+
return;
109+
}
110+
111+
const entries = fs.readdirSync(targetPath);
112+
if (entries.length > 0) {
113+
console.error(`[create-xmem] Target folder is not empty: ${targetPath}`);
114+
console.error("[create-xmem] Choose a new folder name or empty the existing folder.");
115+
process.exit(1);
116+
}
117+
}
118+
119+
function removeGitMetadata(targetPath) {
120+
fs.rmSync(path.join(targetPath, ".git"), {
121+
recursive: true,
122+
force: true,
123+
});
124+
}
125+
126+
const options = parseArgs(process.argv.slice(2));
127+
const targetPath = path.resolve(process.cwd(), options.target);
128+
129+
assertCleanTarget(targetPath);
130+
131+
console.log(`[create-xmem] Creating XMem workspace in ${targetPath}`);
132+
runGit(["clone", "--depth", "1", "--branch", options.branch, options.repo, targetPath], process.cwd());
133+
removeGitMetadata(targetPath);
134+
135+
console.log("");
136+
console.log("[create-xmem] Created local XMem workspace.");
137+
console.log("");
138+
console.log("Next:");
139+
console.log(` cd ${path.relative(process.cwd(), targetPath) || "."}`);
140+
console.log(" npm run dev");
141+
console.log("");
142+
console.log("Chrome extension after setup:");
143+
console.log(" Load unpacked: repos/xmem-extension/dist");

0 commit comments

Comments
 (0)