librarian-code 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.
Files changed (51) hide show
  1. librarian_code-0.1.0/.gitignore +14 -0
  2. librarian_code-0.1.0/.mimocode/plans/1782009063038-gentle-garden.md +95 -0
  3. librarian_code-0.1.0/LICENSE.md +21 -0
  4. librarian_code-0.1.0/PKG-INFO +180 -0
  5. librarian_code-0.1.0/README.md +150 -0
  6. librarian_code-0.1.0/librarian/__init__.py +3 -0
  7. librarian_code-0.1.0/librarian/__main__.py +3 -0
  8. librarian_code-0.1.0/librarian/actions/__init__.py +0 -0
  9. librarian_code-0.1.0/librarian/actions/file_ops.py +47 -0
  10. librarian_code-0.1.0/librarian/actions/safety.py +29 -0
  11. librarian_code-0.1.0/librarian/actions/shell_ops.py +49 -0
  12. librarian_code-0.1.0/librarian/adapter/__init__.py +0 -0
  13. librarian_code-0.1.0/librarian/adapter/base.py +11 -0
  14. librarian_code-0.1.0/librarian/adapter/groq_adapter.py +40 -0
  15. librarian_code-0.1.0/librarian/adapter/openrouter_adapter.py +58 -0
  16. librarian_code-0.1.0/librarian/cli.py +26 -0
  17. librarian_code-0.1.0/librarian/commands/__init__.py +0 -0
  18. librarian_code-0.1.0/librarian/commands/ask.py +46 -0
  19. librarian_code-0.1.0/librarian/commands/do.py +232 -0
  20. librarian_code-0.1.0/librarian/commands/init.py +96 -0
  21. librarian_code-0.1.0/librarian/commands/status.py +71 -0
  22. librarian_code-0.1.0/librarian/commands/undo.py +85 -0
  23. librarian_code-0.1.0/librarian/commands/why.py +47 -0
  24. librarian_code-0.1.0/librarian/exceptions.py +22 -0
  25. librarian_code-0.1.0/librarian/memory/__init__.py +0 -0
  26. librarian_code-0.1.0/librarian/memory/capsule.py +94 -0
  27. librarian_code-0.1.0/librarian/memory/chunker.py +183 -0
  28. librarian_code-0.1.0/librarian/memory/decision_log.py +36 -0
  29. librarian_code-0.1.0/librarian/memory/indexer.py +96 -0
  30. librarian_code-0.1.0/librarian/memory/retriever.py +62 -0
  31. librarian_code-0.1.0/librarian/orchestrator/__init__.py +0 -0
  32. librarian_code-0.1.0/librarian/orchestrator/core.py +47 -0
  33. librarian_code-0.1.0/librarian/orchestrator/router.py +17 -0
  34. librarian_code-0.1.0/librarian/skills/__init__.py +0 -0
  35. librarian_code-0.1.0/librarian/skills/bundled/__init__.py +0 -0
  36. librarian_code-0.1.0/librarian/skills/bundled/api-design/conventions.md +93 -0
  37. librarian_code-0.1.0/librarian/skills/bundled/python/conventions.md +59 -0
  38. librarian_code-0.1.0/librarian/skills/bundled/react/conventions.md +83 -0
  39. librarian_code-0.1.0/librarian/skills/bundled/web-dev/conventions.md +54 -0
  40. librarian_code-0.1.0/librarian/skills/loader.py +109 -0
  41. librarian_code-0.1.0/librarian/utils/__init__.py +0 -0
  42. librarian_code-0.1.0/librarian/utils/config.py +15 -0
  43. librarian_code-0.1.0/librarian/utils/logger.py +32 -0
  44. librarian_code-0.1.0/librarian/utils/token_tracker.py +16 -0
  45. librarian_code-0.1.0/librarian/utils/ui.py +97 -0
  46. librarian_code-0.1.0/pyproject.toml +48 -0
  47. librarian_code-0.1.0/tests/__init__.py +0 -0
  48. librarian_code-0.1.0/tests/test_actions.py +56 -0
  49. librarian_code-0.1.0/tests/test_adapter.py +69 -0
  50. librarian_code-0.1.0/tests/test_commands.py +40 -0
  51. librarian_code-0.1.0/tests/test_memory.py +69 -0
