memstrata 0.6.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.
- memstrata-0.6.0/LICENSE +21 -0
- memstrata-0.6.0/PKG-INFO +182 -0
- memstrata-0.6.0/README.md +119 -0
- memstrata-0.6.0/memstrata/__init__.py +2 -0
- memstrata-0.6.0/memstrata/cli/__init__.py +0 -0
- memstrata-0.6.0/memstrata/cli/cd_hook.py +148 -0
- memstrata-0.6.0/memstrata/cli/ingest.py +432 -0
- memstrata-0.6.0/memstrata/cli/main.py +340 -0
- memstrata-0.6.0/memstrata/config/__init__.py +0 -0
- memstrata-0.6.0/memstrata/config/keychain.py +47 -0
- memstrata-0.6.0/memstrata/layer3/__init__.py +0 -0
- memstrata-0.6.0/memstrata/layer3/_db.py +638 -0
- memstrata-0.6.0/memstrata/layer3/api_server.py +2298 -0
- memstrata-0.6.0/memstrata/layer3/ingestion/__init__.py +115 -0
- memstrata-0.6.0/memstrata/layer3/ingestion/branch_switch.py +230 -0
- memstrata-0.6.0/memstrata/layer3/ingestion/chunker.py +351 -0
- memstrata-0.6.0/memstrata/layer3/ingestion/denylist.py +307 -0
- memstrata-0.6.0/memstrata/layer3/ingestion/lifecycle.py +312 -0
- memstrata-0.6.0/memstrata/layer3/ingestion/orchestrator.py +664 -0
- memstrata-0.6.0/memstrata/layer3/ingestion/progress.py +209 -0
- memstrata-0.6.0/memstrata/layer3/ingestion/resource_policy.py +297 -0
- memstrata-0.6.0/memstrata/layer3/ingestion/watcher.py +523 -0
- memstrata-0.6.0/memstrata/layer3/mcp_app.py +361 -0
- memstrata-0.6.0/memstrata/layer3/mcp_server.py +196 -0
- memstrata-0.6.0/memstrata/layer3/ollama_health.py +181 -0
- memstrata-0.6.0/memstrata/layer3/pricing/__init__.py +0 -0
- memstrata-0.6.0/memstrata/layer3/pricing/fx.py +147 -0
- memstrata-0.6.0/memstrata/layer3/pricing/lookup.py +166 -0
- memstrata-0.6.0/memstrata/layer3/pricing/openrouter_sync.py +174 -0
- memstrata-0.6.0/memstrata/layer3/pricing/pricing_matrix.json +78 -0
- memstrata-0.6.0/memstrata/layer3/retrieval.py +132 -0
- memstrata-0.6.0/memstrata/workers/__init__.py +0 -0
- memstrata-0.6.0/memstrata/workers/embedding_worker.py +301 -0
- memstrata-0.6.0/memstrata.egg-info/PKG-INFO +182 -0
- memstrata-0.6.0/memstrata.egg-info/SOURCES.txt +51 -0
- memstrata-0.6.0/memstrata.egg-info/dependency_links.txt +1 -0
- memstrata-0.6.0/memstrata.egg-info/entry_points.txt +2 -0
- memstrata-0.6.0/memstrata.egg-info/requires.txt +21 -0
- memstrata-0.6.0/memstrata.egg-info/top_level.txt +1 -0
- memstrata-0.6.0/pyproject.toml +108 -0
- memstrata-0.6.0/setup.cfg +4 -0
- memstrata-0.6.0/tests/test_api_server.py +1482 -0
- memstrata-0.6.0/tests/test_cd_hook_generation.py +92 -0
- memstrata-0.6.0/tests/test_cd_hook_idempotent.py +155 -0
- memstrata-0.6.0/tests/test_codebase_ingest.py +196 -0
- memstrata-0.6.0/tests/test_e2e_multi_session.py +625 -0
- memstrata-0.6.0/tests/test_keychain.py +142 -0
- memstrata-0.6.0/tests/test_mcp_server.py +317 -0
- memstrata-0.6.0/tests/test_ollama_health.py +268 -0
- memstrata-0.6.0/tests/test_phase_34_embedding.py +401 -0
- memstrata-0.6.0/tests/test_phase_34_retrieval.py +854 -0
- memstrata-0.6.0/tests/test_shutdown_endpoint.py +109 -0
- memstrata-0.6.0/tests/test_telemetry_edge_cases.py +499 -0
memstrata-0.6.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 MemStrata Contributors
|
|
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.
|
memstrata-0.6.0/PKG-INFO
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: memstrata
|
|
3
|
+
Version: 0.6.0
|
|
4
|
+
Summary: A local-first, verification-first context engine for AI workflows
|
|
5
|
+
License: MIT License
|
|
6
|
+
|
|
7
|
+
Copyright (c) 2026 MemStrata Contributors
|
|
8
|
+
|
|
9
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
10
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
11
|
+
in the Software without restriction, including without limitation the rights
|
|
12
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
13
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
14
|
+
furnished to do so, subject to the following conditions:
|
|
15
|
+
|
|
16
|
+
The above copyright notice and this permission notice shall be included in all
|
|
17
|
+
copies or substantial portions of the Software.
|
|
18
|
+
|
|
19
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
20
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
21
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
22
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
23
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
24
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
25
|
+
SOFTWARE.
|
|
26
|
+
|
|
27
|
+
Project-URL: Homepage, https://memstrata.dev
|
|
28
|
+
Project-URL: Repository, https://github.com/yadu9989/MemStrata
|
|
29
|
+
Project-URL: Issues, https://github.com/yadu9989/MemStrata/issues
|
|
30
|
+
Keywords: llm,context,memory,mcp,local-first,ai-tools
|
|
31
|
+
Classifier: Development Status :: 4 - Beta
|
|
32
|
+
Classifier: Intended Audience :: Developers
|
|
33
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
34
|
+
Classifier: Operating System :: OS Independent
|
|
35
|
+
Classifier: Programming Language :: Python :: 3
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
39
|
+
Classifier: Topic :: Software Development
|
|
40
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
41
|
+
Requires-Python: >=3.10
|
|
42
|
+
Description-Content-Type: text/markdown
|
|
43
|
+
License-File: LICENSE
|
|
44
|
+
Requires-Dist: fastapi>=0.115
|
|
45
|
+
Requires-Dist: uvicorn[standard]>=0.32
|
|
46
|
+
Requires-Dist: httpx>=0.27
|
|
47
|
+
Requires-Dist: pydantic>=2.10
|
|
48
|
+
Requires-Dist: sqlite-vec>=0.1.6
|
|
49
|
+
Requires-Dist: tree-sitter>=0.22
|
|
50
|
+
Requires-Dist: tree-sitter-python>=0.21
|
|
51
|
+
Requires-Dist: tree-sitter-javascript>=0.21
|
|
52
|
+
Requires-Dist: tree-sitter-typescript>=0.21
|
|
53
|
+
Requires-Dist: mcp>=1.0
|
|
54
|
+
Requires-Dist: truststore>=0.9; python_version >= "3.10"
|
|
55
|
+
Requires-Dist: python-dotenv>=1.0
|
|
56
|
+
Provides-Extra: dev
|
|
57
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
58
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
59
|
+
Requires-Dist: ruff>=0.6; extra == "dev"
|
|
60
|
+
Requires-Dist: mypy>=1.11; extra == "dev"
|
|
61
|
+
Requires-Dist: respx>=0.21; extra == "dev"
|
|
62
|
+
Dynamic: license-file
|
|
63
|
+
|
|
64
|
+
# MemStrata
|
|
65
|
+
|
|
66
|
+
**A local-first, verification-first context engine for AI workflows.**
|
|
67
|
+
|
|
68
|
+
MemStrata sits between you and every AI tool you use. It captures conversations
|
|
69
|
+
from the browser, indexes your codebase locally, and serves a context block to
|
|
70
|
+
whichever LLM you're talking to next. Your code stays on your machine. Your
|
|
71
|
+
conversation history stays on your machine. No telemetry leaves the box.
|
|
72
|
+
|
|
73
|
+
This repository is the **open-source core**: the local daemon, the chat-capture
|
|
74
|
+
browser extension, the MCP server, and the dashboard. It is MIT-licensed and
|
|
75
|
+
fully usable on its own.
|
|
76
|
+
|
|
77
|
+
The commercial Pro tier (token-budgeted context injection through a proxy
|
|
78
|
+
harness, money-back-guaranteed savings, IDE integration) lives in a separate,
|
|
79
|
+
private repository and consumes this package as a PyPI dependency. See
|
|
80
|
+
[memstrata.dev](https://memstrata.dev) if you want the paid product.
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## What's in this repo
|
|
85
|
+
|
|
86
|
+
| Path | What it does |
|
|
87
|
+
|---|---|
|
|
88
|
+
| `memstrata/layer3/api_server.py` | The local daemon's FastAPI app — telemetry, dashboard, MCP routing |
|
|
89
|
+
| `memstrata/layer3/ingestion/` | File watcher, tree-sitter chunker, opt-in lifecycle, denylists |
|
|
90
|
+
| `memstrata/layer3/mcp_server.py`, `mcp_app.py` | MCP server (Anthropic-spec) for Claude Desktop / Cursor / etc. |
|
|
91
|
+
| `memstrata/layer3/_db.py` | SQLite schema with `sqlite-vec` for local vector search |
|
|
92
|
+
| `memstrata/layer3/retrieval.py` | Token-budgeted context retrieval against the local store |
|
|
93
|
+
| `memstrata/layer3/pricing/` | Live OpenRouter price sync + bundled static fallback for offline use |
|
|
94
|
+
| `memstrata/layer3/ollama_health.py` | Shared Ollama reachability probe (used by the dashboard) |
|
|
95
|
+
| `memstrata/workers/embedding_worker.py` | Background worker that embeds new turns into the vector store |
|
|
96
|
+
| `memstrata/cli/` | The `memstrata` CLI: `register`, `ingest`, the cd-hook generator |
|
|
97
|
+
| `memstrata/config/keychain.py` | OS keyring wrapper for storing per-provider API keys |
|
|
98
|
+
| `browser-extension/` | Chrome / Edge / Firefox extension that captures chat turns from every major LLM front-end |
|
|
99
|
+
| `migrations/` | SQL migrations |
|
|
100
|
+
| `shared/telemetry_schema.json` | The JSON schema for telemetry events (public contract) |
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Quickstart
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
pip install memstrata
|
|
108
|
+
memstrata init # one-time interactive setup
|
|
109
|
+
memstrata api # start the daemon on 127.0.0.1:8000
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
The daemon binds to `127.0.0.1:8000`. Open `http://127.0.0.1:8000/dashboard`
|
|
113
|
+
to see what's captured.
|
|
114
|
+
|
|
115
|
+
Install the browser extension from your browser's add-on store (see the
|
|
116
|
+
[browser-extension/](browser-extension/) directory for build instructions
|
|
117
|
+
if you want to load it unpacked).
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Architecture commitments
|
|
122
|
+
|
|
123
|
+
These aren't aspirational — they're enforced in the code and tested for in CI:
|
|
124
|
+
|
|
125
|
+
1. **Localhost-only binding.** Every HTTP server in this repo hard-codes
|
|
126
|
+
`host="127.0.0.1"`. No `0.0.0.0`, no LAN exposure, no remote access.
|
|
127
|
+
|
|
128
|
+
2. **No TLS interception.** The MCP server and the dashboard speak plain
|
|
129
|
+
HTTP on loopback. The browser extension talks directly to provider
|
|
130
|
+
APIs and to this daemon's loopback endpoint. There is no MITM proxy
|
|
131
|
+
in the open-source stack.
|
|
132
|
+
|
|
133
|
+
3. **Local storage only.** All telemetry, all chat history, all vectors,
|
|
134
|
+
all API keys live in `~/.memstrata/` (or `$ML_DATA_DIR` if set).
|
|
135
|
+
Nothing is uploaded to a MemStrata-owned cloud service. There is no
|
|
136
|
+
such service.
|
|
137
|
+
|
|
138
|
+
4. **Telemetry never includes user content.** The dashboard and the
|
|
139
|
+
MCP server expose your data back to you. Nothing is sent off-machine.
|
|
140
|
+
|
|
141
|
+
5. **The Pro tier is structurally separate.** Pro code lives in a
|
|
142
|
+
different repository under a different license. This repo has
|
|
143
|
+
zero `import` statements that touch Pro code.
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## Provider pricing (for the dashboard's savings calculator)
|
|
148
|
+
|
|
149
|
+
The dashboard's session-level savings columns compute against a price
|
|
150
|
+
table. By default the daemon syncs the table once per day from
|
|
151
|
+
[OpenRouter](https://openrouter.ai/api/v1/models). When the network is
|
|
152
|
+
down or OpenRouter is unreachable, the bundled
|
|
153
|
+
`memstrata/layer3/pricing/pricing_matrix.json` provides a static
|
|
154
|
+
fallback. The fallback covers the most common Claude, OpenAI, Gemini,
|
|
155
|
+
DeepSeek, xAI, and Mistral models. Prices in the fallback are
|
|
156
|
+
USD-per-million-tokens, last verified mid-2026.
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Contributing
|
|
161
|
+
|
|
162
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md). The short version: open issues
|
|
163
|
+
first for non-trivial changes, run `pytest` before submitting, and keep
|
|
164
|
+
the architectural commitments above intact.
|
|
165
|
+
|
|
166
|
+
## Security
|
|
167
|
+
|
|
168
|
+
See [SECURITY.md](SECURITY.md). Vulnerability reports go to
|
|
169
|
+
`security@memstrata.dev`, **not** GitHub issues.
|
|
170
|
+
|
|
171
|
+
## License
|
|
172
|
+
|
|
173
|
+
[MIT](LICENSE). See `LICENSE` for the full text.
|
|
174
|
+
|
|
175
|
+
## Related
|
|
176
|
+
|
|
177
|
+
- **Commercial Pro tier**: [memstrata.dev](https://memstrata.dev) — the
|
|
178
|
+
token-budgeting interception harness, the money-back guarantee, and
|
|
179
|
+
the IDE extension. Proprietary, paid, separate codebase.
|
|
180
|
+
- **Browser extension store listings**: shipped from the same source
|
|
181
|
+
tree in this repo; see [browser-extension/README.md](browser-extension/)
|
|
182
|
+
for build + sideload instructions.
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# MemStrata
|
|
2
|
+
|
|
3
|
+
**A local-first, verification-first context engine for AI workflows.**
|
|
4
|
+
|
|
5
|
+
MemStrata sits between you and every AI tool you use. It captures conversations
|
|
6
|
+
from the browser, indexes your codebase locally, and serves a context block to
|
|
7
|
+
whichever LLM you're talking to next. Your code stays on your machine. Your
|
|
8
|
+
conversation history stays on your machine. No telemetry leaves the box.
|
|
9
|
+
|
|
10
|
+
This repository is the **open-source core**: the local daemon, the chat-capture
|
|
11
|
+
browser extension, the MCP server, and the dashboard. It is MIT-licensed and
|
|
12
|
+
fully usable on its own.
|
|
13
|
+
|
|
14
|
+
The commercial Pro tier (token-budgeted context injection through a proxy
|
|
15
|
+
harness, money-back-guaranteed savings, IDE integration) lives in a separate,
|
|
16
|
+
private repository and consumes this package as a PyPI dependency. See
|
|
17
|
+
[memstrata.dev](https://memstrata.dev) if you want the paid product.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## What's in this repo
|
|
22
|
+
|
|
23
|
+
| Path | What it does |
|
|
24
|
+
|---|---|
|
|
25
|
+
| `memstrata/layer3/api_server.py` | The local daemon's FastAPI app — telemetry, dashboard, MCP routing |
|
|
26
|
+
| `memstrata/layer3/ingestion/` | File watcher, tree-sitter chunker, opt-in lifecycle, denylists |
|
|
27
|
+
| `memstrata/layer3/mcp_server.py`, `mcp_app.py` | MCP server (Anthropic-spec) for Claude Desktop / Cursor / etc. |
|
|
28
|
+
| `memstrata/layer3/_db.py` | SQLite schema with `sqlite-vec` for local vector search |
|
|
29
|
+
| `memstrata/layer3/retrieval.py` | Token-budgeted context retrieval against the local store |
|
|
30
|
+
| `memstrata/layer3/pricing/` | Live OpenRouter price sync + bundled static fallback for offline use |
|
|
31
|
+
| `memstrata/layer3/ollama_health.py` | Shared Ollama reachability probe (used by the dashboard) |
|
|
32
|
+
| `memstrata/workers/embedding_worker.py` | Background worker that embeds new turns into the vector store |
|
|
33
|
+
| `memstrata/cli/` | The `memstrata` CLI: `register`, `ingest`, the cd-hook generator |
|
|
34
|
+
| `memstrata/config/keychain.py` | OS keyring wrapper for storing per-provider API keys |
|
|
35
|
+
| `browser-extension/` | Chrome / Edge / Firefox extension that captures chat turns from every major LLM front-end |
|
|
36
|
+
| `migrations/` | SQL migrations |
|
|
37
|
+
| `shared/telemetry_schema.json` | The JSON schema for telemetry events (public contract) |
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Quickstart
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install memstrata
|
|
45
|
+
memstrata init # one-time interactive setup
|
|
46
|
+
memstrata api # start the daemon on 127.0.0.1:8000
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The daemon binds to `127.0.0.1:8000`. Open `http://127.0.0.1:8000/dashboard`
|
|
50
|
+
to see what's captured.
|
|
51
|
+
|
|
52
|
+
Install the browser extension from your browser's add-on store (see the
|
|
53
|
+
[browser-extension/](browser-extension/) directory for build instructions
|
|
54
|
+
if you want to load it unpacked).
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Architecture commitments
|
|
59
|
+
|
|
60
|
+
These aren't aspirational — they're enforced in the code and tested for in CI:
|
|
61
|
+
|
|
62
|
+
1. **Localhost-only binding.** Every HTTP server in this repo hard-codes
|
|
63
|
+
`host="127.0.0.1"`. No `0.0.0.0`, no LAN exposure, no remote access.
|
|
64
|
+
|
|
65
|
+
2. **No TLS interception.** The MCP server and the dashboard speak plain
|
|
66
|
+
HTTP on loopback. The browser extension talks directly to provider
|
|
67
|
+
APIs and to this daemon's loopback endpoint. There is no MITM proxy
|
|
68
|
+
in the open-source stack.
|
|
69
|
+
|
|
70
|
+
3. **Local storage only.** All telemetry, all chat history, all vectors,
|
|
71
|
+
all API keys live in `~/.memstrata/` (or `$ML_DATA_DIR` if set).
|
|
72
|
+
Nothing is uploaded to a MemStrata-owned cloud service. There is no
|
|
73
|
+
such service.
|
|
74
|
+
|
|
75
|
+
4. **Telemetry never includes user content.** The dashboard and the
|
|
76
|
+
MCP server expose your data back to you. Nothing is sent off-machine.
|
|
77
|
+
|
|
78
|
+
5. **The Pro tier is structurally separate.** Pro code lives in a
|
|
79
|
+
different repository under a different license. This repo has
|
|
80
|
+
zero `import` statements that touch Pro code.
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Provider pricing (for the dashboard's savings calculator)
|
|
85
|
+
|
|
86
|
+
The dashboard's session-level savings columns compute against a price
|
|
87
|
+
table. By default the daemon syncs the table once per day from
|
|
88
|
+
[OpenRouter](https://openrouter.ai/api/v1/models). When the network is
|
|
89
|
+
down or OpenRouter is unreachable, the bundled
|
|
90
|
+
`memstrata/layer3/pricing/pricing_matrix.json` provides a static
|
|
91
|
+
fallback. The fallback covers the most common Claude, OpenAI, Gemini,
|
|
92
|
+
DeepSeek, xAI, and Mistral models. Prices in the fallback are
|
|
93
|
+
USD-per-million-tokens, last verified mid-2026.
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Contributing
|
|
98
|
+
|
|
99
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md). The short version: open issues
|
|
100
|
+
first for non-trivial changes, run `pytest` before submitting, and keep
|
|
101
|
+
the architectural commitments above intact.
|
|
102
|
+
|
|
103
|
+
## Security
|
|
104
|
+
|
|
105
|
+
See [SECURITY.md](SECURITY.md). Vulnerability reports go to
|
|
106
|
+
`security@memstrata.dev`, **not** GitHub issues.
|
|
107
|
+
|
|
108
|
+
## License
|
|
109
|
+
|
|
110
|
+
[MIT](LICENSE). See `LICENSE` for the full text.
|
|
111
|
+
|
|
112
|
+
## Related
|
|
113
|
+
|
|
114
|
+
- **Commercial Pro tier**: [memstrata.dev](https://memstrata.dev) — the
|
|
115
|
+
token-budgeting interception harness, the money-back guarantee, and
|
|
116
|
+
the IDE extension. Proprietary, paid, separate codebase.
|
|
117
|
+
- **Browser extension store listings**: shipped from the same source
|
|
118
|
+
tree in this repo; see [browser-extension/README.md](browser-extension/)
|
|
119
|
+
for build + sideload instructions.
|
|
File without changes
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Shell cd-hook generation and idempotent installation.
|
|
3
|
+
|
|
4
|
+
Hook text and write/remove patterns taken verbatim from
|
|
5
|
+
v5_1_reference/critical_snippets.py §2. The idempotent marker pair
|
|
6
|
+
ensures repeated writes replace rather than duplicate the block.
|
|
7
|
+
|
|
8
|
+
Hard Rule 54: hooks only check for .git/ — no process scanning.
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
import sys
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Optional
|
|
16
|
+
|
|
17
|
+
_HOOK_MARKER_BEGIN = "# >>> memstrata cd-hook >>>"
|
|
18
|
+
_HOOK_MARKER_END = "# <<< memstrata cd-hook <<<"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def hook_for_shell(shell: str) -> str:
|
|
22
|
+
"""
|
|
23
|
+
Generate the hook block for the given shell.
|
|
24
|
+
|
|
25
|
+
The returned string is delimited by _HOOK_MARKER_BEGIN / _HOOK_MARKER_END
|
|
26
|
+
so write_hook can replace it idempotently.
|
|
27
|
+
"""
|
|
28
|
+
if shell == "zsh":
|
|
29
|
+
body = """
|
|
30
|
+
ml_cd_hook() {
|
|
31
|
+
if [ -d ".git" ] && command -v memstrata >/dev/null 2>&1; then
|
|
32
|
+
(memstrata register "$PWD" --quiet >/dev/null 2>&1 &)
|
|
33
|
+
fi
|
|
34
|
+
}
|
|
35
|
+
typeset -gaU chpwd_functions
|
|
36
|
+
chpwd_functions+=(ml_cd_hook)
|
|
37
|
+
"""
|
|
38
|
+
elif shell == "bash":
|
|
39
|
+
body = """
|
|
40
|
+
ml_cd_hook() {
|
|
41
|
+
if [ -d ".git" ] && command -v memstrata >/dev/null 2>&1; then
|
|
42
|
+
(memstrata register "$PWD" --quiet >/dev/null 2>&1 &)
|
|
43
|
+
fi
|
|
44
|
+
}
|
|
45
|
+
PROMPT_COMMAND="ml_cd_hook;${PROMPT_COMMAND:-:}"
|
|
46
|
+
"""
|
|
47
|
+
elif shell == "fish":
|
|
48
|
+
body = """
|
|
49
|
+
function ml_cd_hook --on-variable PWD
|
|
50
|
+
if test -d .git
|
|
51
|
+
if command -v memstrata >/dev/null 2>&1
|
|
52
|
+
memstrata register "$PWD" --quiet >/dev/null 2>&1 &
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
"""
|
|
57
|
+
elif shell == "powershell":
|
|
58
|
+
body = """
|
|
59
|
+
$global:__MlOriginalPrompt = if (Test-Path Function:prompt) { Get-Item Function:prompt } else { $null }
|
|
60
|
+
function global:prompt {
|
|
61
|
+
if (Test-Path -PathType Container ".git") {
|
|
62
|
+
if (Get-Command memstrata -ErrorAction SilentlyContinue) {
|
|
63
|
+
Start-Job -ScriptBlock {
|
|
64
|
+
param($p) memstrata register $p --quiet
|
|
65
|
+
} -ArgumentList $PWD.Path | Out-Null
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if ($global:__MlOriginalPrompt) { & $global:__MlOriginalPrompt }
|
|
69
|
+
else { "PS $($executionContext.SessionState.Path.CurrentLocation)$('>' * ($nestedPromptLevel + 1)) " }
|
|
70
|
+
}
|
|
71
|
+
"""
|
|
72
|
+
else:
|
|
73
|
+
raise ValueError(f"unsupported shell: {shell!r}")
|
|
74
|
+
|
|
75
|
+
return f"\n{_HOOK_MARKER_BEGIN}\n{body.strip()}\n{_HOOK_MARKER_END}\n"
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def write_hook(shell: str, config_path: Path) -> None:
|
|
79
|
+
"""
|
|
80
|
+
Idempotently install the hook into config_path.
|
|
81
|
+
|
|
82
|
+
If the marker block is already present it is replaced in-place.
|
|
83
|
+
Otherwise the block is appended. A .ml-backup is created once on
|
|
84
|
+
the first write (never overwritten on subsequent writes).
|
|
85
|
+
"""
|
|
86
|
+
backup = config_path.with_suffix(config_path.suffix + ".ml-backup")
|
|
87
|
+
if config_path.exists() and not backup.exists():
|
|
88
|
+
backup.write_text(config_path.read_text(encoding="utf-8"), encoding="utf-8")
|
|
89
|
+
|
|
90
|
+
existing = config_path.read_text(encoding="utf-8") if config_path.exists() else ""
|
|
91
|
+
new_block = hook_for_shell(shell)
|
|
92
|
+
|
|
93
|
+
if _HOOK_MARKER_BEGIN in existing:
|
|
94
|
+
before, _, rest = existing.partition(_HOOK_MARKER_BEGIN)
|
|
95
|
+
_, _, after = rest.partition(_HOOK_MARKER_END)
|
|
96
|
+
after = after.lstrip("\n")
|
|
97
|
+
result = before.rstrip() + new_block + ("\n" + after if after else "")
|
|
98
|
+
else:
|
|
99
|
+
# new_block already starts with "\n", so rstrip() + new_block gives one separator.
|
|
100
|
+
result = existing.rstrip() + new_block
|
|
101
|
+
|
|
102
|
+
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
103
|
+
config_path.write_text(result, encoding="utf-8")
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def remove_hook(config_path: Path) -> None:
|
|
107
|
+
"""
|
|
108
|
+
Reverse write_hook. Strips the marker block from config_path in-place.
|
|
109
|
+
No-op if the file is missing or the block was never written.
|
|
110
|
+
"""
|
|
111
|
+
if not config_path.exists():
|
|
112
|
+
return
|
|
113
|
+
text = config_path.read_text(encoding="utf-8")
|
|
114
|
+
if _HOOK_MARKER_BEGIN not in text:
|
|
115
|
+
return
|
|
116
|
+
before, _, rest = text.partition(_HOOK_MARKER_BEGIN)
|
|
117
|
+
_, _, after = rest.partition(_HOOK_MARKER_END)
|
|
118
|
+
config_path.write_text(before.rstrip() + "\n" + after.lstrip("\n"), encoding="utf-8")
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def detect_shell() -> str | None:
|
|
122
|
+
"""Best-effort shell detection from the environment."""
|
|
123
|
+
shell_env = os.environ.get("SHELL", "")
|
|
124
|
+
if "zsh" in shell_env:
|
|
125
|
+
return "zsh"
|
|
126
|
+
if "bash" in shell_env:
|
|
127
|
+
return "bash"
|
|
128
|
+
if "fish" in shell_env:
|
|
129
|
+
return "fish"
|
|
130
|
+
if os.environ.get("PSModulePath") and not shell_env:
|
|
131
|
+
return "powershell"
|
|
132
|
+
return None
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def config_path_for_shell(shell: str) -> Path:
|
|
136
|
+
"""Return the canonical config file path for the given shell."""
|
|
137
|
+
home = Path.home()
|
|
138
|
+
if shell == "zsh":
|
|
139
|
+
return home / ".zshrc"
|
|
140
|
+
if shell == "bash":
|
|
141
|
+
return home / ".bashrc"
|
|
142
|
+
if shell == "fish":
|
|
143
|
+
return home / ".config" / "fish" / "config.fish"
|
|
144
|
+
if shell == "powershell":
|
|
145
|
+
if sys.platform == "win32":
|
|
146
|
+
return home / "Documents" / "PowerShell" / "Microsoft.PowerShell_profile.ps1"
|
|
147
|
+
return home / ".config" / "powershell" / "Microsoft.PowerShell_profile.ps1"
|
|
148
|
+
raise ValueError(f"unsupported shell: {shell!r}")
|