garage-rag 1.5.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.
Files changed (119) hide show
  1. garage_rag-1.5.0/.gitignore +36 -0
  2. garage_rag-1.5.0/LICENSE +21 -0
  3. garage_rag-1.5.0/PKG-INFO +158 -0
  4. garage_rag-1.5.0/README.md +82 -0
  5. garage_rag-1.5.0/hatch_build.py +36 -0
  6. garage_rag-1.5.0/pyproject.toml +145 -0
  7. garage_rag-1.5.0/src/garage_rag/__init__.py +15 -0
  8. garage_rag-1.5.0/src/garage_rag/_data/models/models.json +681 -0
  9. garage_rag-1.5.0/src/garage_rag/_data/sql/001_extensions.sql +18 -0
  10. garage_rag-1.5.0/src/garage_rag/_data/sql/002_types.sql +43 -0
  11. garage_rag-1.5.0/src/garage_rag/_data/sql/003_core.sql +163 -0
  12. garage_rag-1.5.0/src/garage_rag/_data/sql/004_registry.sql +57 -0
  13. garage_rag-1.5.0/src/garage_rag/_data/sql/005_conversations.sql +61 -0
  14. garage_rag-1.5.0/src/garage_rag/_data/sql/006_facts.sql +33 -0
  15. garage_rag-1.5.0/src/garage_rag/_data/sql/007_chunk_fact_link.sql +17 -0
  16. garage_rag-1.5.0/src/garage_rag/_data/sql/008_source_scan.sql +29 -0
  17. garage_rag-1.5.0/src/garage_rag/_data/sql/009_model_distance.sql +24 -0
  18. garage_rag-1.5.0/src/garage_rag/_data/sql/010_drop_cloud_enrichment.sql +10 -0
  19. garage_rag-1.5.0/src/garage_rag/_data/sql/011_drop_placeholder_documents.sql +16 -0
  20. garage_rag-1.5.0/src/garage_rag/_data/sql/012_ingest_outcomes.sql +23 -0
  21. garage_rag-1.5.0/src/garage_rag/_data/sql/013_fact_prompts.sql +36 -0
  22. garage_rag-1.5.0/src/garage_rag/_data/sql/014_chunk_direction.sql +30 -0
  23. garage_rag-1.5.0/src/garage_rag/attribute/__init__.py +1 -0
  24. garage_rag-1.5.0/src/garage_rag/attribute/git.py +232 -0
  25. garage_rag-1.5.0/src/garage_rag/attribute/pathrules.py +122 -0
  26. garage_rag-1.5.0/src/garage_rag/attribute/resolver.py +403 -0
  27. garage_rag-1.5.0/src/garage_rag/cli.py +1838 -0
  28. garage_rag-1.5.0/src/garage_rag/config/__init__.py +1012 -0
  29. garage_rag-1.5.0/src/garage_rag/config/fact_prompts.py +252 -0
  30. garage_rag-1.5.0/src/garage_rag/db/__init__.py +3 -0
  31. garage_rag-1.5.0/src/garage_rag/db/catalog.py +81 -0
  32. garage_rag-1.5.0/src/garage_rag/db/emb_tables.py +223 -0
  33. garage_rag-1.5.0/src/garage_rag/db/engine.py +115 -0
  34. garage_rag-1.5.0/src/garage_rag/db/migrate.py +258 -0
  35. garage_rag-1.5.0/src/garage_rag/db/models.py +386 -0
  36. garage_rag-1.5.0/src/garage_rag/db/registry.py +188 -0
  37. garage_rag-1.5.0/src/garage_rag/embed/__init__.py +19 -0
  38. garage_rag-1.5.0/src/garage_rag/embed/base.py +62 -0
  39. garage_rag-1.5.0/src/garage_rag/embed/factory.py +56 -0
  40. garage_rag-1.5.0/src/garage_rag/embed/llama_xpc.py +34 -0
  41. garage_rag-1.5.0/src/garage_rag/embed/lmstudio.py +49 -0
  42. garage_rag-1.5.0/src/garage_rag/embed/ollama.py +224 -0
  43. garage_rag-1.5.0/src/garage_rag/embed/xpc.py +111 -0
  44. garage_rag-1.5.0/src/garage_rag/enrich/__init__.py +1 -0
  45. garage_rag-1.5.0/src/garage_rag/enrich/facts.py +392 -0
  46. garage_rag-1.5.0/src/garage_rag/enrich/generation.py +192 -0
  47. garage_rag-1.5.0/src/garage_rag/enrich/langextract/LICENSE +202 -0
  48. garage_rag-1.5.0/src/garage_rag/enrich/langextract/__init__.py +34 -0
  49. garage_rag-1.5.0/src/garage_rag/enrich/langextract/annotation.py +606 -0
  50. garage_rag-1.5.0/src/garage_rag/enrich/langextract/base_model.py +231 -0
  51. garage_rag-1.5.0/src/garage_rag/enrich/langextract/chunking.py +511 -0
  52. garage_rag-1.5.0/src/garage_rag/enrich/langextract/data.py +270 -0
  53. garage_rag-1.5.0/src/garage_rag/enrich/langextract/exceptions.py +168 -0
  54. garage_rag-1.5.0/src/garage_rag/enrich/langextract/extraction.py +194 -0
  55. garage_rag-1.5.0/src/garage_rag/enrich/langextract/format_handler.py +477 -0
  56. garage_rag-1.5.0/src/garage_rag/enrich/langextract/prompting.py +280 -0
  57. garage_rag-1.5.0/src/garage_rag/enrich/langextract/resolver.py +1408 -0
  58. garage_rag-1.5.0/src/garage_rag/enrich/langextract/schema.py +164 -0
  59. garage_rag-1.5.0/src/garage_rag/enrich/langextract/tokenizer.py +646 -0
  60. garage_rag-1.5.0/src/garage_rag/enrich/langextract/types.py +81 -0
  61. garage_rag-1.5.0/src/garage_rag/enrich/local_provider.py +82 -0
  62. garage_rag-1.5.0/src/garage_rag/extract/__init__.py +1 -0
  63. garage_rag-1.5.0/src/garage_rag/extract/base.py +195 -0
  64. garage_rag-1.5.0/src/garage_rag/extract/contact_names.py +113 -0
  65. garage_rag-1.5.0/src/garage_rag/extract/dispatch.py +340 -0
  66. garage_rag-1.5.0/src/garage_rag/extract/image.py +114 -0
  67. garage_rag-1.5.0/src/garage_rag/extract/imageio.py +207 -0
  68. garage_rag-1.5.0/src/garage_rag/extract/mail.py +187 -0
  69. garage_rag-1.5.0/src/garage_rag/extract/messages.py +436 -0
  70. garage_rag-1.5.0/src/garage_rag/extract/nicknames.py +162 -0
  71. garage_rag-1.5.0/src/garage_rag/extract/office.py +212 -0
  72. garage_rag-1.5.0/src/garage_rag/extract/pdf.py +195 -0
  73. garage_rag-1.5.0/src/garage_rag/extract/placeholder.py +180 -0
  74. garage_rag-1.5.0/src/garage_rag/extract/quality.py +153 -0
  75. garage_rag-1.5.0/src/garage_rag/extract/tesseract.py +207 -0
  76. garage_rag-1.5.0/src/garage_rag/extract/text.py +162 -0
  77. garage_rag-1.5.0/src/garage_rag/inference/__init__.py +43 -0
  78. garage_rag-1.5.0/src/garage_rag/inference/bridge.py +91 -0
  79. garage_rag-1.5.0/src/garage_rag/inference/client.py +641 -0
  80. garage_rag-1.5.0/src/garage_rag/inference/transport.py +128 -0
  81. garage_rag-1.5.0/src/garage_rag/ingest/__init__.py +451 -0
  82. garage_rag-1.5.0/src/garage_rag/ingest/author_names.py +34 -0
  83. garage_rag-1.5.0/src/garage_rag/ingest/chunking.py +273 -0
  84. garage_rag-1.5.0/src/garage_rag/ingest/classify.py +109 -0
  85. garage_rag-1.5.0/src/garage_rag/ingest/conversations.py +388 -0
  86. garage_rag-1.5.0/src/garage_rag/ingest/gateway.py +1050 -0
  87. garage_rag-1.5.0/src/garage_rag/ingest/materialize.py +277 -0
  88. garage_rag-1.5.0/src/garage_rag/ingest/pipeline.py +651 -0
  89. garage_rag-1.5.0/src/garage_rag/ingest/reconcile.py +116 -0
  90. garage_rag-1.5.0/src/garage_rag/ingest/scanner.py +652 -0
  91. garage_rag-1.5.0/src/garage_rag/ingest/splitters.py +354 -0
  92. garage_rag-1.5.0/src/garage_rag/ingest/walker.py +292 -0
  93. garage_rag-1.5.0/src/garage_rag/libpq.py +50 -0
  94. garage_rag-1.5.0/src/garage_rag/mcp_server/__init__.py +1 -0
  95. garage_rag-1.5.0/src/garage_rag/mcp_server/agent.py +603 -0
  96. garage_rag-1.5.0/src/garage_rag/mcp_server/install.py +449 -0
  97. garage_rag-1.5.0/src/garage_rag/mcp_server/server.py +1060 -0
  98. garage_rag-1.5.0/src/garage_rag/native/__init__.py +38 -0
  99. garage_rag-1.5.0/src/garage_rag/net/__init__.py +1 -0
  100. garage_rag-1.5.0/src/garage_rag/net/egress.py +238 -0
  101. garage_rag-1.5.0/src/garage_rag/ops/__init__.py +8 -0
  102. garage_rag-1.5.0/src/garage_rag/ops/backfill.py +137 -0
  103. garage_rag-1.5.0/src/garage_rag/ops/facts.py +362 -0
  104. garage_rag-1.5.0/src/garage_rag/ops/mcp.py +210 -0
  105. garage_rag-1.5.0/src/garage_rag/ops/models.py +86 -0
  106. garage_rag-1.5.0/src/garage_rag/ops/settings.py +34 -0
  107. garage_rag-1.5.0/src/garage_rag/ops/sources.py +371 -0
  108. garage_rag-1.5.0/src/garage_rag/proto/__init__.py +9 -0
  109. garage_rag-1.5.0/src/garage_rag/proto/garage_pb2.py +244 -0
  110. garage_rag-1.5.0/src/garage_rag/proto/garage_pb2_grpc.py +1680 -0
  111. garage_rag-1.5.0/src/garage_rag/search/__init__.py +13 -0
  112. garage_rag-1.5.0/src/garage_rag/search/hybrid.py +397 -0
  113. garage_rag-1.5.0/src/garage_rag/service/__init__.py +15 -0
  114. garage_rag-1.5.0/src/garage_rag/service/auth.py +74 -0
  115. garage_rag-1.5.0/src/garage_rag/service/client.py +389 -0
  116. garage_rag-1.5.0/src/garage_rag/service/server.py +1666 -0
  117. garage_rag-1.5.0/src/garage_rag/xpc/__init__.py +15 -0
  118. garage_rag-1.5.0/src/garage_rag/xpc/host.py +167 -0
  119. garage_rag-1.5.0/src/garage_rag/xpc/llama_xpc.py +174 -0