@@ -0,0 +1,14 @@
1
+ .env
2
+ .librarian/
3
+ LIBRARIAN.md
4
+ __pycache__/
5
+ *.pyc
6
+ *.pyo
7
+ .venv/
8
+ venv/
9
+ dist/
10
+ build/
11
+ *.egg-info/
12
+ .mypy_cache/
13
+ .pytest_cache/
14
+ .ruff_cache/
@@ -0,0 +1,95 @@
1
+ # Plan: Publish to PyPI for `pip install -g librarian-code`
2
+
3
+ ## Goal
4
+ Make the package installable globally via `pip install -g librarian-code`.
5
+
6
+ ---
7
+
8
+ ## Phase 1: Fix Package Metadata
9
+
10
+ ### 1. Update `pyproject.toml`
11
+ Add missing fields: `readme`, `license`, `authors`, `classifiers`, `urls`, and build artifacts for bundled skills.
12
+
13
+ ### 2. Add `__version__` to `librarian/__init__.py`
14
+ ```python
15
+ __version__ = "0.1.0"
16
+ ```
17
+
18
+ ### 3. Fix `README.md`
19
+ Remove `.env.example` reference (file doesn't exist).
20
+
21
+ ---
22
+
23
+ ## Phase 2: Set Up PyPI Account
24
+
25
+ 1. Go to https://pypi.org/account/register/
26
+ 2. Create account (use a dedicated email)
27
+ 3. Verify email
28
+
29
+ ### Generate API Token
30
+ 1. Go to https://pypi.org/manage/account/token/
31
+ 2. Click "Add API token"
32
+ 3. Name: `librarian-code-publish`
33
+ 4. Scope: "Entire account" (or project-specific after first upload)
34
+ 5. Copy token — **it won't be shown again**
35
+
36
+ ### Store Token Locally
37
+ ```bash
38
+ pip install twine
39
+ # Create ~/.pypirc (Windows: %USERPROFILE%\.pypirc)
40
+ ```
41
+
42
+ Contents:
43
+ ```ini
44
+ [pypi]
45
+ username = __token__
46
+ password = pypi-YOUR_TOKEN_HERE
47
+ ```
48
+
49
+ ---
50
+
51
+ ## Phase 3: Build & Publish
52
+
53
+ ```bash
54
+ # 1. Install build tools
55
+ pip install build twine
56
+
57
+ # 2. Clean old builds
58
+ rm -rf dist/ build/ *.egg-info
59
+
60
+ # 3. Build
61
+ python -m build
62
+
63
+ # 4. Verify package contents
64
+ twine check dist/*
65
+
66
+ # 5. Upload to PyPI
67
+ twine upload dist/*
68
+ ```
69
+
70
+ ---
71
+
72
+ ## Phase 4: Verify
73
+
74
+ ```bash
75
+ # Test install from PyPI
76
+ pip install -g librarian-code
77
+
78
+ # Should work
79
+ librarian --version
80
+ librarian --help
81
+ ```
82
+
83
+ ---
84
+
85
+ ## Files to Modify
86
+ - `pyproject.toml`
87
+ - `librarian/__init__.py`
88
+ - `README.md`
89
+
90
+ ## Verification
91
+ 1. `python -m build` — produces `.whl` + `.tar.gz`
92
+ 2. `twine check dist/*` — passes
93
+ 3. `pip install dist/*.whl` — installs correctly
94
+ 4. `librarian --version` — prints `0.1.0`
95
+ 5. Bundled skills `.md` files included in wheel
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Neel Sorathiya
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,180 @@
1
+ Metadata-Version: 2.4
2
+ Name: librarian-code
3
+ Version: 0.1.0
4
+ Summary: A local-first CLI coding agent with persistent project memory
5
+ Project-URL: Homepage, https://github.com/Humble-Librarian/Librarian-Code
6
+ Project-URL: Repository, https://github.com/Humble-Librarian/Librarian-Code
7
+ Project-URL: Issues, https://github.com/Humble-Librarian/Librarian-Code/issues
8
+ Author: Neel Sorathiya
9
+ License-Expression: MIT
10
+ License-File: LICENSE.md
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Topic :: Software Development :: Code Generators
18
+ Classifier: Topic :: Software Development :: Libraries
19
+ Requires-Python: >=3.10
20
+ Requires-Dist: chromadb>=0.5.0
21
+ Requires-Dist: gitpython>=3.1.0
22
+ Requires-Dist: groq>=0.11.0
23
+ Requires-Dist: httpx>=0.27.0
24
+ Requires-Dist: pyfiglet>=1.0.0
25
+ Requires-Dist: python-dotenv>=1.0.0
26
+ Requires-Dist: rich>=13.0.0
27
+ Requires-Dist: sentence-transformers>=3.0.0
28
+ Requires-Dist: typer>=0.12.0
29
+ Description-Content-Type: text/markdown
30
+
31
+ # librarian
32
+
33
+ > a CLI coding agent that remembers your project
34
+
35
+ ## what it does differently
36
+
37
+ Librarian is a local-first CLI coding agent with persistent project memory. Unlike tools that call an LLM on every request regardless of confidence, Librarian builds a capsule-based memory of your project's decisions — remembering why edits were made, adjusting confidence over time, and routing between Groq and OpenRouter intelligently when rate limits hit.
38
+
39
+ Built without LangChain — pure Python, owned stack. Every file operation, every LLM call, every decision is transparent and logged.
40
+
41
+ ## install
42
+
43
+ ```bash
44
+ pip install -g librarian-code
45
+ ```
46
+
47
+ ## setup
48
+
49
+ You need at least one free API key:
50
+
51
+ | Provider | Get Key | Cost |
52
+ |----------|---------|------|
53
+ | **Groq** | https://console.groq.com | Free tier available |
54
+ | **OpenRouter** | https://openrouter.ai | Free models available |
55
+
56
+ Create `.env` in your project root:
57
+
58
+ ```bash
59
+ # option 1: groq (fast)
60
+ echo "GROQ_API_KEY=gsk_..." > .env
61
+
62
+ # option 2: openrouter
63
+ echo "OPENROUTER_API_KEY=sk-or-..." > .env
64
+
65
+ # or both (openrouter used as fallback)
66
+ ```
67
+
68
+ ## quick start
69
+
70
+ ```bash
71
+ librarian init # index your project
72
+ librarian ask "what does this project do?"
73
+ librarian do "add input validation to login()"
74
+ librarian why # see decision history
75
+ librarian undo # revert last action
76
+ librarian status # project overview
77
+ ```
78
+
79
+ ## commands
80
+
81
+ | command | what it does |
82
+ |---------|-------------|
83
+ | `librarian init` | indexes project files, generates LIBRARIAN.md conventions |
84
+ | `librarian ask` | asks a question about your codebase, returns answer with sources |
85
+ | `librarian do` | gives librarian a task, shows plan preview, executes with your approval |
86
+ | `librarian why` | shows last decisions with reasoning |
87
+ | `librarian undo` | reverts the last agent action |
88
+ | `librarian status` | shows project info, memory stats, token usage |
89
+
90
+ ## how memory works
91
+
92
+ Librarian uses a **capsule-based memory system**:
93
+
94
+ - Every action creates a **capsule** with a confidence score (starts at 0.5)
95
+ - When you approve an action: confidence × 1.15
96
+ - When you undo an action: confidence × 0.6
97
+ - Unused capsules decay: × 0.98 per day
98
+ - Capsules below 0.4 confidence are archived
99
+
100
+ This means Librarian learns from your feedback over time — actions you approve become more confident, actions you undo become less likely to be repeated.
101
+
102
+ ## skills
103
+
104
+ Librarian auto-detects your project type and loads relevant conventions:
105
+
106
+ - **python**: pyproject.toml, setup.py, requirements.txt
107
+ - **react**: next.config.*, .tsx/.jsx files
108
+ - **web-dev**: .html files, CSS/SCSS
109
+ - **api-design**: routes.py, models.py, schemas.py
110
+
111
+ Skills provide domain-specific best practices that are injected into the LLM context for more relevant suggestions.
112
+
113
+ ## architecture
114
+
115
+ ```
116
+ librarian/
117
+ ├── adapter/ # LLM adapters (Groq primary, OpenRouter fallback)
118
+ │ ├── base.py # abstract adapter interface
119
+ │ ├── groq_adapter.py
120
+ │ └── openrouter_adapter.py
121
+ ├── orchestrator/ # routing and system prompt building
122
+ │ ├── router.py # Groq → OpenRouter fallback
123
+ │ └── core.py # prompt construction
124
+ ├── memory/ # persistent project memory
125
+ │ ├── chunker.py # AST-based code splitting
126
+ │ ├── indexer.py # ChromaDB + sentence-transformers
127
+ │ ├── retriever.py # semantic search (cached model)
128
+ │ ├── capsule.py # decision memory with confidence
129
+ │ └── decision_log.py # append-only action log
130
+ ├── skills/ # auto-detected project conventions
131
+ │ ├── loader.py # project type detection with caching
132
+ │ └── bundled/ # skill convention files
133
+ ├── actions/ # file and shell operations
134
+ │ ├── file_ops.py # read, write, edit files
135
+ │ ├── shell_ops.py # git and shell commands (shell=False)
136
+ │ └── safety.py # risk classification
137
+ ├── commands/ # CLI commands
138
+ │ ├── init.py
139
+ │ ├── ask.py
140
+ │ ├── do.py
141
+ │ ├── why.py
142
+ │ ├── undo.py
143
+ │ └── status.py
144
+ ├── utils/ # shared utilities
145
+ │ ├── config.py # env var loading
146
+ │ ├── ui.py # Terminal Luxury output
147
+ │ ├── logger.py # structured logging
148
+ │ └── token_tracker.py
149
+ ├── cli.py # typer entry point
150
+ └── exceptions.py # custom exception types
151
+ ```
152
+
153
+ ## providers
154
+
155
+ - **Groq** (primary): `llama-3.3-70b-versatile`, fast inference
156
+ - **OpenRouter** (fallback): `qwen/qwen3-coder:free`, automatic on rate limit
157
+
158
+ ## security
159
+
160
+ - Shell commands use `shell=False` with argument lists to prevent injection
161
+ - File operations use proper context managers to prevent handle leaks
162
+ - API responses validated before access
163
+ - LLM-generated delete operations require confirmation
164
+
165
+ ## performance
166
+
167
+ - SentenceTransformer model cached as singleton (~2-3s saved per invocation)
168
+ - ChromaDB client reused across calls
169
+ - Project type detection cached with `@lru_cache`
170
+ - Heavy dependencies lazy-loaded at function call time
171
+
172
+ ## testing
173
+
174
+ ```bash
175
+ python -m pytest tests/ -v
176
+ ```
177
+
178
+ ## license
179
+
180
+ MIT
@@ -0,0 +1,150 @@
1
+ # librarian
2
+
3
+ > a CLI coding agent that remembers your project
4
+
5
+ ## what it does differently
6
+
7
+ Librarian is a local-first CLI coding agent with persistent project memory. Unlike tools that call an LLM on every request regardless of confidence, Librarian builds a capsule-based memory of your project's decisions — remembering why edits were made, adjusting confidence over time, and routing between Groq and OpenRouter intelligently when rate limits hit.
8
+
9
+ Built without LangChain — pure Python, owned stack. Every file operation, every LLM call, every decision is transparent and logged.
10
+
11
+ ## install
12
+
13
+ ```bash
14
+ pip install -g librarian-code
15
+ ```
16
+
17
+ ## setup
18
+
19
+ You need at least one free API key:
20
+
21
+ | Provider | Get Key | Cost |
22
+ |----------|---------|------|
23
+ | **Groq** | https://console.groq.com | Free tier available |
24
+ | **OpenRouter** | https://openrouter.ai | Free models available |
25
+
26
+ Create `.env` in your project root:
27
+
28
+ ```bash
29
+ # option 1: groq (fast)
30
+ echo "GROQ_API_KEY=gsk_..." > .env
31
+
32
+ # option 2: openrouter
33
+ echo "OPENROUTER_API_KEY=sk-or-..." > .env
34
+
35
+ # or both (openrouter used as fallback)
36
+ ```
37
+
38
+ ## quick start
39
+
40
+ ```bash
41
+ librarian init # index your project
42
+ librarian ask "what does this project do?"
43
+ librarian do "add input validation to login()"
44
+ librarian why # see decision history
45
+ librarian undo # revert last action
46
+ librarian status # project overview
47
+ ```
48
+
49
+ ## commands
50
+
51
+ | command | what it does |
52
+ |---------|-------------|
53
+ | `librarian init` | indexes project files, generates LIBRARIAN.md conventions |
54
+ | `librarian ask` | asks a question about your codebase, returns answer with sources |
55
+ | `librarian do` | gives librarian a task, shows plan preview, executes with your approval |
56
+ | `librarian why` | shows last decisions with reasoning |
57
+ | `librarian undo` | reverts the last agent action |
58
+ | `librarian status` | shows project info, memory stats, token usage |
59
+
60
+ ## how memory works
61
+
62
+ Librarian uses a **capsule-based memory system**:
63
+
64
+ - Every action creates a **capsule** with a confidence score (starts at 0.5)
65
+ - When you approve an action: confidence × 1.15
66
+ - When you undo an action: confidence × 0.6
67
+ - Unused capsules decay: × 0.98 per day
68
+ - Capsules below 0.4 confidence are archived
69
+
70
+ This means Librarian learns from your feedback over time — actions you approve become more confident, actions you undo become less likely to be repeated.
71
+
72
+ ## skills
73
+
74
+ Librarian auto-detects your project type and loads relevant conventions:
75
+
76
+ - **python**: pyproject.toml, setup.py, requirements.txt
77
+ - **react**: next.config.*, .tsx/.jsx files
78
+ - **web-dev**: .html files, CSS/SCSS
79
+ - **api-design**: routes.py, models.py, schemas.py
80
+
81
+ Skills provide domain-specific best practices that are injected into the LLM context for more relevant suggestions.
82
+
83
+ ## architecture
84
+
85
+ ```
86
+ librarian/
87
+ ├── adapter/ # LLM adapters (Groq primary, OpenRouter fallback)
88
+ │ ├── base.py # abstract adapter interface
89
+ │ ├── groq_adapter.py
90
+ │ └── openrouter_adapter.py
91
+ ├── orchestrator/ # routing and system prompt building
92
+ │ ├── router.py # Groq → OpenRouter fallback
93
+ │ └── core.py # prompt construction
94
+ ├── memory/ # persistent project memory
95
+ │ ├── chunker.py # AST-based code splitting
96
+ │ ├── indexer.py # ChromaDB + sentence-transformers
97
+ │ ├── retriever.py # semantic search (cached model)
98
+ │ ├── capsule.py # decision memory with confidence
99
+ │ └── decision_log.py # append-only action log
100
+ ├── skills/ # auto-detected project conventions
101
+ │ ├── loader.py # project type detection with caching
102
+ │ └── bundled/ # skill convention files
103
+ ├── actions/ # file and shell operations
104
+ │ ├── file_ops.py # read, write, edit files
105
+ │ ├── shell_ops.py # git and shell commands (shell=False)
106
+ │ └── safety.py # risk classification
107
+ ├── commands/ # CLI commands
108
+ │ ├── init.py
109
+ │ ├── ask.py
110
+ │ ├── do.py
111
+ │ ├── why.py
112
+ │ ├── undo.py
113
+ │ └── status.py
114
+ ├── utils/ # shared utilities
115
+ │ ├── config.py # env var loading
116
+ │ ├── ui.py # Terminal Luxury output
117
+ │ ├── logger.py # structured logging
118
+ │ └── token_tracker.py
119
+ ├── cli.py # typer entry point
120
+ └── exceptions.py # custom exception types
121
+ ```
122
+
123
+ ## providers
124
+
125
+ - **Groq** (primary): `llama-3.3-70b-versatile`, fast inference
126
+ - **OpenRouter** (fallback): `qwen/qwen3-coder:free`, automatic on rate limit
127
+
128
+ ## security
129
+
130
+ - Shell commands use `shell=False` with argument lists to prevent injection
131
+ - File operations use proper context managers to prevent handle leaks
132
+ - API responses validated before access
133
+ - LLM-generated delete operations require confirmation
134
+
135
+ ## performance
136
+
137
+ - SentenceTransformer model cached as singleton (~2-3s saved per invocation)
138
+ - ChromaDB client reused across calls
139
+ - Project type detection cached with `@lru_cache`
140
+ - Heavy dependencies lazy-loaded at function call time
141
+
142
+ ## testing
143
+
144
+ ```bash
145
+ python -m pytest tests/ -v
146
+ ```
147
+
148
+ ## license
149
+
150
+ MIT
@@ -0,0 +1,3 @@
1
+ """Librarian — a CLI coding agent with persistent project memory."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,3 @@
1
+ from librarian.cli import app
2
+
3
+ app()
File without changes
@@ -0,0 +1,47 @@
1
+ import os
2
+ from pathlib import Path
3
+
4
+ IGNORED_PATHS = [".git", "node_modules", "__pycache__", ".librarian", "venv", ".env"]
5
+
6
+
7
+ def read_file(path: str) -> str:
8
+ try:
9
+ return Path(path).read_text(encoding="utf-8")
10
+ except UnicodeDecodeError:
11
+ return Path(path).read_text(encoding="latin-1")
12
+
13
+
14
+ def write_file(path: str, content: str) -> bool:
15
+ Path(path).parent.mkdir(parents=True, exist_ok=True)
16
+ Path(path).write_text(content, encoding="utf-8")
17
+ return True
18
+
19
+
20
+ def edit_file(path: str, old: str, new: str) -> bool:
21
+ content = read_file(path)
22
+ count = content.count(old)
23
+ if count == 0:
24
+ raise ValueError(f"String not found in {path}")
25
+ if count > 1:
26
+ raise ValueError(f"Ambiguous edit: string appears {count} times in {path}")
27
+ content = content.replace(old, new, 1)
28
+ write_file(path, content)
29
+ return True
30
+
31
+
32
+ def list_files(directory: str, extensions: list[str] = None) -> list[str]:
33
+ ignored = get_ignored_paths()
34
+ results = []
35
+ for root, dirs, files in os.walk(directory):
36
+ dirs[:] = [d for d in dirs if d not in ignored]
37
+ for f in files:
38
+ if extensions:
39
+ ext = os.path.splitext(f)[1].lower()
40
+ if ext not in extensions:
41
+ continue
42
+ results.append(os.path.join(root, f))
43
+ return sorted(results)
44
+
45
+
46
+ def get_ignored_paths() -> list[str]:
47
+ return IGNORED_PATHS
@@ -0,0 +1,29 @@
1
+ from enum import Enum
2
+
3
+
4
+ class RiskLevel(Enum):
5
+ SAFE = "safe"
6
+ CONFIRM = "confirm"
7
+
8
+
9
+ CONFIRM_ACTIONS = [
10
+ "git push",
11
+ "git reset --hard",
12
+ "rm ",
13
+ "delete",
14
+ "drop table",
15
+ "truncate",
16
+ ]
17
+
18
+
19
+ def classify_action(action: str) -> RiskLevel:
20
+ action_lower = action.lower()
21
+ for pattern in CONFIRM_ACTIONS:
22
+ if pattern in action_lower:
23
+ return RiskLevel.CONFIRM
24
+ return RiskLevel.SAFE
25
+
26
+
27
+ def request_confirm(action: str) -> bool:
28
+ from rich.prompt import Confirm
29
+ return Confirm.ask(f"[bold #F59E0B]confirm:[/bold #F59E0B] {action}")
@@ -0,0 +1,49 @@
1
+ import subprocess
2
+ import shlex
3
+ from librarian.actions.safety import classify_action, RiskLevel, request_confirm
4
+
5
+
6
+ def run_command(cmd: str, cwd: str = None) -> tuple[int, str, str]:
7
+ if isinstance(cmd, str):
8
+ args = shlex.split(cmd)
9
+ else:
10
+ args = cmd
11
+ result = subprocess.run(
12
+ args, shell=False, cwd=cwd,
13
+ capture_output=True, text=True,
14
+ )
15
+ return result.returncode, result.stdout, result.stderr
16
+
17
+
18
+ def git_stage(files: list[str]) -> bool:
19
+ args = ["git", "add"] + files
20
+ code, _, err = run_command(args)
21
+ if code != 0:
22
+ raise RuntimeError(f"git add failed: {err}")
23
+ return True
24
+
25
+
26
+ def git_commit(message: str) -> bool:
27
+ args = ["git", "commit", "-m", message]
28
+ code, _, err = run_command(args)
29
+ if code != 0:
30
+ raise RuntimeError(f"git commit failed: {err}")
31
+ return True
32
+
33
+
34
+ def git_push() -> bool:
35
+ risk = classify_action("git push")
36
+ if risk == RiskLevel.CONFIRM:
37
+ if not request_confirm("push to remote?"):
38
+ return False
39
+ code, _, err = run_command("git push")
40
+ if code != 0:
41
+ raise RuntimeError(f"git push failed: {err}")
42
+ return True
43
+
44
+
45
+ def git_status() -> str:
46
+ code, out, err = run_command("git status --short")
47
+ if code != 0:
48
+ return err
49
+ return out.strip()
File without changes
@@ -0,0 +1,11 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+
4
+ class LLMAdapter(ABC):
5
+ @abstractmethod
6
+ def complete(self, system: str, prompt: str) -> str:
7
+ pass
8
+
9
+ @abstractmethod
10
+ def is_available(self) -> bool:
11
+ pass
@@ -0,0 +1,40 @@
1
+ from groq import Groq, RateLimitError as GroqRateLimitError, APIConnectionError
2
+ from librarian.adapter.base import LLMAdapter
3
+ from librarian.exceptions import RateLimitError, ProviderUnavailableError
4
+ from librarian.utils.config import GROQ_API_KEY
5
+
6
+
7
+ class GroqAdapter(LLMAdapter):
8
+ def __init__(self):
9
+ self.client = Groq(api_key=GROQ_API_KEY) if GROQ_API_KEY else None
10
+ self.model = "llama-3.3-70b-versatile"
11
+ self.tokens_used = 0
12
+
13
+ def complete(self, system: str, prompt: str) -> str:
14
+ if not self.client:
15
+ raise ProviderUnavailableError("GROQ_API_KEY not set")
16
+ try:
17
+ response = self.client.chat.completions.create(
18
+ model=self.model,
19
+ messages=[
20
+ {"role": "system", "content": system},
21
+ {"role": "user", "content": prompt},
22
+ ],
23
+ temperature=0.2,
24
+ max_tokens=4096,
25
+ )
26
+ self.tokens_used += response.usage.total_tokens
27
+ return response.choices[0].message.content
28
+ except GroqRateLimitError:
29
+ raise RateLimitError("Groq rate limit exceeded")
30
+ except APIConnectionError:
31
+ raise ProviderUnavailableError("Cannot connect to Groq")
32
+
33
+ def is_available(self) -> bool:
34
+ if not self.client:
35
+ return False
36
+ try:
37
+ self.client.models.list()
38
+ return True
39
+ except Exception:
40
+ return False