rapt0r-cli 1.0.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.
- rapt0r_cli-1.0.0/LICENSE +21 -0
- rapt0r_cli-1.0.0/PKG-INFO +172 -0
- rapt0r_cli-1.0.0/README.md +132 -0
- rapt0r_cli-1.0.0/pyproject.toml +77 -0
- rapt0r_cli-1.0.0/rapt0r/__init__.py +1 -0
- rapt0r_cli-1.0.0/rapt0r/__main__.py +4 -0
- rapt0r_cli-1.0.0/rapt0r/core/__init__.py +0 -0
- rapt0r_cli-1.0.0/rapt0r/core/builder.py +20 -0
- rapt0r_cli-1.0.0/rapt0r/core/context_vars.py +97 -0
- rapt0r_cli-1.0.0/rapt0r/core/llm.py +123 -0
- rapt0r_cli-1.0.0/rapt0r/core/loader.py +137 -0
- rapt0r_cli-1.0.0/rapt0r/core/output.py +62 -0
- rapt0r_cli-1.0.0/rapt0r/core/utils.py +54 -0
- rapt0r_cli-1.0.0/rapt0r/main.py +471 -0
- rapt0r_cli-1.0.0/rapt0r/templates/coding/api-design.md +42 -0
- rapt0r_cli-1.0.0/rapt0r/templates/coding/code-review.md +41 -0
- rapt0r_cli-1.0.0/rapt0r/templates/coding/commit.md +59 -0
- rapt0r_cli-1.0.0/rapt0r/templates/coding/debug.md +35 -0
- rapt0r_cli-1.0.0/rapt0r/templates/coding/explain.md +36 -0
- rapt0r_cli-1.0.0/rapt0r/templates/coding/new-feature.md +33 -0
- rapt0r_cli-1.0.0/rapt0r/templates/coding/performance.md +35 -0
- rapt0r_cli-1.0.0/rapt0r/templates/coding/refactor.md +36 -0
- rapt0r_cli-1.0.0/rapt0r/templates/coding/security.md +39 -0
- rapt0r_cli-1.0.0/rapt0r/templates/coding/tests.md +40 -0
- rapt0r_cli-1.0.0/rapt0r/templates/content/brainstorm.md +39 -0
- rapt0r_cli-1.0.0/rapt0r/templates/content/documentation.md +23 -0
- rapt0r_cli-1.0.0/rapt0r/ui.py +115 -0
- rapt0r_cli-1.0.0/rapt0r_cli.egg-info/PKG-INFO +172 -0
- rapt0r_cli-1.0.0/rapt0r_cli.egg-info/SOURCES.txt +32 -0
- rapt0r_cli-1.0.0/rapt0r_cli.egg-info/dependency_links.txt +1 -0
- rapt0r_cli-1.0.0/rapt0r_cli.egg-info/entry_points.txt +2 -0
- rapt0r_cli-1.0.0/rapt0r_cli.egg-info/requires.txt +10 -0
- rapt0r_cli-1.0.0/rapt0r_cli.egg-info/top_level.txt +1 -0
- rapt0r_cli-1.0.0/setup.cfg +4 -0
rapt0r_cli-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 i73
|
|
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,172 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: rapt0r-cli
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Offline terminal prompt builder — answer a few questions, get a perfect AI prompt in your clipboard. Zero tokens spent on explanations.
|
|
5
|
+
Author: i73
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/i73i73i73i73/RAPT0R
|
|
8
|
+
Project-URL: Repository, https://github.com/i73i73i73i73/RAPT0R
|
|
9
|
+
Project-URL: Issues, https://github.com/i73i73i73i73/RAPT0R/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/i73i73i73i73/RAPT0R/blob/main/CHANGELOG.md
|
|
11
|
+
Keywords: prompt,ai,cli,terminal,tui,prompt-engineering,llm,claude,openai,developer-tools
|
|
12
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Topic :: Software Development
|
|
25
|
+
Classifier: Topic :: Terminals
|
|
26
|
+
Classifier: Topic :: Utilities
|
|
27
|
+
Requires-Python: >=3.9
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
License-File: LICENSE
|
|
30
|
+
Requires-Dist: InquirerPy<0.4,>=0.3.4
|
|
31
|
+
Requires-Dist: rich
|
|
32
|
+
Requires-Dist: pyperclip
|
|
33
|
+
Requires-Dist: PyYAML
|
|
34
|
+
Requires-Dist: Jinja2>=3.0
|
|
35
|
+
Provides-Extra: dev
|
|
36
|
+
Requires-Dist: pytest; extra == "dev"
|
|
37
|
+
Requires-Dist: ruff; extra == "dev"
|
|
38
|
+
Requires-Dist: build; extra == "dev"
|
|
39
|
+
Dynamic: license-file
|
|
40
|
+
|
|
41
|
+
<div align="center">
|
|
42
|
+
|
|
43
|
+
<img src="docs/rapt0r.png" alt="RAPT0R" width="100%"/>
|
|
44
|
+
|
|
45
|
+
<br>
|
|
46
|
+
|
|
47
|
+
**build fast. ship faster.**<br>
|
|
48
|
+
*prompt builder by i73*
|
|
49
|
+
|
|
50
|
+
[](https://python.org)
|
|
51
|
+
[](https://github.com/i73i73i73i73/RAPT0R)
|
|
52
|
+
[](LICENSE)
|
|
53
|
+
|
|
54
|
+

|
|
55
|
+
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
> **Don't spend tokens explaining yourself — spend them on results.**
|
|
61
|
+
|
|
62
|
+
**RAPT0R** is a terminal prompt builder. Answer a few questions, get a battle-tested AI prompt in your clipboard, or execute it directly against an LLM. Offline by design — your context window goes entirely to the actual work.
|
|
63
|
+
|
|
64
|
+
## ✨ Features
|
|
65
|
+
|
|
66
|
+
- 🔌 **Offline by default** — build complex prompts with zero API calls, or optionally stream execution directly to OpenAI/Anthropic.
|
|
67
|
+
- ⚡️ **Fast** — answer 3-5 questions, the finished prompt is already in your clipboard.
|
|
68
|
+
- 📁 **Project context** — drop a `.rapt0r` file in your repo and your stack/rules/architecture get injected into every prompt automatically.
|
|
69
|
+
- 🔎 **Instant search** — press `f` anywhere to fuzzy-search templates across all categories.
|
|
70
|
+
- 🌳 **Branching** — smart templates ask follow-up questions based on your answers.
|
|
71
|
+
- 🛠 **Built-in manager** — create, edit, and delete templates right from the menu.
|
|
72
|
+
- 🎨 **Polished TUI** — custom colors, number hotkeys, icons, built on `InquirerPy` and `rich`.
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## 🚀 Install
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
pip install rapt0r-cli
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Run it:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
rapt0r
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
<details>
|
|
89
|
+
<summary>Or run from source</summary>
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
git clone https://github.com/i73i73i73i73/RAPT0R.git
|
|
93
|
+
cd RAPT0R
|
|
94
|
+
pip3 install -e .
|
|
95
|
+
rapt0r # or: python3 main.py
|
|
96
|
+
```
|
|
97
|
+
</details>
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## 📁 Project context (`.rapt0r`)
|
|
102
|
+
|
|
103
|
+
Stop re-explaining your project to the AI in every prompt. Describe it once — RAPT0R appends it to every prompt it builds.
|
|
104
|
+
|
|
105
|
+
Create a `.rapt0r` file in your project root (any plain text or markdown):
|
|
106
|
+
|
|
107
|
+
```markdown
|
|
108
|
+
Stack: FastAPI + React + PostgreSQL
|
|
109
|
+
Style: type hints everywhere, no classes where functions do
|
|
110
|
+
Architecture: monorepo, backend in /api, frontend in /web
|
|
111
|
+
Rules: never touch migrations by hand, tests are pytest
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Run `rapt0r` from anywhere inside the project — it finds the file by walking up the directory tree and adds a `## Project context` section to the generated prompt.
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## ⌨️ Hotkeys
|
|
119
|
+
|
|
120
|
+
| Key | Action |
|
|
121
|
+
|-----|--------|
|
|
122
|
+
| `1-9` | Jump to item |
|
|
123
|
+
| `f` | Fuzzy search across all templates |
|
|
124
|
+
| `0` | Exit / back |
|
|
125
|
+
| `Ctrl+C` | Cancel current flow |
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## 📝 Writing your own templates
|
|
130
|
+
|
|
131
|
+
Templates are plain Markdown with **YAML frontmatter**. Press `Create template` in the menu, or drop a `.md` file into a category folder:
|
|
132
|
+
|
|
133
|
+
```markdown
|
|
134
|
+
---
|
|
135
|
+
name: "✨ My awesome prompt"
|
|
136
|
+
questions:
|
|
137
|
+
role: "What role should the AI play?"
|
|
138
|
+
task: "Describe the task in one sentence:"
|
|
139
|
+
branch:
|
|
140
|
+
question: "Add a constraints section?"
|
|
141
|
+
y:
|
|
142
|
+
questions:
|
|
143
|
+
limits: "List the constraints, comma-separated:"
|
|
144
|
+
body: "constraints_body"
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
You act as: {{role}}.
|
|
148
|
+
Your task: {{task}}.
|
|
149
|
+
|
|
150
|
+
{{constraints_body}}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Syntax
|
|
154
|
+
|
|
155
|
+
- `name` — display name in the menu.
|
|
156
|
+
- `questions` — `key: "Question?"` pairs; keys become `{{variables}}` in the body. Add `choices:` for a pick-list.
|
|
157
|
+
- `branch` *(optional)* — yes/no fork that asks extra questions and switches the body.
|
|
158
|
+
- `{{variable}}` — replaced with your answers.
|
|
159
|
+
|
|
160
|
+
Where templates live: the repo's `rapt0r/templates/` when running from source, `~/.rapt0r/templates/` when installed via pip (seeded automatically on first run). Generated prompts are saved to `~/.rapt0r/prompts/` (or `prompts/temp/` in a source checkout).
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## 🧠 Philosophy
|
|
165
|
+
|
|
166
|
+
> **Precise context = fewer tokens = better results.**
|
|
167
|
+
|
|
168
|
+
Every API call where you explain your project from scratch costs money and context window. RAPT0R forces the discipline of giving the AI exactly the inputs it needs — offline, for free, in seconds.
|
|
169
|
+
|
|
170
|
+
<div align="center">
|
|
171
|
+
<sub>built with ❤️ for AI engineering</sub>
|
|
172
|
+
</div>
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
<img src="docs/rapt0r.png" alt="RAPT0R" width="100%"/>
|
|
4
|
+
|
|
5
|
+
<br>
|
|
6
|
+
|
|
7
|
+
**build fast. ship faster.**<br>
|
|
8
|
+
*prompt builder by i73*
|
|
9
|
+
|
|
10
|
+
[](https://python.org)
|
|
11
|
+
[](https://github.com/i73i73i73i73/RAPT0R)
|
|
12
|
+
[](LICENSE)
|
|
13
|
+
|
|
14
|
+

|
|
15
|
+
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
> **Don't spend tokens explaining yourself — spend them on results.**
|
|
21
|
+
|
|
22
|
+
**RAPT0R** is a terminal prompt builder. Answer a few questions, get a battle-tested AI prompt in your clipboard, or execute it directly against an LLM. Offline by design — your context window goes entirely to the actual work.
|
|
23
|
+
|
|
24
|
+
## ✨ Features
|
|
25
|
+
|
|
26
|
+
- 🔌 **Offline by default** — build complex prompts with zero API calls, or optionally stream execution directly to OpenAI/Anthropic.
|
|
27
|
+
- ⚡️ **Fast** — answer 3-5 questions, the finished prompt is already in your clipboard.
|
|
28
|
+
- 📁 **Project context** — drop a `.rapt0r` file in your repo and your stack/rules/architecture get injected into every prompt automatically.
|
|
29
|
+
- 🔎 **Instant search** — press `f` anywhere to fuzzy-search templates across all categories.
|
|
30
|
+
- 🌳 **Branching** — smart templates ask follow-up questions based on your answers.
|
|
31
|
+
- 🛠 **Built-in manager** — create, edit, and delete templates right from the menu.
|
|
32
|
+
- 🎨 **Polished TUI** — custom colors, number hotkeys, icons, built on `InquirerPy` and `rich`.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## 🚀 Install
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install rapt0r-cli
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Run it:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
rapt0r
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
<details>
|
|
49
|
+
<summary>Or run from source</summary>
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
git clone https://github.com/i73i73i73i73/RAPT0R.git
|
|
53
|
+
cd RAPT0R
|
|
54
|
+
pip3 install -e .
|
|
55
|
+
rapt0r # or: python3 main.py
|
|
56
|
+
```
|
|
57
|
+
</details>
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## 📁 Project context (`.rapt0r`)
|
|
62
|
+
|
|
63
|
+
Stop re-explaining your project to the AI in every prompt. Describe it once — RAPT0R appends it to every prompt it builds.
|
|
64
|
+
|
|
65
|
+
Create a `.rapt0r` file in your project root (any plain text or markdown):
|
|
66
|
+
|
|
67
|
+
```markdown
|
|
68
|
+
Stack: FastAPI + React + PostgreSQL
|
|
69
|
+
Style: type hints everywhere, no classes where functions do
|
|
70
|
+
Architecture: monorepo, backend in /api, frontend in /web
|
|
71
|
+
Rules: never touch migrations by hand, tests are pytest
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Run `rapt0r` from anywhere inside the project — it finds the file by walking up the directory tree and adds a `## Project context` section to the generated prompt.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## ⌨️ Hotkeys
|
|
79
|
+
|
|
80
|
+
| Key | Action |
|
|
81
|
+
|-----|--------|
|
|
82
|
+
| `1-9` | Jump to item |
|
|
83
|
+
| `f` | Fuzzy search across all templates |
|
|
84
|
+
| `0` | Exit / back |
|
|
85
|
+
| `Ctrl+C` | Cancel current flow |
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## 📝 Writing your own templates
|
|
90
|
+
|
|
91
|
+
Templates are plain Markdown with **YAML frontmatter**. Press `Create template` in the menu, or drop a `.md` file into a category folder:
|
|
92
|
+
|
|
93
|
+
```markdown
|
|
94
|
+
---
|
|
95
|
+
name: "✨ My awesome prompt"
|
|
96
|
+
questions:
|
|
97
|
+
role: "What role should the AI play?"
|
|
98
|
+
task: "Describe the task in one sentence:"
|
|
99
|
+
branch:
|
|
100
|
+
question: "Add a constraints section?"
|
|
101
|
+
y:
|
|
102
|
+
questions:
|
|
103
|
+
limits: "List the constraints, comma-separated:"
|
|
104
|
+
body: "constraints_body"
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
You act as: {{role}}.
|
|
108
|
+
Your task: {{task}}.
|
|
109
|
+
|
|
110
|
+
{{constraints_body}}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Syntax
|
|
114
|
+
|
|
115
|
+
- `name` — display name in the menu.
|
|
116
|
+
- `questions` — `key: "Question?"` pairs; keys become `{{variables}}` in the body. Add `choices:` for a pick-list.
|
|
117
|
+
- `branch` *(optional)* — yes/no fork that asks extra questions and switches the body.
|
|
118
|
+
- `{{variable}}` — replaced with your answers.
|
|
119
|
+
|
|
120
|
+
Where templates live: the repo's `rapt0r/templates/` when running from source, `~/.rapt0r/templates/` when installed via pip (seeded automatically on first run). Generated prompts are saved to `~/.rapt0r/prompts/` (or `prompts/temp/` in a source checkout).
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## 🧠 Philosophy
|
|
125
|
+
|
|
126
|
+
> **Precise context = fewer tokens = better results.**
|
|
127
|
+
|
|
128
|
+
Every API call where you explain your project from scratch costs money and context window. RAPT0R forces the discipline of giving the AI exactly the inputs it needs — offline, for free, in seconds.
|
|
129
|
+
|
|
130
|
+
<div align="center">
|
|
131
|
+
<sub>built with ❤️ for AI engineering</sub>
|
|
132
|
+
</div>
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "rapt0r-cli"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Offline terminal prompt builder — answer a few questions, get a perfect AI prompt in your clipboard. Zero tokens spent on explanations."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "i73" }]
|
|
13
|
+
keywords = [
|
|
14
|
+
"prompt",
|
|
15
|
+
"ai",
|
|
16
|
+
"cli",
|
|
17
|
+
"terminal",
|
|
18
|
+
"tui",
|
|
19
|
+
"prompt-engineering",
|
|
20
|
+
"llm",
|
|
21
|
+
"claude",
|
|
22
|
+
"openai",
|
|
23
|
+
"developer-tools",
|
|
24
|
+
]
|
|
25
|
+
classifiers = [
|
|
26
|
+
"Development Status :: 5 - Production/Stable",
|
|
27
|
+
"Environment :: Console",
|
|
28
|
+
"Intended Audience :: Developers",
|
|
29
|
+
"License :: OSI Approved :: MIT License",
|
|
30
|
+
"Operating System :: OS Independent",
|
|
31
|
+
"Programming Language :: Python :: 3",
|
|
32
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
33
|
+
"Programming Language :: Python :: 3.9",
|
|
34
|
+
"Programming Language :: Python :: 3.10",
|
|
35
|
+
"Programming Language :: Python :: 3.11",
|
|
36
|
+
"Programming Language :: Python :: 3.12",
|
|
37
|
+
"Programming Language :: Python :: 3.13",
|
|
38
|
+
"Topic :: Software Development",
|
|
39
|
+
"Topic :: Terminals",
|
|
40
|
+
"Topic :: Utilities",
|
|
41
|
+
]
|
|
42
|
+
dependencies = [
|
|
43
|
+
"InquirerPy>=0.3.4,<0.4",
|
|
44
|
+
"rich",
|
|
45
|
+
"pyperclip",
|
|
46
|
+
"PyYAML",
|
|
47
|
+
"Jinja2>=3.0",
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
[project.optional-dependencies]
|
|
51
|
+
dev = ["pytest", "ruff", "build"]
|
|
52
|
+
|
|
53
|
+
[project.urls]
|
|
54
|
+
Homepage = "https://github.com/i73i73i73i73/RAPT0R"
|
|
55
|
+
Repository = "https://github.com/i73i73i73i73/RAPT0R"
|
|
56
|
+
Issues = "https://github.com/i73i73i73i73/RAPT0R/issues"
|
|
57
|
+
Changelog = "https://github.com/i73i73i73i73/RAPT0R/blob/main/CHANGELOG.md"
|
|
58
|
+
|
|
59
|
+
[project.scripts]
|
|
60
|
+
rapt0r = "rapt0r.main:main"
|
|
61
|
+
|
|
62
|
+
[tool.setuptools.packages.find]
|
|
63
|
+
include = ["rapt0r*"]
|
|
64
|
+
|
|
65
|
+
[tool.setuptools.package-data]
|
|
66
|
+
rapt0r = ["templates/*.md", "templates/**/*.md"]
|
|
67
|
+
|
|
68
|
+
[tool.pytest.ini_options]
|
|
69
|
+
testpaths = ["tests"]
|
|
70
|
+
|
|
71
|
+
[tool.ruff]
|
|
72
|
+
target-version = "py39"
|
|
73
|
+
line-length = 100
|
|
74
|
+
|
|
75
|
+
[tool.ruff.lint]
|
|
76
|
+
select = ["E", "F", "W"]
|
|
77
|
+
ignore = ["E501"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.0.0"
|
|
File without changes
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from jinja2 import Environment, BaseLoader, DebugUndefined
|
|
3
|
+
|
|
4
|
+
from rapt0r.core.utils import DEFAULT_BODY
|
|
5
|
+
from rapt0r.core.context_vars import extract_context_vars, resolve_context_vars
|
|
6
|
+
|
|
7
|
+
logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
_env = Environment(loader=BaseLoader(), undefined=DebugUndefined, keep_trailing_newline=True)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def build_prompt(bodies, answers, body_key=None):
|
|
13
|
+
key = body_key if body_key and body_key in bodies else DEFAULT_BODY
|
|
14
|
+
if key not in bodies:
|
|
15
|
+
logger.warning(f"Body section {key!r} not found, using first available")
|
|
16
|
+
key = next(iter(bodies))
|
|
17
|
+
vars_ = {k: v for k, v in answers.items() if not k.startswith("__")}
|
|
18
|
+
body, pending = extract_context_vars(bodies[key])
|
|
19
|
+
result = _env.from_string(body).render(**vars_)
|
|
20
|
+
return resolve_context_vars(result, pending)
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""Dynamic context variables: {{git_diff}}, {{tree}}, {{tree:path}}, {{file:path}}.
|
|
2
|
+
|
|
3
|
+
The colon forms are not valid Jinja2 syntax, and injected content may itself
|
|
4
|
+
contain ``{{``, so these tokens are swapped for opaque sentinels before the
|
|
5
|
+
Jinja2 render and resolved afterwards (see builder.build_prompt).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import re
|
|
10
|
+
import subprocess
|
|
11
|
+
|
|
12
|
+
MAX_BYTES = 30000
|
|
13
|
+
MAX_TREE_ENTRIES = 500
|
|
14
|
+
SKIP_DIRS = {"__pycache__", "node_modules", "venv", ".venv"}
|
|
15
|
+
|
|
16
|
+
CONTEXT_VAR_NAMES = {"git_diff", "tree", "file"}
|
|
17
|
+
|
|
18
|
+
_PATTERN = re.compile(r"\{\{\s*(git_diff|tree|file)(?::([^}]+))?\s*\}\}")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _truncate(text):
|
|
22
|
+
if len(text) > MAX_BYTES:
|
|
23
|
+
return text[:MAX_BYTES] + "\n... [truncated]"
|
|
24
|
+
return text
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _git_diff():
|
|
28
|
+
try:
|
|
29
|
+
out = subprocess.run(
|
|
30
|
+
["git", "diff", "HEAD"], capture_output=True, text=True, timeout=10
|
|
31
|
+
)
|
|
32
|
+
except (OSError, subprocess.TimeoutExpired):
|
|
33
|
+
return "[git diff unavailable]"
|
|
34
|
+
if out.returncode != 0:
|
|
35
|
+
return "[git diff unavailable: not a git repository?]"
|
|
36
|
+
return _truncate(out.stdout.strip()) or "[no uncommitted changes]"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _tree(root):
|
|
40
|
+
root = (root or ".").strip() or "."
|
|
41
|
+
if not os.path.isdir(root):
|
|
42
|
+
return f"[directory not found: {root}]"
|
|
43
|
+
lines = []
|
|
44
|
+
for dirpath, dirnames, filenames in os.walk(root):
|
|
45
|
+
dirnames[:] = sorted(
|
|
46
|
+
d for d in dirnames if not d.startswith(".") and d not in SKIP_DIRS
|
|
47
|
+
)
|
|
48
|
+
for f in sorted(filenames):
|
|
49
|
+
lines.append(os.path.relpath(os.path.join(dirpath, f), root))
|
|
50
|
+
if len(lines) >= MAX_TREE_ENTRIES:
|
|
51
|
+
lines.append("... [truncated]")
|
|
52
|
+
return "\n".join(lines)
|
|
53
|
+
return "\n".join(lines) or "[empty directory]"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _file(path):
|
|
57
|
+
path = (path or "").strip()
|
|
58
|
+
try:
|
|
59
|
+
with open(path, "r", encoding="utf-8", errors="replace") as f:
|
|
60
|
+
return _truncate(f.read())
|
|
61
|
+
except OSError as e:
|
|
62
|
+
return f"[could not read {path}: {e}]"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _resolve(kind, arg):
|
|
66
|
+
if kind == "git_diff":
|
|
67
|
+
return _git_diff()
|
|
68
|
+
if kind == "tree":
|
|
69
|
+
return _tree(arg)
|
|
70
|
+
if kind == "file":
|
|
71
|
+
return _file(arg)
|
|
72
|
+
return ""
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def extract_context_vars(text):
|
|
76
|
+
"""Replace context tokens with sentinels safe to pass through Jinja2."""
|
|
77
|
+
pending = {}
|
|
78
|
+
|
|
79
|
+
def repl(m):
|
|
80
|
+
token = f"\x00CTX{len(pending)}\x00"
|
|
81
|
+
pending[token] = (m.group(1), m.group(2))
|
|
82
|
+
return token
|
|
83
|
+
|
|
84
|
+
return _PATTERN.sub(repl, text), pending
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def resolve_context_vars(text, pending):
|
|
88
|
+
"""Substitute sentinels with their resolved content after the render."""
|
|
89
|
+
for token, (kind, arg) in pending.items():
|
|
90
|
+
text = text.replace(token, _resolve(kind, arg))
|
|
91
|
+
return text
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def expand_context_vars(text):
|
|
95
|
+
"""One-shot expansion (extract + resolve), used directly in tests."""
|
|
96
|
+
extracted, pending = extract_context_vars(text)
|
|
97
|
+
return resolve_context_vars(extracted, pending)
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""Stream a built prompt to an LLM over raw HTTP (no SDK dependencies).
|
|
2
|
+
|
|
3
|
+
Providers are detected from environment variables:
|
|
4
|
+
ANTHROPIC_API_KEY -> Anthropic Messages API
|
|
5
|
+
OPENAI_API_KEY -> OpenAI Chat Completions API
|
|
6
|
+
|
|
7
|
+
Model overrides: RAPT0R_ANTHROPIC_MODEL, RAPT0R_OPENAI_MODEL.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import os
|
|
12
|
+
import urllib.error
|
|
13
|
+
import urllib.request
|
|
14
|
+
|
|
15
|
+
ANTHROPIC_URL = "https://api.anthropic.com/v1/messages"
|
|
16
|
+
OPENAI_URL = "https://api.openai.com/v1/chat/completions"
|
|
17
|
+
|
|
18
|
+
DEFAULT_ANTHROPIC_MODEL = "claude-opus-4-8"
|
|
19
|
+
DEFAULT_OPENAI_MODEL = "gpt-5-codex"
|
|
20
|
+
|
|
21
|
+
TIMEOUT = 600
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class LLMError(Exception):
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def available_providers():
|
|
29
|
+
providers = []
|
|
30
|
+
if os.environ.get("ANTHROPIC_API_KEY"):
|
|
31
|
+
providers.append("anthropic")
|
|
32
|
+
if os.environ.get("OPENAI_API_KEY"):
|
|
33
|
+
providers.append("openai")
|
|
34
|
+
return providers
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def iter_sse_data(lines):
|
|
38
|
+
"""Yield parsed JSON payloads from an iterable of raw SSE byte lines."""
|
|
39
|
+
for raw in lines:
|
|
40
|
+
line = raw.decode("utf-8", errors="replace").strip()
|
|
41
|
+
if not line.startswith("data:"):
|
|
42
|
+
continue
|
|
43
|
+
data = line[len("data:"):].strip()
|
|
44
|
+
if data == "[DONE]":
|
|
45
|
+
return
|
|
46
|
+
try:
|
|
47
|
+
yield json.loads(data)
|
|
48
|
+
except json.JSONDecodeError:
|
|
49
|
+
continue
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _post_stream(url, headers, body):
|
|
53
|
+
req = urllib.request.Request(
|
|
54
|
+
url, data=json.dumps(body).encode("utf-8"), headers=headers, method="POST"
|
|
55
|
+
)
|
|
56
|
+
try:
|
|
57
|
+
return urllib.request.urlopen(req, timeout=TIMEOUT)
|
|
58
|
+
except urllib.error.HTTPError as e:
|
|
59
|
+
detail = e.read().decode("utf-8", errors="replace")[:500]
|
|
60
|
+
raise LLMError(f"HTTP {e.code}: {detail}") from e
|
|
61
|
+
except urllib.error.URLError as e:
|
|
62
|
+
raise LLMError(f"Network error: {e.reason}") from e
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _stream_anthropic(prompt):
|
|
66
|
+
model = os.environ.get("RAPT0R_ANTHROPIC_MODEL", DEFAULT_ANTHROPIC_MODEL)
|
|
67
|
+
resp = _post_stream(
|
|
68
|
+
ANTHROPIC_URL,
|
|
69
|
+
{
|
|
70
|
+
"x-api-key": os.environ["ANTHROPIC_API_KEY"],
|
|
71
|
+
"anthropic-version": "2023-06-01",
|
|
72
|
+
"content-type": "application/json",
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"model": model,
|
|
76
|
+
"max_tokens": 64000,
|
|
77
|
+
"stream": True,
|
|
78
|
+
"thinking": {"type": "adaptive"},
|
|
79
|
+
"messages": [{"role": "user", "content": prompt}],
|
|
80
|
+
},
|
|
81
|
+
)
|
|
82
|
+
with resp:
|
|
83
|
+
for event in iter_sse_data(resp):
|
|
84
|
+
etype = event.get("type")
|
|
85
|
+
if etype == "content_block_delta":
|
|
86
|
+
delta = event.get("delta", {})
|
|
87
|
+
if delta.get("type") == "text_delta":
|
|
88
|
+
yield delta.get("text", "")
|
|
89
|
+
elif etype == "error":
|
|
90
|
+
raise LLMError(event.get("error", {}).get("message", "stream error"))
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _stream_openai(prompt):
|
|
94
|
+
model = os.environ.get("RAPT0R_OPENAI_MODEL", DEFAULT_OPENAI_MODEL)
|
|
95
|
+
resp = _post_stream(
|
|
96
|
+
OPENAI_URL,
|
|
97
|
+
{
|
|
98
|
+
"Authorization": f"Bearer {os.environ['OPENAI_API_KEY']}",
|
|
99
|
+
"content-type": "application/json",
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"model": model,
|
|
103
|
+
"stream": True,
|
|
104
|
+
"messages": [{"role": "user", "content": prompt}],
|
|
105
|
+
},
|
|
106
|
+
)
|
|
107
|
+
with resp:
|
|
108
|
+
for event in iter_sse_data(resp):
|
|
109
|
+
choices = event.get("choices") or []
|
|
110
|
+
if choices:
|
|
111
|
+
text = choices[0].get("delta", {}).get("content")
|
|
112
|
+
if text:
|
|
113
|
+
yield text
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def stream_completion(prompt, provider):
|
|
117
|
+
"""Yield response text chunks from the given provider."""
|
|
118
|
+
if provider == "anthropic":
|
|
119
|
+
yield from _stream_anthropic(prompt)
|
|
120
|
+
elif provider == "openai":
|
|
121
|
+
yield from _stream_openai(prompt)
|
|
122
|
+
else:
|
|
123
|
+
raise LLMError(f"Unknown provider: {provider}")
|