cli-mem 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.
@@ -0,0 +1,29 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [master]
6
+ pull_request:
7
+ branches: [master]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: macos-latest
12
+ strategy:
13
+ matrix:
14
+ python-version: ["3.11", "3.12"]
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+
18
+ - uses: actions/setup-python@v5
19
+ with:
20
+ python-version: ${{ matrix.python-version }}
21
+
22
+ - name: Install
23
+ run: pip install -e ".[dev]"
24
+
25
+ - name: Lint
26
+ run: ruff check .
27
+
28
+ - name: Test
29
+ run: pytest -v
@@ -0,0 +1,65 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ permissions:
9
+ contents: write
10
+
11
+ jobs:
12
+ release:
13
+ runs-on: macos-latest
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+
17
+ - name: Create GitHub Release
18
+ env:
19
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
20
+ run: |
21
+ gh release create "${{ github.ref_name }}" \
22
+ --title "${{ github.ref_name }}" \
23
+ --generate-notes
24
+
25
+ - name: Update Homebrew tap
26
+ env:
27
+ TAP_TOKEN: ${{ secrets.TAP_TOKEN }}
28
+ run: |
29
+ set -euo pipefail
30
+
31
+ TAG="${{ github.ref_name }}"
32
+ TARBALL_URL="https://github.com/${{ github.repository }}/archive/refs/tags/${TAG}.tar.gz"
33
+
34
+ # Download tarball and compute SHA256
35
+ SHA=$(curl -sL "$TARBALL_URL" | shasum -a 256 | cut -d' ' -f1)
36
+ echo "Tag: $TAG"
37
+ echo "SHA256: $SHA"
38
+
39
+ # Clone the tap repo
40
+ git clone "https://x-access-token:${TAP_TOKEN}@github.com/matinsaurralde/homebrew-tap.git" /tmp/tap
41
+ cd /tmp/tap
42
+
43
+ # Update only the release url and sha256 (lines 7-8), not dependency resources.
44
+ # Uses awk to replace only the FIRST url/sha256 pair matching the release pattern.
45
+ awk -v tag_url="$TARBALL_URL" -v sha="$SHA" '
46
+ /url.*github.com\/matinsaurralde\/mem\/archive/ {
47
+ print " url \"" tag_url "\""
48
+ found_url = 1
49
+ next
50
+ }
51
+ found_url && /sha256/ {
52
+ print " sha256 \"" sha "\""
53
+ found_url = 0
54
+ next
55
+ }
56
+ { print }
57
+ ' Formula/mem.rb > Formula/mem.rb.tmp
58
+ mv Formula/mem.rb.tmp Formula/mem.rb
59
+
60
+ git config user.name "github-actions[bot]"
61
+ git config user.email "github-actions[bot]@users.noreply.github.com"
62
+ git add Formula/mem.rb
63
+ git diff --cached --quiet && echo "No changes" && exit 0
64
+ git commit -m "mem ${TAG}"
65
+ git push
@@ -0,0 +1,44 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ *.egg-info/
7
+ *.egg
8
+ dist/
9
+ build/
10
+ *.whl
11
+
12
+ # Virtual environments
13
+ .venv/
14
+ venv/
15
+ env/
16
+
17
+ # Testing
18
+ .pytest_cache/
19
+ .coverage
20
+ htmlcov/
21
+ .mypy_cache/
22
+
23
+ # IDE
24
+ .vscode/
25
+ .idea/
26
+ *.swp
27
+ *.swo
28
+ *~
29
+
30
+ # OS
31
+ .DS_Store
32
+ Thumbs.db
33
+
34
+ # Internal tooling — not part of the public project
35
+ .specify/
36
+ .claude/
37
+ specs/
38
+ CLAUDE.md
39
+
40
+ # Homebrew formula (lives in separate homebrew-tap repo)
41
+ Formula/
42
+
43
+ # mem runtime data (never commit user history)
44
+ .mem/
@@ -0,0 +1,219 @@
1
+ # Architecture
2
+
3
+ Technical overview of how mem works under the hood.
4
+
5
+ ## Component Diagram
6
+
7
+ ```
8
+ ┌─────────────────────────────────────────────────────────┐
9
+ │ User's Shell │
10
+ │ │
11
+ │ preexec ──► capture cmd + start time │
12
+ │ precmd ──► compute exit code + duration │
13
+ │ └── mem _capture (background, &!) │
14
+ └────────────────────────┬────────────────────────────────┘
15
+
16
+
17
+ ┌─────────────────────────────────────────────────────────┐
18
+ │ src/mem/cli.py │
19
+ │ │
20
+ │ mem <query> mem sync mem session │
21
+ │ mem init <shell> mem stats mem forget │
22
+ │ mem _capture (hidden) │
23
+ └───┬────────────┬──────────────┬──────────────┬──────────┘
24
+ │ │ │ │
25
+ ▼ ▼ ▼ ▼
26
+ ┌────────┐ ┌─────────┐ ┌──────────┐ ┌──────────────────┐
27
+ │capture │ │ search │ │ patterns │ │ storage │
28
+ │ .py │ │ .py │ │ .py │ │ .py │
29
+ │ │ │ │ │ │ │ │
30
+ │ hook │ │ score │ │ Apple FM │ │ JSONL read/write │
31
+ │ capture│ │ rank │ │ SDK │ │ JSON read/write │
32
+ │ session│ │ filter │ │ guided │ │ rotate / forget │
33
+ │ tracker│ │ dedup │ │ gen │ │ │
34
+ └───┬────┘ └────┬────┘ └────┬─────┘ └────────┬──────────┘
35
+ │ │ │ │
36
+ └───────────┴───────────┴─────────────────┘
37
+
38
+
39
+ ┌─────────────────────────────────────────────────────────┐
40
+ │ ~/.mem/ (filesystem) │
41
+ │ │
42
+ │ repos/ sessions/ patterns/ │
43
+ │ myapp.jsonl 2026-03-05.jsonl kubectl.json │
44
+ │ infra.jsonl 2026-03-06.jsonl docker.json │
45
+ │ _global.jsonl git.json │
46
+ └─────────────────────────────────────────────────────────┘
47
+ ```
48
+
49
+ ## Data Flows
50
+
51
+ ### Command Capture
52
+
53
+ ```
54
+ User runs: kubectl get pods
55
+
56
+
57
+ preexec hook fires
58
+ │ saves command text + start timestamp
59
+
60
+ Command executes...
61
+
62
+
63
+ precmd hook fires
64
+ │ computes exit code + duration
65
+ │ runs: mem _capture "kubectl get pods" "/path" "0" "312" &!
66
+ │ └── background, disowned, silent
67
+
68
+ capture.capture_command()
69
+ │ detects git repo via `git rev-parse --show-toplevel`
70
+ │ builds CapturedCommand with all metadata
71
+
72
+ storage.append_command()
73
+ │ appends one JSON line to ~/.mem/repos/<repo>.jsonl
74
+
75
+ capture.SessionTracker.update()
76
+ │ checks idle time and repo change
77
+ │ closes session if boundary detected
78
+
79
+ Done (total overhead: <5ms added to prompt)
80
+ ```
81
+
82
+ ### Search Query
83
+
84
+ ```
85
+ User runs: mem kubectl
86
+
87
+
88
+ cli.cli() dispatches to search
89
+ │ detects current repo via git
90
+
91
+ search.search("kubectl", current_repo="infra")
92
+
93
+ ├── read ~/.mem/repos/infra.jsonl (current repo first)
94
+ ├── read ~/.mem/repos/_global.jsonl
95
+ ├── read other repo files
96
+
97
+
98
+ Filter: substring match "kubectl" in command text
99
+
100
+
101
+ Score each match:
102
+ score = (frequency × 0.4) + (recency × 0.4) + (context × 0.2)
103
+
104
+ ├── frequency: count of identical commands
105
+ ├── recency: exp(-days × ln2/7), half-life 7 days
106
+ └── context: 1.0 same repo, 0.5 same prefix, 0.0 other
107
+
108
+
109
+ Deduplicate by command string (keep highest score)
110
+
111
+
112
+ Return top N sorted by score descending
113
+ ```
114
+
115
+ ### Pattern Extraction
116
+
117
+ ```
118
+ User runs: mem sync
119
+
120
+
121
+ patterns.sync_all_patterns()
122
+ │ reads ALL commands from all repo files
123
+ │ groups by tool (first token)
124
+ │ skips tools with <5 commands
125
+
126
+ ▼ (for each tool with enough data)
127
+ patterns.extract_patterns_for_tool()
128
+
129
+ ├── If apple-fm-sdk available:
130
+ │ │ builds prompt with command list
131
+ │ │ calls Apple FM with Pydantic guided generation
132
+ │ └── returns PatternExtractionResult
133
+
134
+ └── If not available:
135
+ │ groups identical commands
136
+ └── returns frequency-based "patterns" (fallback)
137
+
138
+
139
+ storage.write_patterns()
140
+ │ writes to ~/.mem/patterns/<tool>.json
141
+ │ atomic: write tmp file, then rename
142
+
143
+ storage.rotate()
144
+ │ removes commands older than 90 days
145
+ │ deletes session files older than 30 days
146
+ │ NEVER touches patterns/
147
+
148
+ Done
149
+ ```
150
+
151
+ ## JSONL Schemas
152
+
153
+ ### Command Entry (`repos/<repo>.jsonl`)
154
+
155
+ ```json
156
+ {
157
+ "command": "kubectl rollout restart deployment api",
158
+ "ts": 1709600000,
159
+ "dir": "/Users/mati/projects/services/api",
160
+ "repo": "services",
161
+ "exit_code": 0,
162
+ "duration_ms": 312,
163
+ "session": "abc123def456"
164
+ }
165
+ ```
166
+
167
+ ### Session Entry (`sessions/YYYY-MM-DD.jsonl`)
168
+
169
+ ```json
170
+ {
171
+ "id": "abc123def456",
172
+ "summary": "debugging API outage in production",
173
+ "started_at": 1709599500,
174
+ "ended_at": 1709600400,
175
+ "dir": "/Users/mati/projects/services/api",
176
+ "repo": "services",
177
+ "commands": [
178
+ "kubectl logs api-7f9b --tail=100",
179
+ "kubectl get pods -n production",
180
+ "kubectl rollout restart deployment api"
181
+ ]
182
+ }
183
+ ```
184
+
185
+ ### Pattern Entry (`patterns/<tool>.json`)
186
+
187
+ ```json
188
+ {
189
+ "tool": "kubectl",
190
+ "patterns": [
191
+ {
192
+ "pattern": "kubectl get <resource>",
193
+ "example": "kubectl get pods",
194
+ "frequency": 42
195
+ },
196
+ {
197
+ "pattern": "kubectl describe <resource> <name>",
198
+ "example": "kubectl describe pod api-7f9b",
199
+ "frequency": 17
200
+ }
201
+ ],
202
+ "last_updated": 1709600000
203
+ }
204
+ ```
205
+
206
+ ## Session Boundary Detection
207
+
208
+ A new session begins when either condition is met:
209
+ - **Idle time > 300 seconds (5 minutes)**: Long enough that brief interruptions don't split sessions, short enough that genuine context switches are detected.
210
+ - **Repository change**: Switching to a different git repo almost always means a different task.
211
+
212
+ ## Design Decisions
213
+
214
+ See `docs/decisions/` for detailed ADRs:
215
+
216
+ - [001: JSONL Over SQLite](docs/decisions/001-jsonl-over-sqlite.md)
217
+ - [002: Apple FM SDK for Patterns](docs/decisions/002-apple-fm-sdk-for-patterns.md)
218
+ - [003: No Background Daemon](docs/decisions/003-no-daemon.md)
219
+ - [004: Per-Repository JSONL Files](docs/decisions/004-per-repo-jsonl.md)
cli_mem-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Matias Federico Insaurralde
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.
@@ -0,0 +1,111 @@
1
+ # Philosophy
2
+
3
+ > Your shell history, understood. Not just searched.
4
+
5
+ These are the principles that guide every design decision in mem.
6
+
7
+ ---
8
+
9
+ ## I. Privacy First
10
+
11
+ All processing executes entirely on-device. Zero network requests.
12
+ Zero telemetry. Zero cloud dependencies.
13
+
14
+ - Shell history is treated as sensitive data at all times.
15
+ - No command, pattern, or session data ever leaves the machine.
16
+ - mem does not import or depend on any networking library.
17
+ - Any feature that would require network access is rejected
18
+ at the design stage.
19
+
20
+ **Why**: The shell history is one of the most sensitive artifacts
21
+ a developer has. Privacy is non-negotiable.
22
+
23
+ ## II. Simple Where Simple Works
24
+
25
+ Deterministic algorithms are used wherever they produce
26
+ acceptable results. AI inference (Apple Foundation Models) is
27
+ reserved exclusively for tasks where no deterministic approach
28
+ can match the quality.
29
+
30
+ - Frequency ranking and recency scoring are deterministic
31
+ (exponential decay, weighted sums).
32
+ - Pattern extraction is the sole AI-powered operation — justified
33
+ because no regex or heuristic can infer abstract command structures
34
+ from arbitrary history.
35
+ - Session summaries use AI for semantic matching — justified
36
+ because keyword search alone misses intent.
37
+
38
+ **Why**: AI adds latency, complexity, and opacity. Use it only
39
+ where it earns its keep.
40
+
41
+ ## III. Unix Citizen
42
+
43
+ mem is composable, pipeable, and respectful of existing
44
+ shell conventions.
45
+
46
+ - All storage files are human-readable plain text (JSONL or JSON).
47
+ - `cat`, `grep`, and `tail -f` work on every file in `~/.mem/`.
48
+ - CLI output supports both human-readable (default) and JSON
49
+ (`--json`) formats.
50
+ - mem reads from stdin and writes results to stdout; errors
51
+ and diagnostics go to stderr.
52
+ - mem does not replace, wrap, or intercept the user's shell.
53
+ It hooks into existing precmd/preexec mechanisms only.
54
+
55
+ **Why**: A tool that fights the Unix philosophy will be
56
+ abandoned. mem extends the shell; it does not compete with it.
57
+
58
+ ## IV. Context Is Everything
59
+
60
+ The same command means different things in different repositories.
61
+ mem is context-aware by default.
62
+
63
+ - Commands are stored per git repository in separate JSONL files.
64
+ - Search results are ranked with a context multiplier:
65
+ higher for the current repo, lower for unrelated repos.
66
+ - Session boundaries are defined by idle time or switching
67
+ to a different repository.
68
+
69
+ **Why**: Context transforms a flat list of 10,000 commands into
70
+ a focused, relevant recall system.
71
+
72
+ ## V. Learn Silently, Surface Explicitly
73
+
74
+ mem does not interrupt the user's workflow. It captures data
75
+ passively and speaks only when asked.
76
+
77
+ - Shell hooks append one line per command execution — no
78
+ prompts, no confirmations, no output.
79
+ - Pattern extraction and session grouping run only when the user
80
+ explicitly invokes `mem sync`.
81
+ - No background daemons, no cron jobs, no scheduled processes.
82
+ - The user is always in control of when analysis happens.
83
+
84
+ **Why**: A tool that nags or slows the shell will be uninstalled
85
+ within a day. Silence is a feature.
86
+
87
+ ## VI. Open Source
88
+
89
+ mem is built to be open source from day one. The codebase is
90
+ inspectable, forkable, and contributable.
91
+
92
+ - Every design decision is documented and defensible.
93
+ - No magic, no black boxes. If a contributor cannot understand
94
+ why a piece of code exists by reading it and its context,
95
+ documentation is missing.
96
+ - Internal APIs and data formats are stable enough that
97
+ forks and community tools can rely on them.
98
+
99
+ **Why**: Open source is not a distribution model — it is
100
+ a quality bar. Code that must survive public scrutiny is better code.
101
+
102
+ ---
103
+
104
+ ## What mem is NOT
105
+
106
+ - Not a shell replacement (no Warp, no Fig)
107
+ - Not a semantic search engine (no embeddings, no vector DB)
108
+ - Not a cloud product
109
+ - Not an AI assistant that chats back
110
+ - Not a replacement for `man` pages or docs
111
+ - Not a database-backed tool of any kind