@@ -0,0 +1,36 @@
1
+ *.pyc
2
+ .venv
3
+ .env
4
+ *.db
5
+ .build
6
+ macapp/build/
7
+ # Credential files (an LM Studio token, or a key left over from the retired cloud OCR)
8
+ *.key
9
+ anthropic.key
10
+
11
+ # Per-project config override (holds absolute paths and identity)
12
+ garage.json
13
+
14
+ *.bak-*
15
+
16
+ __pycache__
17
+
18
+ bazel-*
19
+
20
+ .bazelbsp/
21
+ *.xcodeproj
22
+ .output.txt
23
+ build/
24
+ # IDE data-source definitions embed the local database password
25
+ .idea/dataSources.xml
26
+ .idea/dataSources.local.xml
27
+
28
+ # Local Bazel overrides (see .bazelrc)
29
+ user.bazelrc
30
+
31
+ .ruff_cache/
32
+ .mypy_cache/
33
+ garage_python/dist/
34
+
35
+ # Release archives staged by //macapp/package:publish_appcast
36
+ /dist/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Rick Mark-Penwell
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,158 @@
1
+ Metadata-Version: 2.5
2
+ Name: garage_rag
3
+ Version: 1.5.0
4
+ Summary: Local-first personal RAG pipeline over Postgres + pgvector, with authorship attribution and an MCP server
5
+ Project-URL: Homepage, https://garagerag.app
6
+ Project-URL: Source, https://github.com/rickmark/garage-rag
7
+ Project-URL: Issues, https://github.com/rickmark/garage-rag/issues
8
+ Author: Rick Mark-Penwell
9
+ License: MIT License
10
+
11
+ Copyright (c) 2026 Rick Mark-Penwell
12
+
13
+ Permission is hereby granted, free of charge, to any person obtaining a copy
14
+ of this software and associated documentation files (the "Software"), to deal
15
+ in the Software without restriction, including without limitation the rights
16
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
+ copies of the Software, and to permit persons to whom the Software is
18
+ furnished to do so, subject to the following conditions:
19
+
20
+ The above copyright notice and this permission notice shall be included in all
21
+ copies or substantial portions of the Software.
22
+
23
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29
+ SOFTWARE.
30
+ License-File: LICENSE
31
+ Keywords: embeddings,mcp,pgvector,postgres,rag,search
32
+ Classifier: Development Status :: 4 - Beta
33
+ Classifier: Intended Audience :: End Users/Desktop
34
+ Classifier: License :: OSI Approved :: MIT License
35
+ Classifier: Operating System :: MacOS
36
+ Classifier: Operating System :: POSIX :: Linux
37
+ Classifier: Programming Language :: Python :: 3
38
+ Classifier: Programming Language :: Python :: 3.13
39
+ Classifier: Programming Language :: Python :: 3.14
40
+ Classifier: Topic :: Text Processing :: Indexing
41
+ Requires-Python: <3.15,>=3.13
42
+ Requires-Dist: certifi>=2024.2.2
43
+ Requires-Dist: cryptography>=44
44
+ Requires-Dist: grpcio>=1.68.0
45
+ Requires-Dist: httpx>=0.28
46
+ Requires-Dist: mcp[cli]==2.1.0
47
+ Requires-Dist: openpyxl>=3.1.5
48
+ Requires-Dist: pdfplumber>=0.11.10
49
+ Requires-Dist: pgvector<0.6,>=0.5
50
+ Requires-Dist: pillow>=11.0
51
+ Requires-Dist: protobuf>=5.29.0
52
+ Requires-Dist: psycopg[pool]>=3.2
53
+ Requires-Dist: pydantic>=2.7.4
54
+ Requires-Dist: pypdf>=6.16
55
+ Requires-Dist: pytest>=9.1.1
56
+ Requires-Dist: python-docx>=1.2
57
+ Requires-Dist: python-pptx>=1.0.2
58
+ Requires-Dist: pyyaml>=6.0
59
+ Requires-Dist: regex>=2023.0.0
60
+ Requires-Dist: rich>=13.9
61
+ Requires-Dist: sqlalchemy>=2.0
62
+ Requires-Dist: truststore>=0.10
63
+ Requires-Dist: typer>=0.15
64
+ Requires-Dist: uvicorn>=0.30
65
+ Provides-Extra: build-tools
66
+ Requires-Dist: build; extra == 'build-tools'
67
+ Requires-Dist: setuptools; extra == 'build-tools'
68
+ Provides-Extra: dev
69
+ Requires-Dist: faker>=37; extra == 'dev'
70
+ Requires-Dist: grpcio-tools>=1.68.0; extra == 'dev'
71
+ Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
72
+ Requires-Dist: ruff>=0.8; extra == 'dev'
73
+ Requires-Dist: tree-sitter-swift>=0.7; extra == 'dev'
74
+ Requires-Dist: tree-sitter>=0.25; extra == 'dev'
75
+ Description-Content-Type: text/markdown
76
+
77
+ # garage_rag
78
+
79
+ The Python half of [Garage](https://github.com/rickmark/garage-rag#readme): a local-first personal RAG pipeline over
80
+ PostgreSQL + pgvector. It walks personal documents, code repositories and communications,
81
+ extracts and chunks their text, attributes authorship, embeds every chunk under each
82
+ registered model, distills documents into span-grounded facts, and serves the corpus over
83
+ MCP 2.0 and gRPC. Garage never sends communications off the machine (MCP clients you connect still receive what they retrieve); see [`docs/privacy.md`](https://github.com/rickmark/garage-rag/blob/main/docs/privacy.md).
84
+
85
+ ## Entry points
86
+
87
+ | Command | Module | Role |
88
+ |---|---|---|
89
+ | `garage` | `garage_rag.cli:main_cli` | ingest, backfill, enrich-facts, search, config, model registry, `serve` (gRPC bridge for the macOS app) |
90
+ | `garage-mcp` | `garage_rag.mcp_server.server:main` | MCP 2.0 server over stdio, the command MCP clients spawn (`garage mcp-install --stdio` wires it into Claude Desktop/Code); `garage mcp-serve` runs the HTTP server |
91
+
92
+ Both are console scripts of the `garage_rag` package. The macOS app does not
93
+ ship them as standalone binaries: it bundles the package as
94
+ `site-python` inside `Frameworks/PythonXPCService.framework` (`//garage_python:site-packages`) and runs it through
95
+ Swift launchers that embed the bundled interpreter, packaged as helper bundles
96
+ (`Contents/Helpers/garage.app` and `garage-mcp.app`, `//macapp/Sources/GarageCLI:garage_app` and
97
+ `//macapp/Sources/GarageMCPCLI:garage_mcp_app`) and reached through the stable paths
98
+ `Contents/MacOS/garage` and `garage-mcp`. Those launchers find or start the app's database and
99
+ export `GARAGE_DATABASE_URL`; the console scripts from a venv do not.
100
+
101
+ ## Layout
102
+
103
+ ```
104
+ src/garage_rag/
105
+ ingest/ walker, materialization budget, chunking, scanner, pipeline, Messages threads,
106
+ the storage gateway (in-process SQLAlchemy, or gRPC for the XPC workers)
107
+ extract/ per-format extractors (text, PDF, Office, images via in-process Tesseract, mail) + quality gate
108
+ attribute/ git / metadata / path-rule / message-sender authorship signals
109
+ embed/ embedding backends (Ollama, LM Studio, llama XPC) and backfill
110
+ enrich/ fact distillation (vendored local LangExtract subset) and local generation
111
+ inference/ the one HTTP client for LM Studio, Ollama and the app's LlamaXPCService
112
+ net/ egress.py, the only module that opens outbound connections
113
+ search/ hybrid RRF search
114
+ mcp_server/ MCP tools and client registration (mcp-install)
115
+ service/ gRPC GarageService (proto/garage.proto) and its client
116
+ ops/ the operations the CLI and the gRPC handlers both present
117
+ xpc/ hooks for the app's XPC hosts (llama_xpc client, model loader)
118
+ native/ finds libpq / libtesseract already loaded into the process
119
+ db/ SQLAlchemy models mirroring data/sql/00*.sql, migrations, model registry
120
+ config/ garage.json settings and the generated JSON Schema
121
+ tests/ pytest suite, one Bazel py_test target per file
122
+ ```
123
+
124
+ ## Building and testing
125
+
126
+ Everything is driven by the Aspect CLI from the repository root:
127
+
128
+ ```bash
129
+ aspect build //garage_python:site-packages
130
+ aspect test //garage_python/tests:suite
131
+ aspect test //garage_python/tests:test_facts --bazel-flag=--test_arg=-k --bazel-flag=--test_arg=some_case
132
+ ```
133
+
134
+ `tests/BUILD.bazel` has one `py_test` per file (via `//tools/pytest:defs.bzl`, which drives
135
+ pytest); `aspect gazelle` writes the target for a new test file, and the `suite` list is kept by
136
+ hand. Third-party
137
+ dependencies live in `pyproject.toml` and are locked with `uv` (`../tools/repin`), exposed to
138
+ Bazel as `@pypi//<package>`.
139
+
140
+ For quick iteration outside Bazel, use the `uv`-managed virtualenv:
141
+
142
+ ```bash
143
+ cd garage_python
144
+ uv sync --extra dev
145
+ uv run pytest -q
146
+ ```
147
+
148
+ The lockfile resolves for macOS only; on Linux use
149
+ `uv venv --python 3.14 .venv && uv pip install -e '.[dev]'`, then `.venv/bin/pytest -q`.
150
+ `tests/test_postgres.py` runs against a real server when `GARAGE_TEST_DATABASE_URL` is set (see
151
+ "Testing against Postgres" in `../CLAUDE.md`).
152
+
153
+ ## Documentation
154
+
155
+ `../docs/` — [architecture](https://github.com/rickmark/garage-rag/blob/main/docs/architecture.md), [schema](https://github.com/rickmark/garage-rag/blob/main/docs/schema.md),
156
+ [attribution](https://github.com/rickmark/garage-rag/blob/main/docs/attribution.md), [privacy](https://github.com/rickmark/garage-rag/blob/main/docs/privacy.md), plus support and
157
+ troubleshooting guides. The generated config schema is committed at
158
+ `../docs/.data/garage.schema.json` and served as `https://garagerag.app/.data/garage.schema.json`.
@@ -0,0 +1,82 @@
1
+ # garage_rag
2
+
3
+ The Python half of [Garage](https://github.com/rickmark/garage-rag#readme): a local-first personal RAG pipeline over
4
+ PostgreSQL + pgvector. It walks personal documents, code repositories and communications,
5
+ extracts and chunks their text, attributes authorship, embeds every chunk under each
6
+ registered model, distills documents into span-grounded facts, and serves the corpus over
7
+ MCP 2.0 and gRPC. Garage never sends communications off the machine (MCP clients you connect still receive what they retrieve); see [`docs/privacy.md`](https://github.com/rickmark/garage-rag/blob/main/docs/privacy.md).
8
+
9
+ ## Entry points
10
+
11
+ | Command | Module | Role |
12
+ |---|---|---|
13
+ | `garage` | `garage_rag.cli:main_cli` | ingest, backfill, enrich-facts, search, config, model registry, `serve` (gRPC bridge for the macOS app) |
14
+ | `garage-mcp` | `garage_rag.mcp_server.server:main` | MCP 2.0 server over stdio, the command MCP clients spawn (`garage mcp-install --stdio` wires it into Claude Desktop/Code); `garage mcp-serve` runs the HTTP server |
15
+
16
+ Both are console scripts of the `garage_rag` package. The macOS app does not
17
+ ship them as standalone binaries: it bundles the package as
18
+ `site-python` inside `Frameworks/PythonXPCService.framework` (`//garage_python:site-packages`) and runs it through
19
+ Swift launchers that embed the bundled interpreter, packaged as helper bundles
20
+ (`Contents/Helpers/garage.app` and `garage-mcp.app`, `//macapp/Sources/GarageCLI:garage_app` and
21
+ `//macapp/Sources/GarageMCPCLI:garage_mcp_app`) and reached through the stable paths
22
+ `Contents/MacOS/garage` and `garage-mcp`. Those launchers find or start the app's database and
23
+ export `GARAGE_DATABASE_URL`; the console scripts from a venv do not.
24
+
25
+ ## Layout
26
+
27
+ ```
28
+ src/garage_rag/
29
+ ingest/ walker, materialization budget, chunking, scanner, pipeline, Messages threads,
30
+ the storage gateway (in-process SQLAlchemy, or gRPC for the XPC workers)
31
+ extract/ per-format extractors (text, PDF, Office, images via in-process Tesseract, mail) + quality gate
32
+ attribute/ git / metadata / path-rule / message-sender authorship signals
33
+ embed/ embedding backends (Ollama, LM Studio, llama XPC) and backfill
34
+ enrich/ fact distillation (vendored local LangExtract subset) and local generation
35
+ inference/ the one HTTP client for LM Studio, Ollama and the app's LlamaXPCService
36
+ net/ egress.py, the only module that opens outbound connections
37
+ search/ hybrid RRF search
38
+ mcp_server/ MCP tools and client registration (mcp-install)
39
+ service/ gRPC GarageService (proto/garage.proto) and its client
40
+ ops/ the operations the CLI and the gRPC handlers both present
41
+ xpc/ hooks for the app's XPC hosts (llama_xpc client, model loader)
42
+ native/ finds libpq / libtesseract already loaded into the process
43
+ db/ SQLAlchemy models mirroring data/sql/00*.sql, migrations, model registry
44
+ config/ garage.json settings and the generated JSON Schema
45
+ tests/ pytest suite, one Bazel py_test target per file
46
+ ```
47
+
48
+ ## Building and testing
49
+
50
+ Everything is driven by the Aspect CLI from the repository root:
51
+
52
+ ```bash
53
+ aspect build //garage_python:site-packages
54
+ aspect test //garage_python/tests:suite
55
+ aspect test //garage_python/tests:test_facts --bazel-flag=--test_arg=-k --bazel-flag=--test_arg=some_case
56
+ ```
57
+
58
+ `tests/BUILD.bazel` has one `py_test` per file (via `//tools/pytest:defs.bzl`, which drives
59
+ pytest); `aspect gazelle` writes the target for a new test file, and the `suite` list is kept by
60
+ hand. Third-party
61
+ dependencies live in `pyproject.toml` and are locked with `uv` (`../tools/repin`), exposed to
62
+ Bazel as `@pypi//<package>`.
63
+
64
+ For quick iteration outside Bazel, use the `uv`-managed virtualenv:
65
+
66
+ ```bash
67
+ cd garage_python
68
+ uv sync --extra dev
69
+ uv run pytest -q
70
+ ```
71
+
72
+ The lockfile resolves for macOS only; on Linux use
73
+ `uv venv --python 3.14 .venv && uv pip install -e '.[dev]'`, then `.venv/bin/pytest -q`.
74
+ `tests/test_postgres.py` runs against a real server when `GARAGE_TEST_DATABASE_URL` is set (see
75
+ "Testing against Postgres" in `../CLAUDE.md`).
76
+
77
+ ## Documentation
78
+
79
+ `../docs/` — [architecture](https://github.com/rickmark/garage-rag/blob/main/docs/architecture.md), [schema](https://github.com/rickmark/garage-rag/blob/main/docs/schema.md),
80
+ [attribution](https://github.com/rickmark/garage-rag/blob/main/docs/attribution.md), [privacy](https://github.com/rickmark/garage-rag/blob/main/docs/privacy.md), plus support and
81
+ troubleshooting guides. The generated config schema is committed at
82
+ `../docs/.data/garage.schema.json` and served as `https://garagerag.app/.data/garage.schema.json`.
@@ -0,0 +1,36 @@
1
+ """Hatch build hook: ship the repository's ``data/`` artifacts inside the package.
2
+
3
+ The pipeline reads the SQL migrations (``data/sql``) and the model catalog
4
+ (``data/models/models.json``) from the repository root, which a wheel installed
5
+ from PyPI does not have. This copies them into the package as
6
+ ``garage_rag/_data``, where ``garage_rag.config.data_dir`` looks first.
7
+
8
+ It only acts when ``../data`` exists, that is, when building from the checkout.
9
+ The sdist carries the files under ``src/garage_rag/_data``, so a wheel built
10
+ from the sdist picks them up as ordinary package files.
11
+ """
12
+
13
+ from pathlib import Path
14
+
15
+ from hatchling.builders.hooks.plugin.interface import BuildHookInterface
16
+
17
+ # (source relative to the repository's data/, glob) for each artifact the package needs.
18
+ ARTIFACTS = [("sql", "*.sql"), ("models", "models.json")]
19
+
20
+
21
+ class DataBuildHook(BuildHookInterface):
22
+ PLUGIN_NAME = "garage-data"
23
+
24
+ def initialize(self, version: str, build_data: dict) -> None:
25
+ if self.target_name == "wheel" and version != "standard":
26
+ return # editable installs read data/ from the checkout
27
+ data = Path(self.root).parent / "data"
28
+ if not data.is_dir():
29
+ return
30
+ prefix = "src/garage_rag/_data" if self.target_name == "sdist" else "garage_rag/_data"
31
+ for subdir, pattern in ARTIFACTS:
32
+ files = sorted((data / subdir).glob(pattern))
33
+ if not files:
34
+ raise FileNotFoundError(f"no {pattern} under {data / subdir}")
35
+ for path in files:
36
+ build_data["force_include"][str(path)] = f"{prefix}/{subdir}/{path.name}"
@@ -0,0 +1,145 @@
1
+ [project]
2
+ name = "garage_rag"
3
+ version = "1.5.0"
4
+ description = "Local-first personal RAG pipeline over Postgres + pgvector, with authorship attribution and an MCP server"
5
+ readme = "README.md"
6
+ license = { file = "LICENSE" }
7
+ authors = [{ name = "Rick Mark-Penwell" }]
8
+ requires-python = ">=3.13,<3.15"
9
+ keywords = ["rag", "mcp", "pgvector", "postgres", "embeddings", "search"]
10
+ classifiers = [
11
+ "Development Status :: 4 - Beta",
12
+ "Intended Audience :: End Users/Desktop",
13
+ "License :: OSI Approved :: MIT License",
14
+ "Operating System :: MacOS",
15
+ "Operating System :: POSIX :: Linux",
16
+ "Programming Language :: Python :: 3",
17
+ "Programming Language :: Python :: 3.13",
18
+ "Programming Language :: Python :: 3.14",
19
+ "Topic :: Text Processing :: Indexing",
20
+ ]
21
+
22
+ dependencies = [
23
+ # storage
24
+ "sqlalchemy>=2.0",
25
+ "psycopg[pool]>=3.2",
26
+ "pgvector>=0.5,<0.6",
27
+ # document extraction (all permissive licenses; PyMuPDF avoided due to AGPL)
28
+ "pypdf>=6.16",
29
+ "pdfplumber>=0.11.10",
30
+ # Not imported directly: pulled in transitively by pdfminer-six (pdfplumber).
31
+ # The old <44 ceiling had no recorded reason and blocked the free-threaded
32
+ # build: 43.x has no cp314t wheels and does not build from source there.
33
+ "cryptography>=44",
34
+ "python-docx>=1.2",
35
+ "python-pptx>=1.0.2",
36
+ "openpyxl>=3.1.5",
37
+ # OCR
38
+ "pillow>=11.0",
39
+ # fact extraction: the vendored LangExtract subset (garage_rag/enrich/langextract)
40
+ # needs regex for its tokenizer.
41
+ "regex>=2023.0.0",
42
+ # the egress guard's HTTP client (garage_rag/net/egress.py), which every
43
+ # outbound HTTP request goes through, garage_rag/inference's included
44
+ "httpx>=0.28",
45
+ # MCP server (2.x renamed FastMCP -> MCPServer; 1.x is incompatible)
46
+ "mcp[cli]==2.1.0",
47
+ # HTTP transport for garage-mcp (mcp_server/server.py imports it directly)
48
+ "uvicorn>=0.30",
49
+ # plumbing
50
+ "typer>=0.15",
51
+ "pydantic>=2.7.4",
52
+ "rich>=13.9",
53
+ "pyyaml>=6.0",
54
+ # A runtime dep on purpose: the Bazel @pypi hub (--@pypi//dep_group=garage_rag)
55
+ # only exposes packages reachable from [project.dependencies], so the py_test
56
+ # targets can't resolve @pypi//pytest from an optional/dev group.
57
+ "pytest>=9.1.1",
58
+ # gRPC & Protobuf
59
+ "grpcio>=1.68.0",
60
+ "protobuf>=5.29.0",
61
+ # Not imported by garage_rag: the macOS app's embedded interpreter imports them at start-up so
62
+ # ssl's default contexts verify against the system trust store (certifi is the fallback). The
63
+ # bundled OpenSSL has no CA files of its own.
64
+ "truststore>=0.10",
65
+ "certifi>=2024.2.2",
66
+ ]
67
+
68
+ [project.optional-dependencies]
69
+ # Local tooling for the uv venv only; the Bazel @pypi hub does not expose this group.
70
+ dev = [
71
+ "pytest-asyncio>=0.24",
72
+ "ruff>=0.8",
73
+ "grpcio-tools>=1.68.0",
74
+ # tools/swiftcheck: Swift syntax checking where no Swift toolchain exists (Linux, web sessions)
75
+ "tree-sitter>=0.25",
76
+ "tree-sitter-swift>=0.7",
77
+ # garage_python/tests/fake_messages.py: the invented people and messages in the fake chat.db
78
+ "faker>=37",
79
+ ]
80
+ # aspect_rules_py builds sdists into wheels with `build` and `setuptools`, resolved from uv.lock
81
+ # and from the hub's dependency group. grpcio ships no free-threaded (cp314t) wheels, so the 3.14t
82
+ # build compiles it from source. The Bazel group below pulls this extra in.
83
+ build-tools = ["build", "setuptools"]
84
+
85
+ [dependency-groups]
86
+ # The @pypi hub's group (`--@pypi//dep_group=garage_rag` in .bazelrc). Without a
87
+ # [dependency-groups] table the hub derives it from the project alone; declaring one replaces that,
88
+ # so it names the project itself, plus the sdist build tools.
89
+ garage_rag = ["garage_rag[build-tools]"]
90
+
91
+ [project.urls]
92
+ Homepage = "https://garagerag.app"
93
+ Source = "https://github.com/rickmark/garage-rag"
94
+ Issues = "https://github.com/rickmark/garage-rag/issues"
95
+
96
+ [project.scripts]
97
+ garage = "garage_rag.cli:main_cli"
98
+ garage-mcp = "garage_rag.mcp_server.server:main"
99
+
100
+ [build-system]
101
+ requires = ["hatchling"]
102
+ build-backend = "hatchling.build"
103
+
104
+ [tool.hatch.build.targets.wheel]
105
+ packages = ["src/garage_rag"]
106
+ exclude = ["BUILD.bazel"]
107
+
108
+ # PyPI's sdist: the package and its build hook, without the tests (their corpora are most of the
109
+ # size) or the Bazel and uv files.
110
+ [tool.hatch.build.targets.sdist]
111
+ include = ["/src/garage_rag", "/hatch_build.py", "/README.md", "/LICENSE"]
112
+ exclude = ["BUILD.bazel"]
113
+
114
+ # hatch_build.py: copies ../data/sql and ../data/models/models.json into the package as
115
+ # garage_rag/_data, which a PyPI install reads in place of the repository's data/.
116
+ [tool.hatch.build.hooks.custom]
117
+
118
+ [tool.uv]
119
+ environments = ["sys_platform == 'darwin' and platform_machine == 'arm64'"]
120
+
121
+ [tool.ruff]
122
+ line-length = 120
123
+ target-version = "py313"
124
+ # Vendored third-party code keeps its upstream style (see its __init__ docstring), and the test
125
+ # corpora are upstream copies too (ruff also formats the code blocks in their Markdown).
126
+ extend-exclude = ["*_pb2.py", "*_pb2_grpc.py", "*_pb2.pyi", "src/garage_rag/enrich/langextract", "tests/corpora"]
127
+
128
+ [tool.ruff.lint]
129
+ select = ["E", "F", "I", "UP", "B", "SIM"]
130
+
131
+ [tool.ruff.lint.isort]
132
+ known-first-party = ["garage_rag"]
133
+
134
+ [tool.pytest.ini_options]
135
+ testpaths = ["tests"]
136
+ filterwarnings = ["error::DeprecationWarning"]
137
+
138
+ [tool.ty.src]
139
+ # Generated protobuf stubs are excluded from type checking, as they are from Ruff
140
+ # above. `exclude` lives under `src` for ty; a top-level `[tool.ty] exclude` is a
141
+ # hard config error that makes ty exit non-zero before it checks anything.
142
+ exclude = ["*_pb2.py", "*_pb2_grpc.py", "*_pb2.pyi", "src/garage_rag/enrich/langextract"]
143
+
144
+ [tool.ty.rules]
145
+ unresolved-import = "ignore"
@@ -0,0 +1,15 @@
1
+ """garage-rag: a local-first personal RAG pipeline.
2
+
3
+ Indexes personal documents into Postgres + pgvector, preserving authorship,
4
+ reference, and communication distinctions, and exposes the corpus to Claude
5
+ through an MCP server.
6
+ """
7
+
8
+ __version__ = "1.5.0"
9
+
10
+ # Must run before psycopg is imported anywhere in the package: makes psycopg's
11
+ # ctypes based libpq lookup use the signed copy the app's framework has loaded
12
+ # instead of a Homebrew/system library that dyld rejects.
13
+ from . import libpq as _libpq # noqa: E402
14
+
15
+ _libpq.configure()