crossmem 0.1.0__tar.gz
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.
- crossmem-0.1.0/LICENSE +21 -0
- crossmem-0.1.0/PKG-INFO +184 -0
- crossmem-0.1.0/README.md +161 -0
- crossmem-0.1.0/pyproject.toml +51 -0
- crossmem-0.1.0/src/crossmem/__init__.py +3 -0
- crossmem-0.1.0/src/crossmem/cli.py +125 -0
- crossmem-0.1.0/src/crossmem/graph.py +347 -0
- crossmem-0.1.0/src/crossmem/ingest.py +160 -0
- crossmem-0.1.0/src/crossmem/server.py +219 -0
- crossmem-0.1.0/src/crossmem/store.py +234 -0
- crossmem-0.1.0/src/crossmem/sync.py +200 -0
crossmem-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Md Niajul Hasan
|
|
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.
|
crossmem-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: crossmem
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Cross-project memory for AI coding agents
|
|
5
|
+
Keywords: ai,memory,mcp,developer-tools,cross-project
|
|
6
|
+
Author: Md Niajul Hasan
|
|
7
|
+
Author-email: Md Niajul Hasan <niajul1992@gmail.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
16
|
+
Requires-Dist: click>=8.0
|
|
17
|
+
Requires-Dist: mcp>=1.27.0
|
|
18
|
+
Requires-Dist: pytest>=8.0 ; extra == 'dev'
|
|
19
|
+
Requires-Dist: ruff>=0.4 ; extra == 'dev'
|
|
20
|
+
Requires-Python: >=3.12
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# crossmem
|
|
25
|
+
|
|
26
|
+
Your AI assistant remembers everything you've ever solved — across every project and every tool.
|
|
27
|
+
|
|
28
|
+
## The problem
|
|
29
|
+
|
|
30
|
+
You're working on project B. You need credential masking. You know you solved this in project A three months ago — but your AI assistant has no idea. It starts from scratch, suggests a different approach, and you waste an hour getting back to where you already were.
|
|
31
|
+
|
|
32
|
+
Here's what's happening under the hood:
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
~/.claude/projects/
|
|
36
|
+
├── project-A/memory/MEMORY.md ← Claude remembers here
|
|
37
|
+
├── project-B/memory/MEMORY.md ← ...but can't see here
|
|
38
|
+
└── project-C/memory/MEMORY.md ← ...or here
|
|
39
|
+
|
|
40
|
+
~/.gemini/GEMINI.md ← Gemini's memories (separate silo entirely)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Every project is a silo. Every tool is a silo. Knowledge doesn't compound — it resets.
|
|
44
|
+
|
|
45
|
+
## The fix
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
$ crossmem ingest
|
|
49
|
+
Ingested: 298 memories across 10 projects (Claude Code + Gemini CLI)
|
|
50
|
+
|
|
51
|
+
$ crossmem search "credential masking"
|
|
52
|
+
Found 3 results for "credential masking":
|
|
53
|
+
|
|
54
|
+
[1] project-A / Security
|
|
55
|
+
Source: MEMORY.md
|
|
56
|
+
- Credentials masked in experience_memory before persisting (_mask_actions)...
|
|
57
|
+
|
|
58
|
+
[2] project-B / Security
|
|
59
|
+
Source: MEMORY.md
|
|
60
|
+
- Credentials masked via _mask_context_credentials() + _mask_text()...
|
|
61
|
+
|
|
62
|
+
[3] project-A / Security
|
|
63
|
+
Source: GEMINI.md
|
|
64
|
+
- Credential masking pattern: _mask_actions for persistence, _mask_text for logs...
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Three results. Two projects. Two AI tools. One query. The pattern was already solved — twice.
|
|
68
|
+
|
|
69
|
+
> Unlike Mem0 (cloud-based, API-key required) or Basic Memory (single-tool), crossmem is local-only, zero-config, and aggregates across both tools and projects.
|
|
70
|
+
|
|
71
|
+
## Install
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
pip install crossmem
|
|
75
|
+
# or
|
|
76
|
+
uv pip install crossmem
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Quick start
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
pip install crossmem # 1. Install
|
|
83
|
+
crossmem ingest # 2. Index all your AI memories
|
|
84
|
+
crossmem search "retry" # 3. Search across every project
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
That's it. Three commands, zero config. crossmem finds Claude Code and Gemini CLI memory files automatically.
|
|
88
|
+
|
|
89
|
+
To give your AI tools direct access, add the MCP server to your config (see [MCP Server](#mcp-server) below) — then `mem_recall()` and `mem_search()` just work inside your coding sessions.
|
|
90
|
+
|
|
91
|
+
## Usage
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
# Ingest Claude Code + Gemini CLI memories
|
|
95
|
+
crossmem ingest
|
|
96
|
+
|
|
97
|
+
# Search across every project
|
|
98
|
+
crossmem search "JWT token rotation"
|
|
99
|
+
crossmem search "retry strategy" -p backend-api
|
|
100
|
+
crossmem search "docker compose" -n 5
|
|
101
|
+
|
|
102
|
+
# Sync Claude memories → Gemini CLI
|
|
103
|
+
crossmem sync # sync everything
|
|
104
|
+
crossmem sync -p backend-api # sync one project + shared patterns
|
|
105
|
+
|
|
106
|
+
# Watch for changes and auto-sync
|
|
107
|
+
crossmem sync-watch # polls every 30s
|
|
108
|
+
crossmem sync-watch --interval 10 # custom interval
|
|
109
|
+
|
|
110
|
+
# Visualize the knowledge graph
|
|
111
|
+
crossmem graph
|
|
112
|
+
|
|
113
|
+
# See what's in the database
|
|
114
|
+
crossmem stats
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## How it works
|
|
118
|
+
|
|
119
|
+
1. **Ingest** — Reads Claude Code (`~/.claude/projects/*/memory/*.md`) and Gemini CLI (`~/.gemini/GEMINI.md`), splits into granular chunks, deduplicates by content hash
|
|
120
|
+
2. **Index** — Stores in SQLite with FTS5 full-text search (porter stemming + unicode61 tokenizer)
|
|
121
|
+
3. **Search** — Multi-word queries use AND logic by default. Quoted phrases for exact matches
|
|
122
|
+
4. **Learn** — AI tools save new discoveries via `mem_save` during sessions. Knowledge compounds automatically
|
|
123
|
+
5. **Sync** — Translates Claude's structured markdown into Gemini's flat bullet format, preserving each tool's own memories
|
|
124
|
+
|
|
125
|
+
## MCP Server
|
|
126
|
+
|
|
127
|
+
crossmem runs as an MCP server so AI coding tools can search, recall, and save memories in real-time.
|
|
128
|
+
|
|
129
|
+
### Setup
|
|
130
|
+
|
|
131
|
+
Add to your tool's MCP config:
|
|
132
|
+
|
|
133
|
+
**Claude Code** (`~/.mcp.json` for global, or `.mcp.json` in project root):
|
|
134
|
+
```json
|
|
135
|
+
{
|
|
136
|
+
"mcpServers": {
|
|
137
|
+
"crossmem": {
|
|
138
|
+
"command": "crossmem-server"
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**Gemini CLI** (`~/.gemini/settings.json`):
|
|
145
|
+
```json
|
|
146
|
+
{
|
|
147
|
+
"mcpServers": {
|
|
148
|
+
"crossmem": {
|
|
149
|
+
"command": "crossmem-server"
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
> **Note:** If `crossmem-server` isn't on PATH, use:
|
|
156
|
+
> `"command": "uvx", "args": ["--from", "crossmem", "crossmem-server"]`
|
|
157
|
+
|
|
158
|
+
### Tools
|
|
159
|
+
|
|
160
|
+
| Tool | Description |
|
|
161
|
+
|------|-------------|
|
|
162
|
+
| `mem_recall` | Load project context + cross-project patterns at session start (auto-detects project from cwd) |
|
|
163
|
+
| `mem_search` | Search across all memories (query, project filter, limit) |
|
|
164
|
+
| `mem_save` | Save a discovery during a session — immediately searchable |
|
|
165
|
+
| `mem_ingest` | Refresh the index when memory files change |
|
|
166
|
+
|
|
167
|
+
### Start manually
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
crossmem serve # starts MCP server on stdio
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Supported tools
|
|
174
|
+
|
|
175
|
+
| Tool | Ingestion |
|
|
176
|
+
|------|-----------|
|
|
177
|
+
| Claude Code | `~/.claude/projects/*/memory/*.md` |
|
|
178
|
+
| Gemini CLI | `~/.gemini/GEMINI.md` |
|
|
179
|
+
|
|
180
|
+
Ingestion is pluggable — PRs welcome for new tools.
|
|
181
|
+
|
|
182
|
+
## License
|
|
183
|
+
|
|
184
|
+
MIT
|
crossmem-0.1.0/README.md
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# crossmem
|
|
2
|
+
|
|
3
|
+
Your AI assistant remembers everything you've ever solved — across every project and every tool.
|
|
4
|
+
|
|
5
|
+
## The problem
|
|
6
|
+
|
|
7
|
+
You're working on project B. You need credential masking. You know you solved this in project A three months ago — but your AI assistant has no idea. It starts from scratch, suggests a different approach, and you waste an hour getting back to where you already were.
|
|
8
|
+
|
|
9
|
+
Here's what's happening under the hood:
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
~/.claude/projects/
|
|
13
|
+
├── project-A/memory/MEMORY.md ← Claude remembers here
|
|
14
|
+
├── project-B/memory/MEMORY.md ← ...but can't see here
|
|
15
|
+
└── project-C/memory/MEMORY.md ← ...or here
|
|
16
|
+
|
|
17
|
+
~/.gemini/GEMINI.md ← Gemini's memories (separate silo entirely)
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Every project is a silo. Every tool is a silo. Knowledge doesn't compound — it resets.
|
|
21
|
+
|
|
22
|
+
## The fix
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
$ crossmem ingest
|
|
26
|
+
Ingested: 298 memories across 10 projects (Claude Code + Gemini CLI)
|
|
27
|
+
|
|
28
|
+
$ crossmem search "credential masking"
|
|
29
|
+
Found 3 results for "credential masking":
|
|
30
|
+
|
|
31
|
+
[1] project-A / Security
|
|
32
|
+
Source: MEMORY.md
|
|
33
|
+
- Credentials masked in experience_memory before persisting (_mask_actions)...
|
|
34
|
+
|
|
35
|
+
[2] project-B / Security
|
|
36
|
+
Source: MEMORY.md
|
|
37
|
+
- Credentials masked via _mask_context_credentials() + _mask_text()...
|
|
38
|
+
|
|
39
|
+
[3] project-A / Security
|
|
40
|
+
Source: GEMINI.md
|
|
41
|
+
- Credential masking pattern: _mask_actions for persistence, _mask_text for logs...
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Three results. Two projects. Two AI tools. One query. The pattern was already solved — twice.
|
|
45
|
+
|
|
46
|
+
> Unlike Mem0 (cloud-based, API-key required) or Basic Memory (single-tool), crossmem is local-only, zero-config, and aggregates across both tools and projects.
|
|
47
|
+
|
|
48
|
+
## Install
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pip install crossmem
|
|
52
|
+
# or
|
|
53
|
+
uv pip install crossmem
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Quick start
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
pip install crossmem # 1. Install
|
|
60
|
+
crossmem ingest # 2. Index all your AI memories
|
|
61
|
+
crossmem search "retry" # 3. Search across every project
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
That's it. Three commands, zero config. crossmem finds Claude Code and Gemini CLI memory files automatically.
|
|
65
|
+
|
|
66
|
+
To give your AI tools direct access, add the MCP server to your config (see [MCP Server](#mcp-server) below) — then `mem_recall()` and `mem_search()` just work inside your coding sessions.
|
|
67
|
+
|
|
68
|
+
## Usage
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
# Ingest Claude Code + Gemini CLI memories
|
|
72
|
+
crossmem ingest
|
|
73
|
+
|
|
74
|
+
# Search across every project
|
|
75
|
+
crossmem search "JWT token rotation"
|
|
76
|
+
crossmem search "retry strategy" -p backend-api
|
|
77
|
+
crossmem search "docker compose" -n 5
|
|
78
|
+
|
|
79
|
+
# Sync Claude memories → Gemini CLI
|
|
80
|
+
crossmem sync # sync everything
|
|
81
|
+
crossmem sync -p backend-api # sync one project + shared patterns
|
|
82
|
+
|
|
83
|
+
# Watch for changes and auto-sync
|
|
84
|
+
crossmem sync-watch # polls every 30s
|
|
85
|
+
crossmem sync-watch --interval 10 # custom interval
|
|
86
|
+
|
|
87
|
+
# Visualize the knowledge graph
|
|
88
|
+
crossmem graph
|
|
89
|
+
|
|
90
|
+
# See what's in the database
|
|
91
|
+
crossmem stats
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## How it works
|
|
95
|
+
|
|
96
|
+
1. **Ingest** — Reads Claude Code (`~/.claude/projects/*/memory/*.md`) and Gemini CLI (`~/.gemini/GEMINI.md`), splits into granular chunks, deduplicates by content hash
|
|
97
|
+
2. **Index** — Stores in SQLite with FTS5 full-text search (porter stemming + unicode61 tokenizer)
|
|
98
|
+
3. **Search** — Multi-word queries use AND logic by default. Quoted phrases for exact matches
|
|
99
|
+
4. **Learn** — AI tools save new discoveries via `mem_save` during sessions. Knowledge compounds automatically
|
|
100
|
+
5. **Sync** — Translates Claude's structured markdown into Gemini's flat bullet format, preserving each tool's own memories
|
|
101
|
+
|
|
102
|
+
## MCP Server
|
|
103
|
+
|
|
104
|
+
crossmem runs as an MCP server so AI coding tools can search, recall, and save memories in real-time.
|
|
105
|
+
|
|
106
|
+
### Setup
|
|
107
|
+
|
|
108
|
+
Add to your tool's MCP config:
|
|
109
|
+
|
|
110
|
+
**Claude Code** (`~/.mcp.json` for global, or `.mcp.json` in project root):
|
|
111
|
+
```json
|
|
112
|
+
{
|
|
113
|
+
"mcpServers": {
|
|
114
|
+
"crossmem": {
|
|
115
|
+
"command": "crossmem-server"
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**Gemini CLI** (`~/.gemini/settings.json`):
|
|
122
|
+
```json
|
|
123
|
+
{
|
|
124
|
+
"mcpServers": {
|
|
125
|
+
"crossmem": {
|
|
126
|
+
"command": "crossmem-server"
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
> **Note:** If `crossmem-server` isn't on PATH, use:
|
|
133
|
+
> `"command": "uvx", "args": ["--from", "crossmem", "crossmem-server"]`
|
|
134
|
+
|
|
135
|
+
### Tools
|
|
136
|
+
|
|
137
|
+
| Tool | Description |
|
|
138
|
+
|------|-------------|
|
|
139
|
+
| `mem_recall` | Load project context + cross-project patterns at session start (auto-detects project from cwd) |
|
|
140
|
+
| `mem_search` | Search across all memories (query, project filter, limit) |
|
|
141
|
+
| `mem_save` | Save a discovery during a session — immediately searchable |
|
|
142
|
+
| `mem_ingest` | Refresh the index when memory files change |
|
|
143
|
+
|
|
144
|
+
### Start manually
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
crossmem serve # starts MCP server on stdio
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Supported tools
|
|
151
|
+
|
|
152
|
+
| Tool | Ingestion |
|
|
153
|
+
|------|-----------|
|
|
154
|
+
| Claude Code | `~/.claude/projects/*/memory/*.md` |
|
|
155
|
+
| Gemini CLI | `~/.gemini/GEMINI.md` |
|
|
156
|
+
|
|
157
|
+
Ingestion is pluggable — PRs welcome for new tools.
|
|
158
|
+
|
|
159
|
+
## License
|
|
160
|
+
|
|
161
|
+
MIT
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "crossmem"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Cross-project memory for AI coding agents"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "Md Niajul Hasan", email = "niajul1992@gmail.com" }
|
|
8
|
+
]
|
|
9
|
+
requires-python = ">=3.12"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
keywords = ["ai", "memory", "mcp", "developer-tools", "cross-project"]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 3 - Alpha",
|
|
15
|
+
"Intended Audience :: Developers",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Programming Language :: Python :: 3.12",
|
|
18
|
+
"Programming Language :: Python :: 3.13",
|
|
19
|
+
"Topic :: Software Development :: Libraries",
|
|
20
|
+
]
|
|
21
|
+
dependencies = [
|
|
22
|
+
"click>=8.0",
|
|
23
|
+
"mcp>=1.27.0",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[project.scripts]
|
|
27
|
+
crossmem = "crossmem.cli:main"
|
|
28
|
+
crossmem-server = "crossmem.server:main"
|
|
29
|
+
|
|
30
|
+
[project.optional-dependencies]
|
|
31
|
+
dev = [
|
|
32
|
+
"pytest>=8.0",
|
|
33
|
+
"ruff>=0.4",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
[build-system]
|
|
37
|
+
requires = ["uv_build>=0.10.10,<0.11.0"]
|
|
38
|
+
build-backend = "uv_build"
|
|
39
|
+
|
|
40
|
+
[tool.ruff]
|
|
41
|
+
line-length = 100
|
|
42
|
+
target-version = "py312"
|
|
43
|
+
|
|
44
|
+
[tool.ruff.lint]
|
|
45
|
+
select = ["E", "F", "I", "W", "UP"]
|
|
46
|
+
|
|
47
|
+
[tool.ruff.lint.per-file-ignores]
|
|
48
|
+
"src/crossmem/graph.py" = ["E501"] # HTML/JS template has long lines
|
|
49
|
+
|
|
50
|
+
[tool.pytest.ini_options]
|
|
51
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""CLI interface for crossmem."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
from crossmem.ingest import ingest_claude_memory, ingest_gemini_memory
|
|
6
|
+
from crossmem.store import DEFAULT_DB_PATH, MemoryStore
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@click.group()
|
|
10
|
+
@click.version_option()
|
|
11
|
+
def main() -> None:
|
|
12
|
+
"""Cross-project memory for AI coding agents."""
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@main.command()
|
|
16
|
+
def ingest() -> None:
|
|
17
|
+
"""Ingest memory files from AI coding tools."""
|
|
18
|
+
store = MemoryStore()
|
|
19
|
+
try:
|
|
20
|
+
click.echo("Ingesting Claude Code memories...")
|
|
21
|
+
added = ingest_claude_memory(store)
|
|
22
|
+
click.echo("Ingesting Gemini CLI memories...")
|
|
23
|
+
added += ingest_gemini_memory(store)
|
|
24
|
+
total = store.count()
|
|
25
|
+
stats = store.stats()
|
|
26
|
+
|
|
27
|
+
click.echo(f"\nAdded {added} new memories ({total} total)")
|
|
28
|
+
click.echo(f"Database: {DEFAULT_DB_PATH}")
|
|
29
|
+
click.echo(f"\nProjects ({len(stats)}):")
|
|
30
|
+
for project, count in stats.items():
|
|
31
|
+
click.echo(f" {project}: {count} memories")
|
|
32
|
+
finally:
|
|
33
|
+
store.close()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@main.command()
|
|
37
|
+
@click.argument("query")
|
|
38
|
+
@click.option("-p", "--project", default=None, help="Filter by project name")
|
|
39
|
+
@click.option("-n", "--limit", default=10, help="Max results")
|
|
40
|
+
def search(query: str, project: str | None, limit: int) -> None:
|
|
41
|
+
"""Search across all project memories."""
|
|
42
|
+
store = MemoryStore()
|
|
43
|
+
try:
|
|
44
|
+
results = store.search(query, limit=limit, project=project)
|
|
45
|
+
|
|
46
|
+
if not results:
|
|
47
|
+
click.echo(f'No results for "{query}"')
|
|
48
|
+
return
|
|
49
|
+
|
|
50
|
+
click.echo(f'Found {len(results)} results for "{query}":\n')
|
|
51
|
+
for i, result in enumerate(results, 1):
|
|
52
|
+
mem = result.memory
|
|
53
|
+
click.echo(f"[{i}] {mem.project} / {mem.section or '(root)'}")
|
|
54
|
+
click.echo(f" Source: {mem.source_file.split('/')[-1]}")
|
|
55
|
+
click.echo(f" {mem.snippet}")
|
|
56
|
+
click.echo()
|
|
57
|
+
finally:
|
|
58
|
+
store.close()
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@main.command()
|
|
62
|
+
@click.option("--port", default=8765, help="Port for local server")
|
|
63
|
+
def graph(port: int) -> None:
|
|
64
|
+
"""Visualize the knowledge graph in your browser."""
|
|
65
|
+
from crossmem.graph import serve_graph
|
|
66
|
+
|
|
67
|
+
store = MemoryStore()
|
|
68
|
+
if store.count() == 0:
|
|
69
|
+
click.echo("No memories yet. Run: crossmem ingest")
|
|
70
|
+
store.close()
|
|
71
|
+
return
|
|
72
|
+
serve_graph(store, port=port) # closes store internally before serving
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@main.command()
|
|
76
|
+
@click.option("-p", "--project", default=None, help="Sync this project + shared patterns")
|
|
77
|
+
def sync(project: str | None) -> None:
|
|
78
|
+
"""Sync Claude Code memories → Gemini CLI (one-shot)."""
|
|
79
|
+
from crossmem.sync import sync_once
|
|
80
|
+
|
|
81
|
+
count, changed = sync_once(project=project)
|
|
82
|
+
if changed:
|
|
83
|
+
label = f"{project} + shared patterns" if project else "all"
|
|
84
|
+
click.echo(f"Synced {count} memories ({label}) → ~/.gemini/GEMINI.md")
|
|
85
|
+
else:
|
|
86
|
+
click.echo(f"Already in sync ({count} memories)")
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@main.command(name="sync-watch")
|
|
90
|
+
@click.option("--interval", default=30, help="Poll interval in seconds")
|
|
91
|
+
@click.option("-p", "--project", default=None, help="Sync this project + shared patterns")
|
|
92
|
+
def sync_watch(interval: int, project: str | None) -> None:
|
|
93
|
+
"""Watch Claude memories and sync to Gemini on changes."""
|
|
94
|
+
from crossmem.sync import watch
|
|
95
|
+
|
|
96
|
+
watch(interval=interval, project=project)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@main.command()
|
|
100
|
+
def serve() -> None:
|
|
101
|
+
"""Start the MCP server (stdio transport)."""
|
|
102
|
+
from crossmem.server import main as serve_main
|
|
103
|
+
|
|
104
|
+
serve_main()
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@main.command()
|
|
108
|
+
def stats() -> None:
|
|
109
|
+
"""Show memory statistics."""
|
|
110
|
+
store = MemoryStore()
|
|
111
|
+
try:
|
|
112
|
+
total = store.count()
|
|
113
|
+
projects = store.stats()
|
|
114
|
+
|
|
115
|
+
if total == 0:
|
|
116
|
+
click.echo("No memories yet. Run: crossmem ingest")
|
|
117
|
+
return
|
|
118
|
+
|
|
119
|
+
click.echo(f"Total memories: {total}")
|
|
120
|
+
click.echo(f"Projects: {len(projects)}\n")
|
|
121
|
+
for project, count in projects.items():
|
|
122
|
+
click.echo(f" {project}: {count}")
|
|
123
|
+
click.echo(f"\nDatabase: {DEFAULT_DB_PATH}")
|
|
124
|
+
finally:
|
|
125
|
+
store.close()
|