localgate 0.7.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 (142) hide show
  1. localgate-0.7.0/.env.example +62 -0
  2. localgate-0.7.0/.github/CODEOWNERS +11 -0
  3. localgate-0.7.0/.github/FUNDING.yml +1 -0
  4. localgate-0.7.0/.github/ISSUE_TEMPLATE/bug_report.md +13 -0
  5. localgate-0.7.0/.github/ISSUE_TEMPLATE/config.yml +1 -0
  6. localgate-0.7.0/.github/ISSUE_TEMPLATE/feature_request.md +9 -0
  7. localgate-0.7.0/.github/PULL_REQUEST_TEMPLATE.md +7 -0
  8. localgate-0.7.0/.github/workflows/ci.yml +110 -0
  9. localgate-0.7.0/.github/workflows/docker.yml +45 -0
  10. localgate-0.7.0/.github/workflows/release.yml +50 -0
  11. localgate-0.7.0/.gitignore +44 -0
  12. localgate-0.7.0/.pre-commit-config.yaml +29 -0
  13. localgate-0.7.0/.python-version +1 -0
  14. localgate-0.7.0/CHANGELOG.md +108 -0
  15. localgate-0.7.0/CODE_OF_CONDUCT.md +6 -0
  16. localgate-0.7.0/CONTRIBUTING.md +115 -0
  17. localgate-0.7.0/Dockerfile +55 -0
  18. localgate-0.7.0/LICENSE +21 -0
  19. localgate-0.7.0/Makefile +37 -0
  20. localgate-0.7.0/PKG-INFO +239 -0
  21. localgate-0.7.0/README.md +188 -0
  22. localgate-0.7.0/SECURITY.md +86 -0
  23. localgate-0.7.0/docker-compose.yml +26 -0
  24. localgate-0.7.0/docs/api-reference.md +254 -0
  25. localgate-0.7.0/docs/architecture.md +172 -0
  26. localgate-0.7.0/docs/configuration.md +158 -0
  27. localgate-0.7.0/docs/database-setup.md +154 -0
  28. localgate-0.7.0/docs/decisions/0001-dependencies-over-middleware.md +50 -0
  29. localgate-0.7.0/docs/decisions/0002-json-embeddings-over-pgvector.md +61 -0
  30. localgate-0.7.0/docs/decisions/0003-sha256-for-api-keys.md +60 -0
  31. localgate-0.7.0/docs/deployment.md +174 -0
  32. localgate-0.7.0/docs/getting-started.md +169 -0
  33. localgate-0.7.0/docs/images/dashboard-keys.png +0 -0
  34. localgate-0.7.0/docs/images/dashboard-overview.png +0 -0
  35. localgate-0.7.0/docs/rag-memory.md +153 -0
  36. localgate-0.7.0/examples/curl_examples.sh +61 -0
  37. localgate-0.7.0/examples/docker-compose.postgres.yml +44 -0
  38. localgate-0.7.0/examples/langchain_example.py +5 -0
  39. localgate-0.7.0/examples/openai_sdk_example.py +71 -0
  40. localgate-0.7.0/pyproject.toml +122 -0
  41. localgate-0.7.0/scripts/benchmark.py +129 -0
  42. localgate-0.7.0/scripts/dev-setup.sh +11 -0
  43. localgate-0.7.0/src/localgate/__init__.py +8 -0
  44. localgate-0.7.0/src/localgate/__main__.py +6 -0
  45. localgate-0.7.0/src/localgate/agent/__init__.py +8 -0
  46. localgate-0.7.0/src/localgate/agent/gitutil.py +104 -0
  47. localgate-0.7.0/src/localgate/agent/ignore.py +66 -0
  48. localgate-0.7.0/src/localgate/agent/loop.py +322 -0
  49. localgate-0.7.0/src/localgate/agent/memory.py +149 -0
  50. localgate-0.7.0/src/localgate/agent/render.py +64 -0
  51. localgate-0.7.0/src/localgate/agent/repl.py +238 -0
  52. localgate-0.7.0/src/localgate/agent/tools.py +292 -0
  53. localgate-0.7.0/src/localgate/api/__init__.py +0 -0
  54. localgate-0.7.0/src/localgate/api/chat.py +410 -0
  55. localgate-0.7.0/src/localgate/api/completions.py +82 -0
  56. localgate-0.7.0/src/localgate/api/config.py +150 -0
  57. localgate-0.7.0/src/localgate/api/conversations.py +69 -0
  58. localgate-0.7.0/src/localgate/api/deps.py +85 -0
  59. localgate-0.7.0/src/localgate/api/embeddings.py +55 -0
  60. localgate-0.7.0/src/localgate/api/export.py +92 -0
  61. localgate-0.7.0/src/localgate/api/health.py +126 -0
  62. localgate-0.7.0/src/localgate/api/keys.py +114 -0
  63. localgate-0.7.0/src/localgate/api/models.py +39 -0
  64. localgate-0.7.0/src/localgate/api/usage.py +31 -0
  65. localgate-0.7.0/src/localgate/app.py +183 -0
  66. localgate-0.7.0/src/localgate/backends/__init__.py +98 -0
  67. localgate-0.7.0/src/localgate/backends/base.py +43 -0
  68. localgate-0.7.0/src/localgate/backends/fake.py +90 -0
  69. localgate-0.7.0/src/localgate/backends/llamacpp.py +16 -0
  70. localgate-0.7.0/src/localgate/backends/ollama.py +23 -0
  71. localgate-0.7.0/src/localgate/backends/openai_compat.py +97 -0
  72. localgate-0.7.0/src/localgate/backends/vllm.py +16 -0
  73. localgate-0.7.0/src/localgate/cli.py +471 -0
  74. localgate-0.7.0/src/localgate/config.py +142 -0
  75. localgate-0.7.0/src/localgate/core/__init__.py +0 -0
  76. localgate-0.7.0/src/localgate/core/auth.py +46 -0
  77. localgate-0.7.0/src/localgate/core/cache.py +91 -0
  78. localgate-0.7.0/src/localgate/core/db_config_store.py +46 -0
  79. localgate-0.7.0/src/localgate/core/errors.py +146 -0
  80. localgate-0.7.0/src/localgate/core/logging.py +66 -0
  81. localgate-0.7.0/src/localgate/core/metrics.py +68 -0
  82. localgate-0.7.0/src/localgate/core/rate_limiter.py +66 -0
  83. localgate-0.7.0/src/localgate/core/streaming.py +41 -0
  84. localgate-0.7.0/src/localgate/core/token_counter.py +58 -0
  85. localgate-0.7.0/src/localgate/core/types.py +159 -0
  86. localgate-0.7.0/src/localgate/dashboard/__init__.py +0 -0
  87. localgate-0.7.0/src/localgate/dashboard/routes.py +29 -0
  88. localgate-0.7.0/src/localgate/dashboard/static/index.html +878 -0
  89. localgate-0.7.0/src/localgate/db/__init__.py +0 -0
  90. localgate-0.7.0/src/localgate/db/engine.py +134 -0
  91. localgate-0.7.0/src/localgate/db/migrations/__init__.py +1 -0
  92. localgate-0.7.0/src/localgate/db/migrations/alembic.ini +46 -0
  93. localgate-0.7.0/src/localgate/db/migrations/env.py +92 -0
  94. localgate-0.7.0/src/localgate/db/migrations/script.py.mako +27 -0
  95. localgate-0.7.0/src/localgate/db/migrations/versions/0001_initial_schema.py +92 -0
  96. localgate-0.7.0/src/localgate/db/migrations/versions/0002_summaries_and_accounting.py +135 -0
  97. localgate-0.7.0/src/localgate/db/models.py +133 -0
  98. localgate-0.7.0/src/localgate/db/repositories/__init__.py +0 -0
  99. localgate-0.7.0/src/localgate/db/repositories/conversations.py +114 -0
  100. localgate-0.7.0/src/localgate/db/repositories/embeddings.py +103 -0
  101. localgate-0.7.0/src/localgate/db/repositories/keys.py +82 -0
  102. localgate-0.7.0/src/localgate/db/repositories/usage.py +146 -0
  103. localgate-0.7.0/src/localgate/memory/__init__.py +0 -0
  104. localgate-0.7.0/src/localgate/memory/chunker.py +23 -0
  105. localgate-0.7.0/src/localgate/memory/context_builder.py +53 -0
  106. localgate-0.7.0/src/localgate/memory/embedder.py +7 -0
  107. localgate-0.7.0/src/localgate/memory/retriever.py +31 -0
  108. localgate-0.7.0/src/localgate/memory/summarizer.py +125 -0
  109. localgate-0.7.0/src/localgate/middleware/__init__.py +10 -0
  110. localgate-0.7.0/src/localgate/middleware/logging_middleware.py +113 -0
  111. localgate-0.7.0/tests/__init__.py +0 -0
  112. localgate-0.7.0/tests/conftest.py +80 -0
  113. localgate-0.7.0/tests/e2e/__init__.py +0 -0
  114. localgate-0.7.0/tests/e2e/test_full_flow.py +71 -0
  115. localgate-0.7.0/tests/integration/__init__.py +0 -0
  116. localgate-0.7.0/tests/integration/test_chat_endpoint.py +143 -0
  117. localgate-0.7.0/tests/integration/test_chat_resilience.py +206 -0
  118. localgate-0.7.0/tests/integration/test_cli.py +146 -0
  119. localgate-0.7.0/tests/integration/test_database_connectors.py +112 -0
  120. localgate-0.7.0/tests/integration/test_key_management.py +152 -0
  121. localgate-0.7.0/tests/integration/test_migrations.py +247 -0
  122. localgate-0.7.0/tests/integration/test_operations.py +389 -0
  123. localgate-0.7.0/tests/integration/test_rag_pipeline.py +137 -0
  124. localgate-0.7.0/tests/integration/test_summarizer.py +100 -0
  125. localgate-0.7.0/tests/unit/__init__.py +0 -0
  126. localgate-0.7.0/tests/unit/test_agent_gitutil.py +88 -0
  127. localgate-0.7.0/tests/unit/test_agent_ignore.py +67 -0
  128. localgate-0.7.0/tests/unit/test_agent_loop.py +361 -0
  129. localgate-0.7.0/tests/unit/test_agent_memory.py +186 -0
  130. localgate-0.7.0/tests/unit/test_agent_render.py +42 -0
  131. localgate-0.7.0/tests/unit/test_agent_repl.py +259 -0
  132. localgate-0.7.0/tests/unit/test_agent_streaming.py +107 -0
  133. localgate-0.7.0/tests/unit/test_agent_tools.py +205 -0
  134. localgate-0.7.0/tests/unit/test_auth.py +26 -0
  135. localgate-0.7.0/tests/unit/test_backend_error_messages.py +53 -0
  136. localgate-0.7.0/tests/unit/test_backends.py +207 -0
  137. localgate-0.7.0/tests/unit/test_cache.py +82 -0
  138. localgate-0.7.0/tests/unit/test_chunker.py +35 -0
  139. localgate-0.7.0/tests/unit/test_config.py +84 -0
  140. localgate-0.7.0/tests/unit/test_rate_limiter.py +68 -0
  141. localgate-0.7.0/tests/unit/test_token_counter.py +28 -0
  142. localgate-0.7.0/uv.lock +2093 -0
