cli-mem 0.3.6__tar.gz → 0.4.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.
Potentially problematic release.
This version of cli-mem might be problematic. Click here for more details.
- {cli_mem-0.3.6 → cli_mem-0.4.0}/ARCHITECTURE.md +156 -26
- {cli_mem-0.3.6 → cli_mem-0.4.0}/PHILOSOPHY.md +7 -4
- cli_mem-0.4.0/PKG-INFO +410 -0
- cli_mem-0.4.0/README.md +381 -0
- cli_mem-0.4.0/command-groups/databases/postgres-debug.json +31 -0
- cli_mem-0.4.0/command-groups/databases/redis-ops.json +31 -0
- cli_mem-0.4.0/command-groups/devops/docker-cleanup.json +31 -0
- cli_mem-0.4.0/command-groups/devops/incident-response.json +31 -0
- cli_mem-0.4.0/command-groups/devops/k8s-debug.json +35 -0
- cli_mem-0.4.0/command-groups/git/cleanup.json +31 -0
- cli_mem-0.4.0/command-groups/git/forensics.json +35 -0
- cli_mem-0.4.0/command-groups/networking/dns-debug.json +35 -0
- cli_mem-0.4.0/command-groups/networking/network-debug.json +31 -0
- cli_mem-0.4.0/command-groups/performance/system-profile.json +35 -0
- cli_mem-0.4.0/command-groups/security/headers-audit.json +31 -0
- cli_mem-0.4.0/command-groups/security/recon.json +35 -0
- cli_mem-0.4.0/command-groups/security/ssl-audit.json +31 -0
- cli_mem-0.4.0/docs/decisions/003-no-daemon.md +34 -0
- cli_mem-0.4.0/hooks/mem.bash +42 -0
- cli_mem-0.4.0/hooks/mem.fish +13 -0
- {cli_mem-0.3.6 → cli_mem-0.4.0}/pyproject.toml +1 -1
- {cli_mem-0.3.6 → cli_mem-0.4.0}/src/mem/__init__.py +1 -1
- {cli_mem-0.3.6 → cli_mem-0.4.0}/src/mem/cli.py +176 -32
- {cli_mem-0.3.6 → cli_mem-0.4.0}/src/mem/groups.py +56 -16
- {cli_mem-0.3.6 → cli_mem-0.4.0}/tests/conftest.py +1 -3
- {cli_mem-0.3.6 → cli_mem-0.4.0}/tests/test_groups.py +309 -1
- {cli_mem-0.3.6 → cli_mem-0.4.0}/tests/test_patterns.py +1 -3
- cli_mem-0.3.6/PKG-INFO +0 -315
- cli_mem-0.3.6/README.md +0 -286
- cli_mem-0.3.6/docs/decisions/003-no-daemon.md +0 -25
- {cli_mem-0.3.6 → cli_mem-0.4.0}/.github/workflows/ci.yml +0 -0
- {cli_mem-0.3.6 → cli_mem-0.4.0}/.github/workflows/release.yml +0 -0
- {cli_mem-0.3.6 → cli_mem-0.4.0}/.gitignore +0 -0
- {cli_mem-0.3.6 → cli_mem-0.4.0}/LICENSE +0 -0
- {cli_mem-0.3.6 → cli_mem-0.4.0}/assets/.gitkeep +0 -0
- {cli_mem-0.3.6 → cli_mem-0.4.0}/docs/decisions/001-jsonl-over-sqlite.md +0 -0
- {cli_mem-0.3.6 → cli_mem-0.4.0}/docs/decisions/002-apple-fm-sdk-for-patterns.md +0 -0
- {cli_mem-0.3.6 → cli_mem-0.4.0}/docs/decisions/004-per-repo-jsonl.md +0 -0
- {cli_mem-0.3.6 → cli_mem-0.4.0}/hooks/mem.zsh +0 -0
- {cli_mem-0.3.6 → cli_mem-0.4.0}/install.sh +0 -0
- {cli_mem-0.3.6 → cli_mem-0.4.0}/src/mem/_generable.py +0 -0
- {cli_mem-0.3.6 → cli_mem-0.4.0}/src/mem/capture.py +0 -0
- {cli_mem-0.3.6 → cli_mem-0.4.0}/src/mem/models.py +0 -0
- {cli_mem-0.3.6 → cli_mem-0.4.0}/src/mem/patterns.py +0 -0
- {cli_mem-0.3.6 → cli_mem-0.4.0}/src/mem/search.py +0 -0
- {cli_mem-0.3.6 → cli_mem-0.4.0}/src/mem/storage.py +0 -0
- {cli_mem-0.3.6 → cli_mem-0.4.0}/src/mem/variables.py +0 -0
- {cli_mem-0.3.6 → cli_mem-0.4.0}/tests/test_capture.py +0 -0
- {cli_mem-0.3.6 → cli_mem-0.4.0}/tests/test_search.py +0 -0
- {cli_mem-0.3.6 → cli_mem-0.4.0}/tests/test_storage.py +0 -0
|
@@ -17,12 +17,15 @@ Technical overview of how mem works under the hood.
|
|
|
17
17
|
┌─────────────────────────────────────────────────────────┐
|
|
18
18
|
│ src/mem/cli.py │
|
|
19
19
|
│ │
|
|
20
|
-
│ mem <query>
|
|
21
|
-
│ mem
|
|
22
|
-
│ mem
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
20
|
+
│ mem <query> mem save mem run │
|
|
21
|
+
│ mem list mem session mem stats │
|
|
22
|
+
│ mem forget mem export mem import │
|
|
23
|
+
│ mem group * mem saved * mem vars * │
|
|
24
|
+
│ mem init <shell> │
|
|
25
|
+
│ mem _capture (hidden) mem _sync (hidden) │
|
|
26
|
+
└───┬────────────┬──────────┬──────────┬──────────────────┘
|
|
27
|
+
│ │ │ │
|
|
28
|
+
▼ ▼ ▼ ▼
|
|
26
29
|
┌────────┐ ┌─────────┐ ┌──────────┐ ┌──────────────────┐
|
|
27
30
|
│capture │ │ search │ │ patterns │ │ storage │
|
|
28
31
|
│ .py │ │ .py │ │ .py │ │ .py │
|
|
@@ -30,19 +33,36 @@ Technical overview of how mem works under the hood.
|
|
|
30
33
|
│ hook │ │ score │ │ Apple FM │ │ JSONL read/write │
|
|
31
34
|
│ capture│ │ rank │ │ SDK │ │ JSON read/write │
|
|
32
35
|
│ session│ │ filter │ │ guided │ │ rotate / forget │
|
|
33
|
-
│ tracker│ │ dedup │ │ gen │ │
|
|
36
|
+
│ tracker│ │ dedup │ │ gen │ │ groups / vars │
|
|
37
|
+
│ auto- │ │ │ │ caching │ │ │
|
|
38
|
+
│ sync │ │ │ │ │ │ │
|
|
34
39
|
└───┬────┘ └────┬────┘ └────┬─────┘ └────────┬──────────┘
|
|
35
40
|
│ │ │ │
|
|
36
|
-
|
|
41
|
+
▼ ▼ ▼ ▼
|
|
42
|
+
┌────────┐ ┌─────────┐ ┌──────────┐ ┌──────────────────┐
|
|
43
|
+
│groups │ │variables│ │_generable│ │ models.py │
|
|
44
|
+
│ .py │ │ .py │ │ .py │ │ │
|
|
45
|
+
│ │ │ │ │ │ │ Pydantic schemas │
|
|
46
|
+
│ save │ │ parse │ │ @fm. │ │ CapturedCommand │
|
|
47
|
+
│ list │ │ resolve │ │ generable│ │ WorkSession │
|
|
48
|
+
│ export │ │ detect │ │ types │ │ GroupFile / Group │
|
|
49
|
+
│ import │ │ credent.│ │ │ │ VarsFile │
|
|
50
|
+
│ scope │ │ store │ │ │ │ PatternFile │
|
|
51
|
+
└────────┘ └─────────┘ └──────────┘ └──────────────────┘
|
|
37
52
|
│
|
|
38
53
|
▼
|
|
39
54
|
┌─────────────────────────────────────────────────────────┐
|
|
40
|
-
│ ~/.mem/ (filesystem)
|
|
55
|
+
│ ~/.mem/ (filesystem) │
|
|
41
56
|
│ │
|
|
42
|
-
│ repos/ sessions/ patterns/
|
|
57
|
+
│ repos/ sessions/ patterns/ │
|
|
43
58
|
│ myapp.jsonl 2026-03-05.jsonl kubectl.json │
|
|
44
59
|
│ infra.jsonl 2026-03-06.jsonl docker.json │
|
|
45
60
|
│ _global.jsonl git.json │
|
|
61
|
+
│ │
|
|
62
|
+
│ groups/ │
|
|
63
|
+
│ repos/ │
|
|
64
|
+
│ myapp.json vars.json .sync_counter │
|
|
65
|
+
│ _global.json .session_state.json │
|
|
46
66
|
└─────────────────────────────────────────────────────────┘
|
|
47
67
|
```
|
|
48
68
|
|
|
@@ -76,6 +96,11 @@ capture.SessionTracker.update()
|
|
|
76
96
|
│ checks idle time and repo change
|
|
77
97
|
│ closes session if boundary detected
|
|
78
98
|
▼
|
|
99
|
+
Auto-sync check
|
|
100
|
+
│ increments capture counter
|
|
101
|
+
│ every 20 captures: spawns `mem _sync` as detached background process
|
|
102
|
+
│ └── pattern extraction + data rotation, fully silent
|
|
103
|
+
▼
|
|
79
104
|
Done (total overhead: <5ms added to prompt)
|
|
80
105
|
```
|
|
81
106
|
|
|
@@ -112,24 +137,32 @@ Deduplicate by command string (keep highest score)
|
|
|
112
137
|
Return top N sorted by score descending
|
|
113
138
|
```
|
|
114
139
|
|
|
115
|
-
### Pattern Extraction
|
|
140
|
+
### Automatic Pattern Extraction
|
|
116
141
|
|
|
117
142
|
```
|
|
118
|
-
|
|
143
|
+
Every 20 captured commands:
|
|
119
144
|
│
|
|
120
145
|
▼
|
|
121
|
-
|
|
146
|
+
capture.py spawns `mem _sync` as detached background process
|
|
147
|
+
│ subprocess.Popen with start_new_session=True
|
|
148
|
+
│ no stdout, no stderr — completely invisible
|
|
149
|
+
▼
|
|
150
|
+
patterns.sync_all_patterns(silent=True)
|
|
122
151
|
│ reads ALL commands from all repo files
|
|
123
152
|
│ groups by tool (first token)
|
|
124
153
|
│ skips tools with <5 commands
|
|
125
154
|
│
|
|
126
155
|
▼ (for each tool with enough data)
|
|
127
156
|
patterns.extract_patterns_for_tool()
|
|
157
|
+
│
|
|
158
|
+
├── Load cached processed_commands from existing PatternFile
|
|
159
|
+
├── Skip already-processed commands (cache hit)
|
|
128
160
|
│
|
|
129
161
|
├── If apple-fm-sdk available:
|
|
130
|
-
│ │
|
|
131
|
-
│ │
|
|
132
|
-
│
|
|
162
|
+
│ │ generalize each NEW unique command individually
|
|
163
|
+
│ │ └── fresh LanguageModelSession per command (context safety)
|
|
164
|
+
│ │ merge with cached generalizations
|
|
165
|
+
│ └── aggregate frequencies by pattern (code)
|
|
133
166
|
│
|
|
134
167
|
└── If not available:
|
|
135
168
|
│ groups identical commands
|
|
@@ -138,16 +171,78 @@ patterns.extract_patterns_for_tool()
|
|
|
138
171
|
▼
|
|
139
172
|
storage.write_patterns()
|
|
140
173
|
│ writes to ~/.mem/patterns/<tool>.json
|
|
174
|
+
│ includes processed_commands list for caching
|
|
141
175
|
│ atomic: write tmp file, then rename
|
|
142
176
|
▼
|
|
143
177
|
storage.rotate()
|
|
144
178
|
│ removes commands older than 90 days
|
|
145
179
|
│ deletes session files older than 30 days
|
|
146
|
-
│ NEVER touches patterns/
|
|
180
|
+
│ NEVER touches patterns/ (accumulated learning)
|
|
181
|
+
▼
|
|
182
|
+
Done (user never sees any of this)
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Saving Commands with Variables
|
|
186
|
+
|
|
187
|
+
```
|
|
188
|
+
User runs: mem save "curl -H 'Bearer eyJhbG...' https://api.example.com" -g api
|
|
189
|
+
│
|
|
190
|
+
▼
|
|
191
|
+
cli.save()
|
|
192
|
+
│ interactive terminal detected
|
|
193
|
+
▼
|
|
194
|
+
variables.detect_credentials(cmd)
|
|
195
|
+
│
|
|
196
|
+
├── _command_may_contain_credentials() — heuristic pre-filter
|
|
197
|
+
│ checks for credential keywords + long tokens (≥16 chars)
|
|
198
|
+
│ skips simple commands (echo, ls, cd) entirely
|
|
199
|
+
│
|
|
200
|
+
├── If pre-filter passes and apple-fm-sdk available:
|
|
201
|
+
│ │ _detect_credentials_async() via Apple FM
|
|
202
|
+
│ │ └── guided generation with CredentialList generable
|
|
203
|
+
│ │
|
|
204
|
+
│ └── _deduplicate_detections() — post-filter
|
|
205
|
+
│ removes: hallucinations, URLs, hostnames, short values, subsets
|
|
206
|
+
│ extracts: actual secret from --flag=value syntax
|
|
207
|
+
│ normalizes: CamelCase → UPPER_SNAKE_CASE
|
|
208
|
+
│
|
|
209
|
+
└── If SDK unavailable: returns empty list (save proceeds normally)
|
|
210
|
+
│
|
|
211
|
+
▼
|
|
212
|
+
User confirms/renames each detected credential
|
|
213
|
+
│ command text updated with $VAR_NAME tokens
|
|
214
|
+
▼
|
|
215
|
+
variables.parse_variables() — detect $VAR_NAME tokens
|
|
216
|
+
variables.merge_var_declarations() — merge with --var flags
|
|
217
|
+
▼
|
|
218
|
+
groups.save_command()
|
|
219
|
+
│ stores command structure + var declarations (never values)
|
|
147
220
|
▼
|
|
148
221
|
Done
|
|
149
222
|
```
|
|
150
223
|
|
|
224
|
+
### Running Commands with Variables
|
|
225
|
+
|
|
226
|
+
```
|
|
227
|
+
User runs: mem run api API_TOKEN=abc123
|
|
228
|
+
│
|
|
229
|
+
▼
|
|
230
|
+
Parse inline VAR=VALUE arguments
|
|
231
|
+
▼
|
|
232
|
+
Resolve all variables upfront (before any execution):
|
|
233
|
+
1. Inline arguments ← highest priority
|
|
234
|
+
2. Shell environment (os.environ)
|
|
235
|
+
3. Persistent store (~/.mem/vars.json)
|
|
236
|
+
4. Default value (from --var at save time)
|
|
237
|
+
5. Interactive prompt ← last resort
|
|
238
|
+
▼
|
|
239
|
+
Display resolution summary
|
|
240
|
+
✓ $API_TOKEN resolved from arguments
|
|
241
|
+
✓ $NAMESPACE resolved from default
|
|
242
|
+
▼
|
|
243
|
+
Execute commands with substituted values
|
|
244
|
+
```
|
|
245
|
+
|
|
151
246
|
## JSONL Schemas
|
|
152
247
|
|
|
153
248
|
### Command Entry (`repos/<repo>.jsonl`)
|
|
@@ -157,7 +252,7 @@ Done
|
|
|
157
252
|
"command": "kubectl rollout restart deployment api",
|
|
158
253
|
"ts": 1709600000,
|
|
159
254
|
"dir": "/Users/mati/projects/services/api",
|
|
160
|
-
"repo": "services",
|
|
255
|
+
"repo": "/Users/mati/projects/services/api",
|
|
161
256
|
"exit_code": 0,
|
|
162
257
|
"duration_ms": 312,
|
|
163
258
|
"session": "abc123def456"
|
|
@@ -173,7 +268,7 @@ Done
|
|
|
173
268
|
"started_at": 1709599500,
|
|
174
269
|
"ended_at": 1709600400,
|
|
175
270
|
"dir": "/Users/mati/projects/services/api",
|
|
176
|
-
"repo": "services",
|
|
271
|
+
"repo": "/Users/mati/projects/services/api",
|
|
177
272
|
"commands": [
|
|
178
273
|
"kubectl logs api-7f9b --tail=100",
|
|
179
274
|
"kubectl get pods -n production",
|
|
@@ -192,14 +287,49 @@ Done
|
|
|
192
287
|
"pattern": "kubectl get <resource>",
|
|
193
288
|
"example": "kubectl get pods",
|
|
194
289
|
"frequency": 42
|
|
195
|
-
},
|
|
196
|
-
{
|
|
197
|
-
"pattern": "kubectl describe <resource> <name>",
|
|
198
|
-
"example": "kubectl describe pod api-7f9b",
|
|
199
|
-
"frequency": 17
|
|
200
290
|
}
|
|
201
291
|
],
|
|
202
|
-
"last_updated": 1709600000
|
|
292
|
+
"last_updated": 1709600000,
|
|
293
|
+
"processed_commands": [
|
|
294
|
+
"kubectl get pods",
|
|
295
|
+
"kubectl get services",
|
|
296
|
+
"kubectl describe pod api-7f9b"
|
|
297
|
+
]
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### Group File (`groups/repos/<repo>.json` or `groups/_global.json`)
|
|
302
|
+
|
|
303
|
+
```json
|
|
304
|
+
{
|
|
305
|
+
"saved": [
|
|
306
|
+
{ "cmd": "echo hello", "comment": "test" }
|
|
307
|
+
],
|
|
308
|
+
"groups": {
|
|
309
|
+
"api": {
|
|
310
|
+
"description": "API troubleshooting",
|
|
311
|
+
"commands": [
|
|
312
|
+
{
|
|
313
|
+
"cmd": "curl -H 'Authorization: Bearer $API_TOKEN' https://api.example.com/users",
|
|
314
|
+
"comment": "list users",
|
|
315
|
+
"vars": [
|
|
316
|
+
{ "name": "API_TOKEN", "default": null }
|
|
317
|
+
]
|
|
318
|
+
}
|
|
319
|
+
]
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
### Variable Store (`vars.json`)
|
|
326
|
+
|
|
327
|
+
```json
|
|
328
|
+
{
|
|
329
|
+
"vars": {
|
|
330
|
+
"API_TOKEN": { "value": "sk-abc123...", "last_used": 1709600000 },
|
|
331
|
+
"DB_HOST": { "value": "staging.db.internal", "last_used": 1709500000 }
|
|
332
|
+
}
|
|
203
333
|
}
|
|
204
334
|
```
|
|
205
335
|
|
|
@@ -215,5 +345,5 @@ See `docs/decisions/` for detailed ADRs:
|
|
|
215
345
|
|
|
216
346
|
- [001: JSONL Over SQLite](docs/decisions/001-jsonl-over-sqlite.md)
|
|
217
347
|
- [002: Apple FM SDK for Patterns](docs/decisions/002-apple-fm-sdk-for-patterns.md)
|
|
218
|
-
- [003: No Background
|
|
348
|
+
- [003: No Daemon, Background Subprocess Instead](docs/decisions/003-no-daemon.md)
|
|
219
349
|
- [004: Per-Repository JSONL Files](docs/decisions/004-per-repo-jsonl.md)
|
|
@@ -76,10 +76,13 @@ passively and speaks only when asked.
|
|
|
76
76
|
|
|
77
77
|
- Shell hooks append one line per command execution — no
|
|
78
78
|
prompts, no confirmations, no output.
|
|
79
|
-
- Pattern extraction
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
-
|
|
79
|
+
- Pattern extraction runs automatically in a detached background
|
|
80
|
+
process every 20 captures. Completely invisible — no stdout,
|
|
81
|
+
no stderr, no wait. The user never notices it.
|
|
82
|
+
- No daemons, no cron jobs, no persistent processes. The
|
|
83
|
+
background subprocess runs, finishes, and exits on its own.
|
|
84
|
+
- When the user searches or lists, results reflect the latest
|
|
85
|
+
patterns without any manual step.
|
|
83
86
|
|
|
84
87
|
**Why**: A tool that nags or slows the shell will be uninstalled
|
|
85
88
|
within a day. Silence is a feature.
|
cli_mem-0.4.0/PKG-INFO
ADDED
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cli-mem
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: Privacy-first CLI that turns shell history into searchable memory
|
|
5
|
+
Project-URL: GitHub, https://github.com/matinsaurralde/mem
|
|
6
|
+
Author: Matias Insaurralde
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Environment :: Console
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Topic :: System :: Shells
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Requires-Dist: click
|
|
20
|
+
Requires-Dist: pydantic
|
|
21
|
+
Requires-Dist: rich
|
|
22
|
+
Provides-Extra: ai
|
|
23
|
+
Requires-Dist: apple-fm-sdk; extra == 'ai'
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
26
|
+
Requires-Dist: pytest-asyncio; extra == 'dev'
|
|
27
|
+
Requires-Dist: ruff; extra == 'dev'
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
<p align="center">
|
|
31
|
+
<h1 align="center">mem</h1>
|
|
32
|
+
<p align="center">
|
|
33
|
+
<strong>Your shell, remembered.</strong>
|
|
34
|
+
</p>
|
|
35
|
+
<p align="center">
|
|
36
|
+
A privacy-first CLI that captures, searches, and organizes your terminal history<br>
|
|
37
|
+
with on-device AI. Nothing ever leaves your machine.
|
|
38
|
+
</p>
|
|
39
|
+
<p align="center">
|
|
40
|
+
<a href="#install"><img alt="macOS 26+" src="https://img.shields.io/badge/macOS-26%2B-blue?logo=apple&logoColor=white"></a>
|
|
41
|
+
<a href="#install"><img alt="Python 3.10+" src="https://img.shields.io/badge/python-3.10%2B-3776AB?logo=python&logoColor=white"></a>
|
|
42
|
+
<a href="LICENSE"><img alt="License: MIT" src="https://img.shields.io/badge/license-MIT-green"></a>
|
|
43
|
+
<a href="PHILOSOPHY.md"><img alt="Privacy: on-device" src="https://img.shields.io/badge/privacy-100%25%20on--device-brightgreen"></a>
|
|
44
|
+
</p>
|
|
45
|
+
</p>
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## What mem does
|
|
50
|
+
|
|
51
|
+
mem silently captures every command you type, then lets you search, save, and replay them — scoped to the git repo you're in.
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
mem deploy # search your history
|
|
55
|
+
mem save "cmd" -g ops # save a command to a group
|
|
56
|
+
mem run ops # run the group interactively
|
|
57
|
+
mem vars set API_KEY # store a secret for saved commands
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Unlike `Ctrl+R`, mem ranks results by frequency, recency, and the repo you're currently in. Unlike cloud-based tools, everything stays on your machine as plain text files in `~/.mem/`.
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Install
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
# Homebrew (recommended)
|
|
68
|
+
brew install matinsaurralde/tap/mem
|
|
69
|
+
|
|
70
|
+
# pip
|
|
71
|
+
pip install cli-mem
|
|
72
|
+
|
|
73
|
+
# With AI features (pattern extraction + credential detection)
|
|
74
|
+
pip install "cli-mem[ai]"
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Then activate the shell hook for your shell:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
# zsh
|
|
81
|
+
echo 'eval "$(mem init zsh)"' >> ~/.zshrc
|
|
82
|
+
source ~/.zshrc
|
|
83
|
+
|
|
84
|
+
# bash
|
|
85
|
+
echo 'eval "$(mem init bash)"' >> ~/.bashrc
|
|
86
|
+
source ~/.bashrc
|
|
87
|
+
|
|
88
|
+
# fish
|
|
89
|
+
echo 'mem init fish | source' >> ~/.config/fish/config.fish
|
|
90
|
+
source ~/.config/fish/config.fish
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
That's it. Every command you type is now silently captured with full context (directory, git repo, exit code, duration).
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Search
|
|
98
|
+
|
|
99
|
+
Just type `mem` followed by any keyword. Results are ranked by your current repo.
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
mem kubectl # search by keyword
|
|
103
|
+
mem "docker compose" # search by phrase
|
|
104
|
+
mem deploy -n 20 # more results
|
|
105
|
+
mem deploy --json # machine-readable output
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
1 kubectl apply -f deployment.yaml infra 2h ago
|
|
110
|
+
2 docker compose up -d backend 1d ago
|
|
111
|
+
3 fly deploy api 3d ago
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Patterns
|
|
115
|
+
|
|
116
|
+
mem automatically learns structural patterns from your history using on-device AI. No manual step needed — extraction runs in the background every 20 commands.
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
mem kubectl -p
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
Patterns for "kubectl":
|
|
124
|
+
|
|
125
|
+
kubectl get <resource>
|
|
126
|
+
kubectl describe <resource> <name>
|
|
127
|
+
kubectl logs <pod> [--tail=<n>]
|
|
128
|
+
kubectl apply -f <file>
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Groups
|
|
134
|
+
|
|
135
|
+
Groups are named collections of commands — like runbooks you can execute.
|
|
136
|
+
|
|
137
|
+
### Save commands to a group
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
mem save "kubectl get pods -n production" --group k8s --comment "list pods"
|
|
141
|
+
mem save "docker compose up -d" -g deploy -c "start services"
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Save the last command you ran:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
mem save "!" -g troubleshooting
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### List groups
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
mem list # show all groups and saved commands
|
|
154
|
+
mem list k8s # show commands in a specific group
|
|
155
|
+
mem list -g # global scope only
|
|
156
|
+
mem list -r # current repo only
|
|
157
|
+
mem list --json # JSON output
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Run a group
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
mem run k8s # run interactively (pick one or all)
|
|
164
|
+
mem run deploy -y # run all without prompts
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Manage groups
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
mem group rename old new # rename a group
|
|
171
|
+
mem group remove k8s # delete a group
|
|
172
|
+
mem group copy k8s --global # copy from repo to global scope
|
|
173
|
+
mem group edit k8s # open in $EDITOR
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Export and import
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
mem export k8s # copy JSON to clipboard
|
|
180
|
+
mem export k8s --format markdown # copy as markdown
|
|
181
|
+
mem export k8s --stdout # print instead of clipboard
|
|
182
|
+
|
|
183
|
+
mem import # import from clipboard (auto-detect format + group name)
|
|
184
|
+
mem import -g renamed # import from clipboard with custom group name
|
|
185
|
+
mem import runbook.json -g ops # import from file (auto-detects format)
|
|
186
|
+
mem import runbook.md -g ops # markdown works too
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## Variables
|
|
192
|
+
|
|
193
|
+
Saved commands can contain `$VAR_NAME` placeholders that get resolved at runtime. Values never get stored in group files.
|
|
194
|
+
|
|
195
|
+
### Save commands with variables
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
# Variables are detected automatically from $VAR_NAME tokens
|
|
199
|
+
mem save "ssh -i ~/.ssh/\$KEY_NAME ubuntu@\$BASTION_HOST" -g ssh
|
|
200
|
+
|
|
201
|
+
# Set a default value with --var
|
|
202
|
+
mem save "kubectl get pods -n \$NAMESPACE" -g k8s --var NAMESPACE=production
|
|
203
|
+
|
|
204
|
+
# AI detects hardcoded credentials and suggests variables
|
|
205
|
+
mem save "curl -H 'Authorization: Bearer eyJhbGci...' https://api.example.com/users" -g api
|
|
206
|
+
# Detected possible credential: Bearer token
|
|
207
|
+
# Suggested: curl -H 'Authorization: Bearer $API_TOKEN' ...
|
|
208
|
+
# Variable name [API_TOKEN]: █
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### Resolution priority
|
|
212
|
+
|
|
213
|
+
When `mem run` encounters variables, it resolves them in this order:
|
|
214
|
+
|
|
215
|
+
1. **Inline arguments** — `mem run api API_TOKEN=abc123`
|
|
216
|
+
2. **Shell environment** — `export API_TOKEN=abc123`
|
|
217
|
+
3. **Persistent store** — `mem vars set API_TOKEN`
|
|
218
|
+
4. **Default value** — from `--var NAME=default` at save time
|
|
219
|
+
5. **Interactive prompt** — asks you, only as a last resort
|
|
220
|
+
|
|
221
|
+
All prompts are collected upfront before any command runs. With `--yes`, unresolved variables cause an immediate error listing what's missing.
|
|
222
|
+
|
|
223
|
+
### Variable store
|
|
224
|
+
|
|
225
|
+
For values that persist across sessions but shouldn't be in `.zshrc`:
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
mem vars set API_TOKEN # hidden input (like sudo)
|
|
229
|
+
mem vars set DB_HOST staging.db # inline for non-sensitive values
|
|
230
|
+
mem vars list # shows names only, never values
|
|
231
|
+
mem vars remove API_TOKEN
|
|
232
|
+
mem vars clear
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Variable status in listings
|
|
236
|
+
|
|
237
|
+
`mem list` shows whether each variable is ready:
|
|
238
|
+
|
|
239
|
+
```
|
|
240
|
+
● backend / api
|
|
241
|
+
──────────────────────────────────────────────────────
|
|
242
|
+
1. curl -H 'Authorization: Bearer $API_TOKEN' .../users/$USER_ID
|
|
243
|
+
✓ $API_TOKEN resolved from environment
|
|
244
|
+
⚠ $USER_ID unset — pass inline: mem run api USER_ID=42
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## Scoping
|
|
250
|
+
|
|
251
|
+
Every group and saved command lives in either **repo scope** (tied to the current git repo) or **global scope** (available everywhere).
|
|
252
|
+
|
|
253
|
+
- Inside a git repo: defaults to repo scope
|
|
254
|
+
- Outside a git repo: defaults to global scope
|
|
255
|
+
- Use `--global` / `-g` to force global scope
|
|
256
|
+
- A repo group **shadows** a global group with the same name
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## Sessions
|
|
261
|
+
|
|
262
|
+
mem groups your commands into work sessions (based on 5-minute idle gaps and repo changes) so you can recall exactly what you did.
|
|
263
|
+
|
|
264
|
+
```bash
|
|
265
|
+
mem session "api outage" # search sessions by keyword
|
|
266
|
+
mem session debug --json # machine-readable output
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
```
|
|
270
|
+
┌ [1] Session: 2026-03-07 14:30 myapp ──────────────────┐
|
|
271
|
+
│ 1 kubectl logs api-7f9b --tail=100 │
|
|
272
|
+
│ 2 kubectl get pods -n production │
|
|
273
|
+
│ 3 kubectl rollout restart deploy api │
|
|
274
|
+
│ 4 curl -s localhost:8080/health │
|
|
275
|
+
└─────────────────────────────────────────────────────────┘
|
|
276
|
+
|
|
277
|
+
Replay a session? [number/n]: _
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
Replaying a session executes each command with per-command confirmation.
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
## Other commands
|
|
285
|
+
|
|
286
|
+
```bash
|
|
287
|
+
mem stats # top commands, repos, totals
|
|
288
|
+
mem stats --json # machine-readable stats
|
|
289
|
+
mem forget "API_KEY=sk-..." # permanently delete matching commands
|
|
290
|
+
mem forget "password" --yes # skip confirmation
|
|
291
|
+
mem init zsh # print shell hook code (also: bash, fish)
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
---
|
|
295
|
+
|
|
296
|
+
## How it works
|
|
297
|
+
|
|
298
|
+
```
|
|
299
|
+
You type a command
|
|
300
|
+
│
|
|
301
|
+
▼
|
|
302
|
+
Shell hook (preexec/precmd)
|
|
303
|
+
│
|
|
304
|
+
▼
|
|
305
|
+
mem _capture ← runs in background, <5ms
|
|
306
|
+
│
|
|
307
|
+
├─→ Append to ~/.mem/repos/<repo>.jsonl
|
|
308
|
+
└─→ Every 20 captures: background pattern extraction
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
**Search scoring:**
|
|
312
|
+
|
|
313
|
+
```
|
|
314
|
+
score = (frequency × 0.4) + (recency × 0.4) + (context × 0.2)
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
- **Frequency** — how often you've run this command
|
|
318
|
+
- **Recency** — exponential decay, 7-day half-life
|
|
319
|
+
- **Context** — 1.0 same repo, 0.5 same directory prefix, 0.0 otherwise
|
|
320
|
+
|
|
321
|
+
**AI features** use [Apple Foundation Models](https://developer.apple.com/machine-learning/api/) running entirely on your Mac's neural engine. No API keys, no cloud, no data leaves the machine. If Apple Intelligence isn't available, everything still works — you just don't get pattern extraction or credential detection.
|
|
322
|
+
|
|
323
|
+
---
|
|
324
|
+
|
|
325
|
+
## Storage
|
|
326
|
+
|
|
327
|
+
All data lives in `~/.mem/` as human-readable plain text:
|
|
328
|
+
|
|
329
|
+
```
|
|
330
|
+
~/.mem/
|
|
331
|
+
repos/
|
|
332
|
+
myapp.jsonl # commands captured in this git repo
|
|
333
|
+
_global.jsonl # commands outside any repo
|
|
334
|
+
sessions/
|
|
335
|
+
2026-03-07.jsonl # work sessions by date
|
|
336
|
+
patterns/
|
|
337
|
+
kubectl.json # AI-extracted command patterns
|
|
338
|
+
docker.json
|
|
339
|
+
groups/
|
|
340
|
+
repos/
|
|
341
|
+
myapp.json # repo-scoped groups and saved commands
|
|
342
|
+
_global.json # global groups and saved commands
|
|
343
|
+
vars.json # persistent variable store (0600 permissions)
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
Inspect anything:
|
|
347
|
+
|
|
348
|
+
```bash
|
|
349
|
+
cat ~/.mem/repos/myapp.jsonl
|
|
350
|
+
tail -f ~/.mem/repos/myapp.jsonl # watch commands arrive in real-time
|
|
351
|
+
grep "docker" ~/.mem/repos/*.jsonl # search across repos
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
Data rotation happens automatically in the background:
|
|
355
|
+
|
|
356
|
+
| Data | Retention |
|
|
357
|
+
|------|-----------|
|
|
358
|
+
| Commands | 90 days |
|
|
359
|
+
| Sessions | 30 days |
|
|
360
|
+
| Patterns | Forever |
|
|
361
|
+
|
|
362
|
+
---
|
|
363
|
+
|
|
364
|
+
## Privacy
|
|
365
|
+
|
|
366
|
+
- Zero network requests — not even update checks
|
|
367
|
+
- Zero telemetry — no analytics, no crash reports
|
|
368
|
+
- Zero cloud dependencies — fully offline, always
|
|
369
|
+
- On-device AI only — runs on your Mac's neural engine
|
|
370
|
+
- Plain text storage — no proprietary formats, you own your data
|
|
371
|
+
|
|
372
|
+
Read more in [PHILOSOPHY.md](PHILOSOPHY.md).
|
|
373
|
+
|
|
374
|
+
---
|
|
375
|
+
|
|
376
|
+
## Requirements
|
|
377
|
+
|
|
378
|
+
| Requirement | Version |
|
|
379
|
+
|-------------|---------|
|
|
380
|
+
| macOS | 26.0+ |
|
|
381
|
+
| Python | 3.10+ |
|
|
382
|
+
| Apple Intelligence | Optional (for patterns + credential detection) |
|
|
383
|
+
|
|
384
|
+
---
|
|
385
|
+
|
|
386
|
+
## Uninstall
|
|
387
|
+
|
|
388
|
+
```bash
|
|
389
|
+
brew uninstall mem # or: pip uninstall cli-mem
|
|
390
|
+
rm -rf ~/.mem # remove all captured data
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
Remove the shell hook line from your shell config (`~/.zshrc`, `~/.bashrc`, or `~/.config/fish/config.fish`).
|
|
394
|
+
|
|
395
|
+
---
|
|
396
|
+
|
|
397
|
+
## Contributing
|
|
398
|
+
|
|
399
|
+
```bash
|
|
400
|
+
git clone https://github.com/matinsaurralde/mem.git
|
|
401
|
+
cd mem
|
|
402
|
+
pip install -e ".[dev]"
|
|
403
|
+
pytest
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
Read [PHILOSOPHY.md](PHILOSOPHY.md) first.
|
|
407
|
+
|
|
408
|
+
## License
|
|
409
|
+
|
|
410
|
+
[MIT](LICENSE)
|