@@ -0,0 +1,62 @@
1
+ # Copy to .env and edit. Every setting is documented in docs/configuration.md.
2
+
3
+ # --- Deployment ------------------------------------------------------------
4
+ # `production` makes the gateway REFUSE to start with the placeholder admin key below.
5
+ LOCALGATE_ENVIRONMENT=development
6
+
7
+ # --- Auth ------------------------------------------------------------------
8
+ # Generate a real one: openssl rand -hex 32
9
+ LOCALGATE_ADMIN_KEY=change-me-in-production
10
+ LOCALGATE_DEFAULT_RATE_LIMIT_PER_MIN=60
11
+
12
+ # --- Server ----------------------------------------------------------------
13
+ LOCALGATE_HOST=0.0.0.0
14
+ LOCALGATE_PORT=8000
15
+ # Only needed if a browser calls the API directly. Comma-separated.
16
+ # LOCALGATE_CORS_ORIGINS=http://localhost:3000
17
+
18
+ # --- Inference backend -----------------------------------------------------
19
+ # ollama | vllm | llamacpp | openai_compat (or any installed plugin)
20
+ LOCALGATE_BACKEND_TYPE=ollama
21
+ LOCALGATE_BACKEND_URL=http://localhost:11434
22
+ LOCALGATE_DEFAULT_MODEL=llama3
23
+ # Raise for large models on CPU.
24
+ LOCALGATE_BACKEND_TIMEOUT=120
25
+ # For upstreams that need their own auth.
26
+ # LOCALGATE_BACKEND_API_KEY=
27
+
28
+ # Map friendly names to real model ids, so you can swap models without touching clients.
29
+ # LOCALGATE_MODEL_ALIASES={"fast":"phi4-mini","smart":"llama3:70b"}
30
+
31
+ # --- Database --------------------------------------------------------------
32
+ # The driver must be ASYNC. Note `+asyncpg`, not a bare `postgresql://`.
33
+ LOCALGATE_DATABASE_URL=sqlite+aiosqlite:///./localgate.db
34
+ # Postgres: LOCALGATE_DATABASE_URL=postgresql+asyncpg://user:pass@localhost:5432/localgate
35
+ # Neon: LOCALGATE_DATABASE_URL=postgresql+asyncpg://user:pass@ep-xxx.neon.tech/localgate?ssl=require
36
+ # (Neon's copy-paste string says `sslmode=require`; asyncpg spells it `ssl=require`.)
37
+
38
+ # --- Memory / RAG ----------------------------------------------------------
39
+ LOCALGATE_MEMORY_ENABLED=true
40
+ # Must be pulled on the backend: ollama pull nomic-embed-text
41
+ LOCALGATE_EMBEDDING_MODEL=nomic-embed-text
42
+ LOCALGATE_CHUNK_SIZE=512
43
+ LOCALGATE_CHUNK_OVERLAP=50
44
+ LOCALGATE_MAX_RETRIEVED_CHUNKS=5
45
+ # Cosine-similarity floor for injecting a chunk. 0.0 = no floor. With a real embedding
46
+ # model, 0.3-0.5 is typical — see docs/rag-memory.md for how to tune it from your logs.
47
+ LOCALGATE_MEMORY_MIN_SCORE=0.0
48
+ # Summarize older turns past this many messages. 0 disables.
49
+ LOCALGATE_SUMMARIZE_AFTER_MESSAGES=20
50
+
51
+ # --- Prompt cache ----------------------------------------------------------
52
+ # Off by default: caching makes identical requests return identical completions, which is
53
+ # a real behaviour change if you sample at temperature > 0.
54
+ LOCALGATE_CACHE_ENABLED=false
55
+ LOCALGATE_CACHE_TTL_SECONDS=300
56
+ LOCALGATE_CACHE_MAX_ENTRIES=512
57
+
58
+ # --- Observability ---------------------------------------------------------
59
+ LOCALGATE_LOG_LEVEL=INFO
60
+ # `console` for humans, `json` for log ingestion.
61
+ LOCALGATE_LOG_FORMAT=console
62
+ LOCALGATE_METRICS_ENABLED=true
@@ -0,0 +1,11 @@
1
+ # Default reviewer for everything.
2
+ * @AnjalLLL
3
+
4
+ # Changes here alter the security posture — auth, key handling, the rules that decide who
5
+ # can call what. They always want a second pair of eyes, even on a one-line diff.
6
+ /src/localgate/core/auth.py @AnjalLLL
7
+ /src/localgate/api/deps.py @AnjalLLL
8
+ /SECURITY.md @AnjalLLL
9
+
10
+ # Migrations are the one thing a user cannot roll back by reverting a commit.
11
+ /src/localgate/db/migrations/ @AnjalLLL
@@ -0,0 +1 @@
1
+ # github: [AnjalLLL]
@@ -0,0 +1,13 @@
1
+ ---
2
+ name: Bug report
3
+ about: Report a bug
4
+ labels: bug
5
+ ---
6
+
7
+ **Describe the bug**
8
+
9
+ **To reproduce**
10
+
11
+ **Expected behavior**
12
+
13
+ **Environment** (OS, Python version, backend used)
@@ -0,0 +1 @@
1
+ blank_issues_enabled: true
@@ -0,0 +1,9 @@
1
+ ---
2
+ name: Feature request
3
+ about: Suggest an idea
4
+ labels: enhancement
5
+ ---
6
+
7
+ **Problem this solves**
8
+
9
+ **Proposed solution**
@@ -0,0 +1,7 @@
1
+ ## What this changes
2
+
3
+ ## How it was tested
4
+
5
+ ## Checklist
6
+ - [ ] Tests added/updated
7
+ - [ ] `make lint` passes
@@ -0,0 +1,110 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ concurrency:
10
+ group: ${{ github.workflow }}-${{ github.ref }}
11
+ cancel-in-progress: true
12
+
13
+ jobs:
14
+ test:
15
+ runs-on: ubuntu-latest
16
+ strategy:
17
+ fail-fast: false
18
+ matrix:
19
+ python-version: ["3.10", "3.11", "3.12", "3.13"]
20
+
21
+ steps:
22
+ - uses: actions/checkout@v4
23
+
24
+ - name: Install uv
25
+ uses: astral-sh/setup-uv@v5
26
+ with:
27
+ enable-cache: true
28
+
29
+ - name: Set up Python ${{ matrix.python-version }}
30
+ run: uv python install ${{ matrix.python-version }}
31
+
32
+ - name: Sync dependencies
33
+ run: uv sync --all-extras --python ${{ matrix.python-version }}
34
+
35
+ - name: Lint
36
+ run: uv run ruff check src/ tests/
37
+
38
+ - name: Format check
39
+ run: uv run ruff format --check src/ tests/
40
+
41
+ - name: Type check
42
+ run: uv run mypy src/
43
+
44
+ - name: Test
45
+ run: uv run pytest -q --cov --cov-report=term-missing
46
+
47
+ postgres:
48
+ # The SQLite suite proves the logic. This proves the migrations and queries actually
49
+ # run on Postgres, which is what production uses — skipping it is how you ship a
50
+ # migration that only works on SQLite.
51
+ runs-on: ubuntu-latest
52
+ env:
53
+ LOCALGATE_DATABASE_URL: postgresql+asyncpg://localgate:localgate@localhost:5432/localgate
54
+ LOCALGATE_BACKEND_TYPE: fake
55
+ LOCALGATE_ADMIN_KEY: ci-admin-key
56
+
57
+ services:
58
+ postgres:
59
+ image: postgres:16
60
+ env:
61
+ POSTGRES_USER: localgate
62
+ POSTGRES_PASSWORD: localgate
63
+ POSTGRES_DB: localgate
64
+ ports: ["5432:5432"]
65
+ options: >-
66
+ --health-cmd pg_isready
67
+ --health-interval 10s
68
+ --health-timeout 5s
69
+ --health-retries 5
70
+
71
+ steps:
72
+ - uses: actions/checkout@v4
73
+ - uses: astral-sh/setup-uv@v5
74
+ with:
75
+ enable-cache: true
76
+ - run: uv sync --all-extras
77
+
78
+ - name: Migrate a real Postgres database
79
+ run: |
80
+ uv run localgate db upgrade
81
+ uv run localgate db current
82
+
83
+ - name: Migrations are idempotent (startup re-runs them on every boot)
84
+ run: uv run localgate db upgrade
85
+
86
+ - name: Create and list a key against Postgres
87
+ run: |
88
+ uv run localgate keys create --name ci-key
89
+ uv run localgate keys list
90
+
91
+ docker:
92
+ runs-on: ubuntu-latest
93
+ steps:
94
+ - uses: actions/checkout@v4
95
+
96
+ - name: Build the image
97
+ run: docker build -t localgate:ci .
98
+
99
+ - name: The image starts and serves
100
+ run: |
101
+ docker run -d --name lg -p 8000:8000 \
102
+ -e LOCALGATE_BACKEND_TYPE=fake \
103
+ -e LOCALGATE_ADMIN_KEY=ci-admin-key \
104
+ localgate:ci
105
+ for _ in $(seq 1 30); do
106
+ curl -sf http://localhost:8000/health/live > /dev/null && break
107
+ sleep 1
108
+ done
109
+ curl -sf http://localhost:8000/health/live || (docker logs lg && exit 1)
110
+ docker rm -f lg
@@ -0,0 +1,45 @@
1
+ name: Docker
2
+
3
+ on:
4
+ push:
5
+ tags: ["v*"]
6
+
7
+ jobs:
8
+ docker:
9
+ runs-on: ubuntu-latest
10
+ permissions:
11
+ contents: read
12
+ packages: write
13
+
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+
17
+ - uses: docker/setup-qemu-action@v3
18
+ - uses: docker/setup-buildx-action@v3
19
+
20
+ - uses: docker/login-action@v3
21
+ with:
22
+ registry: ghcr.io
23
+ username: ${{ github.actor }}
24
+ password: ${{ secrets.GITHUB_TOKEN }}
25
+
26
+ - name: Tags and labels
27
+ id: meta
28
+ uses: docker/metadata-action@v5
29
+ with:
30
+ images: ghcr.io/${{ github.repository }}
31
+ tags: |
32
+ type=semver,pattern={{version}}
33
+ type=semver,pattern={{major}}.{{minor}}
34
+ type=raw,value=latest
35
+
36
+ - uses: docker/build-push-action@v6
37
+ with:
38
+ push: true
39
+ # Local models run on Apple Silicon and on Raspberry Pis as often as on x86, so
40
+ # a gateway that only ships amd64 is useless to a large share of its users.
41
+ platforms: linux/amd64,linux/arm64
42
+ tags: ${{ steps.meta.outputs.tags }}
43
+ labels: ${{ steps.meta.outputs.labels }}
44
+ cache-from: type=gha
45
+ cache-to: type=gha,mode=max
@@ -0,0 +1,50 @@
1
+ name: Release to PyPI
2
+
3
+ on:
4
+ push:
5
+ tags: ["v*"]
6
+
7
+ jobs:
8
+ # Never publish something that doesn't pass. A tag is not a promise that CI ran — the
9
+ # tag can be pushed from a branch that was never tested, and PyPI releases cannot be
10
+ # unpublished, only yanked.
11
+ test:
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+ - uses: astral-sh/setup-uv@v5
16
+ with:
17
+ enable-cache: true
18
+ - run: uv sync --all-extras
19
+ - run: uv run ruff check src/ tests/
20
+ - run: uv run mypy src/
21
+ - run: uv run pytest -q
22
+
23
+ publish:
24
+ needs: test
25
+ runs-on: ubuntu-latest
26
+ environment: release
27
+ permissions:
28
+ # Trusted publishing: no API token to store, rotate, or leak.
29
+ id-token: write
30
+
31
+ steps:
32
+ - uses: actions/checkout@v4
33
+ - uses: astral-sh/setup-uv@v5
34
+
35
+ # __version__ and pyproject's version are two places that can disagree, and the one
36
+ # users see at runtime is __version__. A release whose version lies about itself is
37
+ # worse than no release, so the tag must match both.
38
+ - name: Check the tag matches the declared version
39
+ run: |
40
+ TAG="${GITHUB_REF_NAME#v}"
41
+ INIT=$(sed -n 's/^__version__ = "\(.*\)"/\1/p' src/localgate/__init__.py)
42
+ TOML=$(sed -n 's/^version = "\(.*\)"/\1/p' pyproject.toml | head -1)
43
+ echo "tag=$TAG __init__=$INIT pyproject=$TOML"
44
+ test "$TAG" = "$INIT" && test "$TAG" = "$TOML"
45
+
46
+ - name: Build
47
+ run: uv build
48
+
49
+ - name: Publish
50
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,44 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+
8
+ # Environments
9
+ .venv/
10
+ venv/
11
+ env/
12
+
13
+ # Secrets and local state. .env holds the admin key; localgate.config.json holds the
14
+ # established database URL, credentials included.
15
+ .env
16
+ !.env.example
17
+ localgate.config.json
18
+
19
+ # Private planning notes — kept locally, never published.
20
+ ROADMAP.md
21
+
22
+ # Local codegen / patch scratch scripts
23
+ apply_*.sh
24
+ reapply_*.sh
25
+
26
+ # Databases
27
+ *.db
28
+ *.sqlite
29
+ *.sqlite3
30
+
31
+ # Tooling caches
32
+ .pytest_cache/
33
+ .mypy_cache/
34
+ .ruff_cache/
35
+ .coverage
36
+ coverage.xml
37
+ htmlcov/
38
+
39
+ # Editors / OS
40
+ .vscode/
41
+ .idea/
42
+ *.swp
43
+ .DS_Store
44
+ Thumbs.db
@@ -0,0 +1,29 @@
1
+ repos:
2
+ - repo: https://github.com/astral-sh/ruff-pre-commit
3
+ rev: v0.9.6
4
+ hooks:
5
+ - id: ruff
6
+ args: [--fix]
7
+ - id: ruff-format
8
+
9
+ - repo: https://github.com/pre-commit/pre-commit-hooks
10
+ rev: v5.0.0
11
+ hooks:
12
+ - id: trailing-whitespace
13
+ - id: end-of-file-fixer
14
+ - id: check-yaml
15
+ - id: check-toml
16
+ - id: check-merge-conflict
17
+ - id: check-added-large-files
18
+ # .gitignore already excludes .env, but `git add -f` exists and a committed .env
19
+ # leaks the admin key. This is the second line of defence.
20
+ - id: detect-private-key
21
+
22
+ - repo: https://github.com/pre-commit/mirrors-mypy
23
+ rev: v1.14.1
24
+ hooks:
25
+ - id: mypy
26
+ files: ^src/
27
+ additional_dependencies:
28
+ - pydantic>=2.7
29
+ - pydantic-settings>=2.3
@@ -0,0 +1 @@
1
+ 3.12
@@ -0,0 +1,108 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here.
4
+ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this
5
+ project adheres to [Semantic Versioning](https://semver.org/).
6
+
7
+ ## [0.7.0] — 2026-07-19
8
+
9
+ A coding agent, backed by whatever model localgate is already pointed at.
10
+
11
+ ### Added
12
+
13
+ - **`localgate code`** — reads and edits files in a project directory, backed directly by
14
+ the configured inference backend (no API key needed, same pattern as `localgate health`).
15
+ Run it with no argument for an interactive REPL (`/exit`, `/clear`, `/model`, `/undo`), or
16
+ `localgate code "task"` for a single-shot run.
17
+ - Tools: `read_file`, `write_file`, `list_directory`, `search_files` (grep-like), `git_status`,
18
+ `git_diff`. All confined to the project root; `.gitignore` and `.localgateignore` keep
19
+ secrets and generated directories out of the model's reach. No shell/`run_command` tool —
20
+ deliberately.
21
+ - A git-aware safety net: a one-time warning before writing into a dirty tree (`--force` to
22
+ skip), optional `--auto-commit` (tagged `localgate-agent:`), and `/undo` to revert the last
23
+ write or the last agent commit.
24
+ - Colored diffs before every write, streamed responses, and a spinner while waiting on the
25
+ model — via `rich`.
26
+ - Session memory: conversation history and recalled context persist per project
27
+ (`.localgate/session_id`), reusing the same RAG memory tables the HTTP API uses.
28
+ `--no-memory` to opt out for a single run.
29
+ - A fallback parser for models that print a tool call as plain-text JSON instead of using
30
+ structured `tool_calls` (observed with `qwen2.5-coder` via Ollama) — same execution path as
31
+ a real tool call, so confirmation/diff/memory logic isn't duplicated.
32
+ - Shell completion: `localgate --install-completion`.
33
+
34
+ ## [0.6.0] — 2026-07-14
35
+
36
+ The release where the advertised features actually work.
37
+
38
+ ### Fixed
39
+
40
+ - **The vLLM, llama.cpp and generic OpenAI backends did not work at all.** They declared
41
+ `InferenceBackend` subclasses that implemented none of its abstract methods, so
42
+ `get_backend("vllm")` raised `TypeError` at startup. Three of the four advertised backends
43
+ were unusable. They now share a real OpenAI-compatible HTTP implementation.
44
+ - **Malformed chat requests returned 500 instead of 422.** The handler read `body["messages"]`
45
+ off a raw dict, so a missing field was a crash rather than a client error. Every request
46
+ body is now validated by Pydantic.
47
+ - **The admin key was compared with `==`**, which short-circuits at the first differing byte
48
+ and leaks the secret's prefix length through timing. Now `hmac.compare_digest`.
49
+ - **The rate limiter never forgot a key it had seen**, leaking one dict entry per key
50
+ forever. Expired windows are now swept.
51
+ - **Errors did not use the OpenAI envelope**, so the OpenAI SDK could not parse them. All
52
+ errors — including validation failures and mid-stream backend failures — now return
53
+ `{"error": {"message", "type", "code"}}`.
54
+ - **A mid-stream backend failure left clients hanging.** It now reports the error inside the
55
+ stream and still terminates with `[DONE]`.
56
+
57
+ ### Added
58
+
59
+ - **Migrations.** The schema is versioned with Alembic; `create_all` is gone. Databases from
60
+ 0.1–0.2 (tables, but no `alembic_version`) are detected and adopted automatically without
61
+ data loss.
62
+ - **A real CLI.** `keys create/list/revoke/usage`, `db upgrade/current/init`, `health`,
63
+ `backends` — the commands the README had been advertising all along.
64
+ - **`/v1/models`, `/v1/embeddings`, `/v1/completions`** — previously empty files. The legacy
65
+ completions route is implemented by translating to chat, so it works against backends that
66
+ never implemented it.
67
+ - **Rolling conversation summarization.** Past a threshold, older turns are incrementally
68
+ summarized and injected alongside retrieved chunks.
69
+ - **Model aliasing.** `LOCALGATE_MODEL_ALIASES='{"fast":"phi4-mini"}'` — swap models without
70
+ touching clients.
71
+ - **Prompt caching** (opt-in). Identical prompts skip inference. Off by default because it
72
+ makes sampling deterministic.
73
+ - **Plugin system.** Backends are discovered via the `localgate.backends` entry point. The
74
+ built-ins use the same mechanism, so there's no privileged path a plugin can't take.
75
+ - **Production hardening.** Structured JSON logs with correlation IDs, Prometheus `/metrics`,
76
+ a liveness/readiness split, graceful shutdown, and fail-fast config validation — production
77
+ now refuses to start with the placeholder admin key.
78
+ - **`GET /admin/export`** — every row as JSON, so nobody is locked in.
79
+ - **Retrieval score logging and `MEMORY_MIN_SCORE`** — the floor that stops irrelevant chunks
80
+ from filling the context window with noise.
81
+ - **`key_prefix`** on keys, so a key can be identified in a listing.
82
+ - **`PATCH /admin/keys/{id}`** — change a rate limit without reissuing the key.
83
+ - **Documentation.** All seven docs were one-line stubs; they are now written, along with
84
+ three ADRs covering the decisions that shaped the codebase.
85
+
86
+ ### Changed
87
+
88
+ - `GET /v1/conversations/{id}` now returns an object (messages, summary, chunk count) rather
89
+ than a bare array.
90
+ - `POST /admin/keys` returns 201, not 200.
91
+ - Usage records now carry latency and a cached flag.
92
+
93
+ ### Security
94
+
95
+ - SECURITY.md claimed keys were bcrypted; they are SHA-256, which is the *correct* choice for
96
+ high-entropy random keys verified on every request. The document now says so and explains
97
+ why ([ADR 0003](docs/decisions/0003-sha256-for-api-keys.md)).
98
+
99
+ ## [0.2.0] — 2026-07-13
100
+
101
+ ### Added
102
+ - Auth, token accounting, RAG memory, dashboard UI, established-database config.
103
+ - Test suite with a deterministic `FakeBackend`.
104
+
105
+ ## [0.1.0] — 2026-07-13
106
+
107
+ ### Added
108
+ - Initial scaffolding: FastAPI app, Ollama backend, `/v1/chat/completions`, config, CI.
@@ -0,0 +1,6 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ This project adopts the Contributor Covenant. Be respectful, assume good faith,
4
+ and report unacceptable behavior to the maintainers.
5
+
6
+ Full text: https://www.contributor-covenant.org/version/2/1/code_of_conduct/
@@ -0,0 +1,115 @@
1
+ # Contributing
2
+
3
+ Thanks for wanting to help. This is meant to be an easy project to contribute to — the
4
+ architecture is deliberately shaped so that the most common contributions (a new backend, a
5
+ new database) are one file each.
6
+
7
+ ## Setup
8
+
9
+ ```bash
10
+ git clone https://github.com/YOUR_USERNAME/localgate.git
11
+ cd localgate
12
+ uv sync --all-extras
13
+ uv run pre-commit install
14
+
15
+ make test # 160 tests, no live Ollama needed
16
+ make lint # ruff + mypy
17
+ ```
18
+
19
+ Tests run against a deterministic `FakeBackend` and an in-memory SQLite database, so the
20
+ suite is fast and needs nothing installed.
21
+
22
+ ## The codebase in one minute
23
+
24
+ ```
25
+ api/ routes — thin. Validate, delegate, respond. Never touch the database.
26
+ core/ business logic — auth, rate limiting, tokens, caching. No HTTP awareness.
27
+ backends/ one adapter per inference server.
28
+ memory/ chunking, embedding, retrieval, summarization.
29
+ db/ models + repositories. The only code that writes SQL.
30
+ middleware/ request id, access log, metrics.
31
+ ```
32
+
33
+ Read [docs/architecture.md](docs/architecture.md) before a substantial change, and the
34
+ [ADRs](docs/decisions/) for *why* things are the way they are. If you're about to change
35
+ something an ADR covers, that's fine — but say so in the PR, and update the ADR.
36
+
37
+ ## Adding a backend
38
+
39
+ This is the highest-value contribution and it's genuinely small. If the server speaks the
40
+ OpenAI API — most do — it's a subclass and a default port:
41
+
42
+ ```python
43
+ # src/localgate/backends/my_server.py
44
+ from localgate.backends.openai_compat import OpenAICompatBackend
45
+
46
+ class MyServerBackend(OpenAICompatBackend):
47
+ name = "my-server"
48
+ default_base_url = "http://localhost:9000"
49
+ ```
50
+
51
+ Register it in `pyproject.toml`:
52
+
53
+ ```toml
54
+ [project.entry-points."localgate.backends"]
55
+ my-server = "localgate.backends.my_server:MyServerBackend"
56
+ ```
57
+
58
+ Add a case to `tests/unit/test_backends.py::test_every_advertised_backend_can_actually_be_instantiated`.
59
+ That's it.
60
+
61
+ If the server *doesn't* speak OpenAI, implement `InferenceBackend` directly (see
62
+ `backends/base.py` — five methods) and translate in the adapter. Nothing above the backend
63
+ layer should ever learn that your server is different.
64
+
65
+ You can also ship a backend as **your own package** without touching this repo at all —
66
+ declare the same entry point in your `pyproject.toml`. See
67
+ [docs/architecture.md](docs/architecture.md#plugin-system).
68
+
69
+ ## Standards
70
+
71
+ - **Type hints** on every function signature. `mypy src/` must pass.
72
+ - **Comments explain *why*, never *what*.** The code already says what it does. A comment
73
+ earns its place by capturing the constraint, the tradeoff, or the bug that isn't visible
74
+ from reading the line. If it restates the code, delete it.
75
+ - **Tests for every change.** A bug fix without a test that fails before it is not a fix.
76
+ - **`ruff check` and `ruff format`** — pre-commit runs both.
77
+ - **No `print`.** Use the structlog logger (`core/logging.py`).
78
+
79
+ ## Tests
80
+
81
+ Write the test that would have caught the bug. The ones in here that earn their keep are:
82
+
83
+ - `test_every_route_is_guarded` — auth is per-route, so a new route that forgets its
84
+ dependency would be silently public. This catches that.
85
+ - `test_pre_migrations_database_is_adopted_without_losing_data` — the upgrade path for
86
+ existing users, which is exactly the thing you can't test by hand twice.
87
+ - `test_streaming_still_records_usage_after_the_body_is_sent` — works in dev, breaks in
88
+ prod, if you get the session lifecycle wrong.
89
+
90
+ Aim for that. Not coverage of lines, coverage of *failure modes*.
91
+
92
+ ## Pull requests
93
+
94
+ 1. Branch from `main`: `git checkout -b feat/my-thing`
95
+ 2. Make the change, with tests.
96
+ 3. `make lint && make test`
97
+ 4. Update `CHANGELOG.md` under `[Unreleased]`.
98
+ 5. Open the PR and fill in the template.
99
+
100
+ Small PRs get reviewed fast. A 2000-line PR touching six subsystems will sit.
101
+
102
+ ## Good first issues
103
+
104
+ Look for the `good-first-issue` label. Reliably useful things that are always open:
105
+
106
+ - A backend adapter for a server that isn't supported yet
107
+ - Better error messages — if something confused you, it will confuse the next person, and
108
+ the fix is a sentence
109
+ - Documentation gaps, especially in [getting-started](docs/getting-started.md)
110
+ - Anything in the [open issues](https://github.com/AnjalLLL/localgate/issues)
111
+
112
+ ## Code of Conduct
113
+
114
+ This project follows the [Code of Conduct](CODE_OF_CONDUCT.md). It applies everywhere the
115
+ project does.