fableforge-shell-whisperer 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.
- fableforge_shell_whisperer-0.1.0/LICENSE +21 -0
- fableforge_shell_whisperer-0.1.0/PKG-INFO +483 -0
- fableforge_shell_whisperer-0.1.0/README.md +431 -0
- fableforge_shell_whisperer-0.1.0/pyproject.toml +87 -0
- fableforge_shell_whisperer-0.1.0/setup.cfg +4 -0
- fableforge_shell_whisperer-0.1.0/src/fableforge_shell_whisperer.egg-info/PKG-INFO +483 -0
- fableforge_shell_whisperer-0.1.0/src/fableforge_shell_whisperer.egg-info/SOURCES.txt +19 -0
- fableforge_shell_whisperer-0.1.0/src/fableforge_shell_whisperer.egg-info/dependency_links.txt +1 -0
- fableforge_shell_whisperer-0.1.0/src/fableforge_shell_whisperer.egg-info/entry_points.txt +2 -0
- fableforge_shell_whisperer-0.1.0/src/fableforge_shell_whisperer.egg-info/requires.txt +38 -0
- fableforge_shell_whisperer-0.1.0/src/fableforge_shell_whisperer.egg-info/top_level.txt +1 -0
- fableforge_shell_whisperer-0.1.0/src/shell_whisperer/__init__.py +23 -0
- fableforge_shell_whisperer-0.1.0/src/shell_whisperer/cli.py +350 -0
- fableforge_shell_whisperer-0.1.0/src/shell_whisperer/data_extractor.py +660 -0
- fableforge_shell_whisperer-0.1.0/src/shell_whisperer/exporter.py +426 -0
- fableforge_shell_whisperer-0.1.0/src/shell_whisperer/inference.py +549 -0
- fableforge_shell_whisperer-0.1.0/src/shell_whisperer/prompts.py +122 -0
- fableforge_shell_whisperer-0.1.0/src/shell_whisperer/server.py +289 -0
- fableforge_shell_whisperer-0.1.0/src/shell_whisperer/trainer.py +326 -0
- fableforge_shell_whisperer-0.1.0/tests/test_data_extractor.py +351 -0
- fableforge_shell_whisperer-0.1.0/tests/test_inference.py +274 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 FableForge Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fableforge-shell-whisperer
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: 1.5B edge-native shell agent — natural language to shell commands, 50ms latency on phone/edge
|
|
5
|
+
Author: FableForge Team
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: shell,nlp,edge,onnx,llm,cli
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Topic :: System :: Shells
|
|
16
|
+
Requires-Python: >=3.10
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Requires-Dist: rich>=13.0
|
|
20
|
+
Requires-Dist: click>=8.1
|
|
21
|
+
Requires-Dist: pydantic>=2.0
|
|
22
|
+
Requires-Dist: httpx>=0.25
|
|
23
|
+
Requires-Dist: fastapi>=0.110.0
|
|
24
|
+
Requires-Dist: uvicorn>=0.29.0
|
|
25
|
+
Requires-Dist: websockets>=12.0
|
|
26
|
+
Provides-Extra: train
|
|
27
|
+
Requires-Dist: torch>=2.1.0; extra == "train"
|
|
28
|
+
Requires-Dist: transformers>=4.40.0; extra == "train"
|
|
29
|
+
Requires-Dist: peft>=0.10.0; extra == "train"
|
|
30
|
+
Requires-Dist: datasets>=2.18.0; extra == "train"
|
|
31
|
+
Requires-Dist: unsloth>=2024.1; extra == "train"
|
|
32
|
+
Requires-Dist: accelerate>=0.27.0; extra == "train"
|
|
33
|
+
Requires-Dist: trl>=0.8.0; extra == "train"
|
|
34
|
+
Requires-Dist: wandb>=0.16.0; extra == "train"
|
|
35
|
+
Provides-Extra: gpu
|
|
36
|
+
Requires-Dist: bitsandbytes>=0.43.0; extra == "gpu"
|
|
37
|
+
Requires-Dist: xformers>=0.0.23; extra == "gpu"
|
|
38
|
+
Provides-Extra: export
|
|
39
|
+
Requires-Dist: onnx>=1.16.0; extra == "export"
|
|
40
|
+
Requires-Dist: onnxruntime>=1.17.0; extra == "export"
|
|
41
|
+
Requires-Dist: optimum>=1.17.0; extra == "export"
|
|
42
|
+
Provides-Extra: gguf
|
|
43
|
+
Requires-Dist: llama-cpp-python>=0.2.50; extra == "gguf"
|
|
44
|
+
Provides-Extra: all
|
|
45
|
+
Requires-Dist: shell-whisperer[export,gguf,gpu,train]; extra == "all"
|
|
46
|
+
Provides-Extra: dev
|
|
47
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
48
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"
|
|
49
|
+
Requires-Dist: ruff>=0.3.0; extra == "dev"
|
|
50
|
+
Requires-Dist: mypy>=1.9.0; extra == "dev"
|
|
51
|
+
Dynamic: license-file
|
|
52
|
+
|
|
53
|
+
# ShellWhisperer
|
|
54
|
+
|
|
55
|
+
[](LICENSE) [](https://www.python.org/downloads/) [](tests/)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
> **Natural language → shell commands. 50ms on edge.**
|
|
59
|
+
|
|
60
|
+
A 1.5B parameter edge-native shell agent fine-tuned from Qwen3-1.5B. Converts natural language descriptions into safe, correct shell commands — designed to run on phones and edge devices via ONNX Runtime or llama.cpp GGUF.
|
|
61
|
+
|
|
62
|
+
## Features
|
|
63
|
+
|
|
64
|
+
- **Edge-native**: Runs in <50ms on mobile/edge via ONNX or GGUF
|
|
65
|
+
- **Multi-OS**: Linux, macOS, Windows PowerShell prompts
|
|
66
|
+
- **Safety-first**: Built-in guardrails against destructive commands (rm -rf /, fork bombs, pipe-to-shell)
|
|
67
|
+
- **Context-aware**: Uses working directory, OS type, and recent command history
|
|
68
|
+
- **Multiple backends**: HuggingFace Transformers, ONNX Runtime, llama.cpp
|
|
69
|
+
- **Streaming**: WebSocket streaming for real-time output
|
|
70
|
+
- **Fine-tune your own**: LoRA training on Fable5 traces or custom data
|
|
71
|
+
|
|
72
|
+
## Quick Start
|
|
73
|
+
|
|
74
|
+
### Install
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
pip install shell-whisperer
|
|
78
|
+
|
|
79
|
+
# With training support:
|
|
80
|
+
pip install "shell-whisperer[train]"
|
|
81
|
+
|
|
82
|
+
# With GGUF inference:
|
|
83
|
+
pip install "shell-whisperer[gguf]"
|
|
84
|
+
|
|
85
|
+
# Everything:
|
|
86
|
+
pip install "shell-whisperer[train,gguf,dev]"
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### One-shot Prediction
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
# Basic usage
|
|
93
|
+
sw "find all python files over 100 lines"
|
|
94
|
+
# → find . -name "*.py" -exec wc -l {} + | awk '$1 > 100'
|
|
95
|
+
|
|
96
|
+
sw "kill the process on port 8080"
|
|
97
|
+
# → lsof -ti:8080 | xargs kill -9
|
|
98
|
+
|
|
99
|
+
sw --os-type macos "install ffmpeg"
|
|
100
|
+
# → brew install ffmpeg
|
|
101
|
+
|
|
102
|
+
sw --os-type windows "show all listening ports"
|
|
103
|
+
# → Get-NetTCPConnection -State Listen | Format-Table LocalPort, OwningProcess -AutoSize
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Interactive Mode
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
sw --interactive
|
|
110
|
+
|
|
111
|
+
sw> find all python files over 100 lines
|
|
112
|
+
┌─────────────────────────────────────────────────────────┐
|
|
113
|
+
│ find . -name "*.py" -exec wc -l {} + | awk '$1 > 100' │
|
|
114
|
+
└─────────────────────────────────────────────────────────┘
|
|
115
|
+
42.5ms
|
|
116
|
+
|
|
117
|
+
sw> !os macos
|
|
118
|
+
OS set to: macos
|
|
119
|
+
|
|
120
|
+
sw> install ffmpeg
|
|
121
|
+
┌──────────────────────┐
|
|
122
|
+
│ brew install ffmpeg │
|
|
123
|
+
└──────────────────────┘
|
|
124
|
+
28.1ms
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Start API Server
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
sw --serve --port 8000
|
|
131
|
+
# Or specify model:
|
|
132
|
+
sw --serve --model ./models/shell-whisperer-merged --port 8000
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Fine-Tuning
|
|
136
|
+
|
|
137
|
+
### Prepare Training Data
|
|
138
|
+
|
|
139
|
+
ShellWhisperer extracts training pairs from Fable5 trace formats:
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
from shell_whisperer.data_extractor import load_training_data
|
|
143
|
+
|
|
144
|
+
# Load from JSONL traces (auto-detects format)
|
|
145
|
+
pairs = load_training_data("./traces/glint_data.jsonl", fmt="auto")
|
|
146
|
+
|
|
147
|
+
# Or specify format explicitly
|
|
148
|
+
from shell_whisperer.data_extractor import (
|
|
149
|
+
extract_bash_from_glint,
|
|
150
|
+
extract_bash_from_armand0e,
|
|
151
|
+
extract_bash_from_vfable,
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
pairs = extract_bash_from_glint("./traces/glint.jsonl")
|
|
155
|
+
pairs = extract_bash_from_armand0e("./traces/armand0e.jsonl")
|
|
156
|
+
pairs = extract_bash_from_vfable("./traces/vfable.jsonl")
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
#### Supported Trace Formats
|
|
160
|
+
|
|
161
|
+
**Glint** — Command traces with shell intent metadata:
|
|
162
|
+
```jsonl
|
|
163
|
+
{"type": "shell_intent", "intent": "find all python files over 100 lines"}
|
|
164
|
+
{"type": "shell_command", "command": "find . -name '*.py' -exec wc -l {} + | awk '$1 > 100'", "shell": "bash", "exit_code": 0}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**armand0e** — Structured shell session logs:
|
|
168
|
+
```jsonl
|
|
169
|
+
{"event": "command_executed", "prompt": "show disk usage sorted by size", "command": "du -sh * | sort -rh", "exit_status": 0}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
**v-Fable** — Validated Fable traces with confirmation signals:
|
|
173
|
+
```jsonl
|
|
174
|
+
{"role": "user", "utterance": "find all json files modified recently"}
|
|
175
|
+
{"role": "assistant", "tool_call": {"name": "execute_shell", "arguments": {"command": "find . -name '*.json' -mtime -7"}}, "validation": {"confirmed": true, "exit_code": 0}}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Train with LoRA
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
# LoRA fine-tune (default)
|
|
182
|
+
sw train --data ./traces/data.jsonl --epochs 3
|
|
183
|
+
|
|
184
|
+
# Full fine-tune
|
|
185
|
+
sw train --data ./traces/data.jsonl --full-finetune
|
|
186
|
+
|
|
187
|
+
# Custom parameters
|
|
188
|
+
sw train \
|
|
189
|
+
--data ./traces/data.jsonl \
|
|
190
|
+
--model-name Qwen/Qwen3-1.5B \
|
|
191
|
+
--output-dir ./models/my-shell-whisperer \
|
|
192
|
+
--epochs 5 \
|
|
193
|
+
--lr 1e-4 \
|
|
194
|
+
--batch-size 8 \
|
|
195
|
+
--os-type linux
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Training in Python
|
|
199
|
+
|
|
200
|
+
```python
|
|
201
|
+
from shell_whisperer.trainer import TrainConfig, train_lora
|
|
202
|
+
from shell_whisperer.data_extractor import load_training_data
|
|
203
|
+
|
|
204
|
+
# Load data
|
|
205
|
+
pairs = load_training_data("./traces/data.jsonl", include_builtin=True)
|
|
206
|
+
print(f"Training with {len(pairs)} pairs")
|
|
207
|
+
|
|
208
|
+
# Configure and train
|
|
209
|
+
config = TrainConfig(
|
|
210
|
+
model_name="Qwen/Qwen3-1.5B",
|
|
211
|
+
output_dir="./models/shell-whisperer-lora",
|
|
212
|
+
epochs=3,
|
|
213
|
+
learning_rate=2e-4,
|
|
214
|
+
lora_r=16,
|
|
215
|
+
use_4bit=True,
|
|
216
|
+
use_unsloth=True,
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
adapter_path = train_lora(config=config, training_pairs=pairs)
|
|
220
|
+
|
|
221
|
+
# Merge adapter with base model
|
|
222
|
+
from shell_whisperer.trainer import merge_and_save
|
|
223
|
+
|
|
224
|
+
merge_and_save(
|
|
225
|
+
adapter_path=adapter_path,
|
|
226
|
+
output_dir="./models/shell-whisperer-merged",
|
|
227
|
+
)
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## Export for Edge
|
|
231
|
+
|
|
232
|
+
```bash
|
|
233
|
+
# Export to ONNX
|
|
234
|
+
sw export --format onnx --model ./models/shell-whisperer-merged
|
|
235
|
+
|
|
236
|
+
# Export to GGUF (for llama.cpp)
|
|
237
|
+
sw export --format gguf --model ./models/shell-whisperer-merged
|
|
238
|
+
|
|
239
|
+
# 4-bit quantization (smallest, fastest)
|
|
240
|
+
sw export --format 4bit --model ./models/shell-whisperer-merged
|
|
241
|
+
|
|
242
|
+
# 8-bit quantization
|
|
243
|
+
sw export --format 8bit --model ./models/shell-whisperer-merged
|
|
244
|
+
|
|
245
|
+
# Export all formats
|
|
246
|
+
sw export --format all --model ./models/shell-whisperer-merged
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Memory Estimates
|
|
250
|
+
|
|
251
|
+
| Format | RAM Required | Latency (edge) |
|
|
252
|
+
|--------|-------------|----------------|
|
|
253
|
+
| FP32 | ~6.0 GB | ~200ms |
|
|
254
|
+
| FP16 | ~3.0 GB | ~100ms |
|
|
255
|
+
| 8-bit | ~1.5 GB | ~60ms |
|
|
256
|
+
| 4-bit | ~0.75 GB | ~50ms |
|
|
257
|
+
| GGUF Q4_K_M | ~0.84 GB | ~50ms |
|
|
258
|
+
|
|
259
|
+
## Inference
|
|
260
|
+
|
|
261
|
+
### Python API
|
|
262
|
+
|
|
263
|
+
```python
|
|
264
|
+
from shell_whisperer import ShellWhisperer
|
|
265
|
+
|
|
266
|
+
# Load model
|
|
267
|
+
sw = ShellWhisperer(os_type="linux")
|
|
268
|
+
sw.load_model("./models/shell-whisperer-merged")
|
|
269
|
+
|
|
270
|
+
# Predict
|
|
271
|
+
result = sw.predict("find all python files over 100 lines")
|
|
272
|
+
print(result.command)
|
|
273
|
+
# → find . -name "*.py" -exec wc -l {} + | awk '$1 > 100'
|
|
274
|
+
|
|
275
|
+
# Context-aware prediction
|
|
276
|
+
result = sw.predict(
|
|
277
|
+
"find config files",
|
|
278
|
+
working_directory="/etc",
|
|
279
|
+
recent_history=["ls -la", "cd /etc"],
|
|
280
|
+
os_type="linux",
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
# Batch prediction
|
|
284
|
+
results = sw.predict_batch([
|
|
285
|
+
"find all python files over 100 lines",
|
|
286
|
+
"kill the process on port 8080",
|
|
287
|
+
"show disk usage sorted by size",
|
|
288
|
+
])
|
|
289
|
+
|
|
290
|
+
# Streaming
|
|
291
|
+
for token in sw.predict_stream("find all python files"):
|
|
292
|
+
print(token, end="", flush=True)
|
|
293
|
+
|
|
294
|
+
# Safety warnings
|
|
295
|
+
result = sw.predict("delete everything")
|
|
296
|
+
if result.safety_warnings:
|
|
297
|
+
for warning in result.safety_warnings:
|
|
298
|
+
print(f"⚠ {warning}")
|
|
299
|
+
|
|
300
|
+
sw.unload()
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### REST API
|
|
304
|
+
|
|
305
|
+
```bash
|
|
306
|
+
# Start server
|
|
307
|
+
sw --serve --port 8000
|
|
308
|
+
|
|
309
|
+
# Predict
|
|
310
|
+
curl -X POST http://localhost:8000/predict \
|
|
311
|
+
-H "Content-Type: application/json" \
|
|
312
|
+
-d '{"prompt": "find all python files over 100 lines", "os_type": "linux"}'
|
|
313
|
+
|
|
314
|
+
# Batch predict
|
|
315
|
+
curl -X POST http://localhost:8000/predict/batch \
|
|
316
|
+
-H "Content-Type: application/json" \
|
|
317
|
+
-d '{"prompts": ["find python files", "kill port 8080"]}'
|
|
318
|
+
|
|
319
|
+
# Health check
|
|
320
|
+
curl http://localhost:8000/health
|
|
321
|
+
|
|
322
|
+
# Model info
|
|
323
|
+
curl http://localhost:8000/info
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
### WebSocket Streaming
|
|
327
|
+
|
|
328
|
+
```javascript
|
|
329
|
+
const ws = new WebSocket("ws://localhost:8000/ws/stream");
|
|
330
|
+
|
|
331
|
+
ws.onopen = () => {
|
|
332
|
+
ws.send(JSON.stringify({
|
|
333
|
+
prompt: "find all python files over 100 lines",
|
|
334
|
+
os_type: "linux"
|
|
335
|
+
}));
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
ws.onmessage = (event) => {
|
|
339
|
+
const data = JSON.parse(event.data);
|
|
340
|
+
if (data.token) {
|
|
341
|
+
process.stdout.write(data.token);
|
|
342
|
+
} else if (data.done) {
|
|
343
|
+
console.log("\nCommand:", data.command);
|
|
344
|
+
if (data.safety_warnings.length) {
|
|
345
|
+
console.log("Warnings:", data.safety_warnings);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
};
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
## Example Training Pairs
|
|
352
|
+
|
|
353
|
+
Built-in high-quality pairs from real-world shell usage:
|
|
354
|
+
|
|
355
|
+
| Natural Language | Shell Command | Quality |
|
|
356
|
+
|----------------|---------------|---------|
|
|
357
|
+
| find all python files over 100 lines | `find . -name "*.py" -exec wc -l {} + \| awk '$1 > 100'` | 0.85 |
|
|
358
|
+
| kill the process on port 8080 | `lsof -ti:8080 \| xargs kill -9` | 0.80 |
|
|
359
|
+
| show disk usage sorted by size | `du -sh * \| sort -rh` | 0.75 |
|
|
360
|
+
| recursively search for TODO in all python files | `grep -rn "TODO" --include="*.py" .` | 0.83 |
|
|
361
|
+
| rename all .txt files to .md | `for f in *.txt; do mv "$f" "${f%.txt}.md"; done` | 0.84 |
|
|
362
|
+
| remove all stopped docker containers | `docker container prune -f` | 0.73 |
|
|
363
|
+
| show all git commits by the current user this month | `git log --author="$(git config user.name)" --since="$(date +%Y-%m-01)" --oneline` | 0.88 |
|
|
364
|
+
| list all unique IPs that connected via SSH | `grep "Accepted" /var/log/auth.log \| awk '{print $11}' \| sort -u` | 0.84 |
|
|
365
|
+
|
|
366
|
+
## Safety System
|
|
367
|
+
|
|
368
|
+
ShellWhisperer includes a built-in safety layer that:
|
|
369
|
+
|
|
370
|
+
1. **Blocks destructive commands**: `rm -rf /`, fork bombs, `dd` to disk
|
|
371
|
+
2. **Warns on sudo**: Flags commands requiring elevated privileges
|
|
372
|
+
3. **Flags pipe-to-shell**: Warns about `curl | bash` patterns
|
|
373
|
+
4. **Prevents chmod 777**: Warns about insecure permissions
|
|
374
|
+
|
|
375
|
+
```python
|
|
376
|
+
result = sw.predict("delete all files")
|
|
377
|
+
# Safety warning: ⚠ SAFETY: Destructive: recursive force-delete
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
## Architecture
|
|
381
|
+
|
|
382
|
+
```
|
|
383
|
+
┌─────────────────────────────────────────┐
|
|
384
|
+
│ Natural Language Input │
|
|
385
|
+
└──────────────┬──────────────────────────┘
|
|
386
|
+
│
|
|
387
|
+
┌──────────────▼──────────────────────────┐
|
|
388
|
+
│ System Prompt (OS-specific) │
|
|
389
|
+
│ LINUX_PROMPT / MACOS_PROMPT / │
|
|
390
|
+
│ WINDOWS_PROMPT + Safety Rules │
|
|
391
|
+
└──────────────┬──────────────────────────┘
|
|
392
|
+
│
|
|
393
|
+
┌──────────────▼──────────────────────────┐
|
|
394
|
+
│ Qwen3-1.5B (LoRA fine-tuned) │
|
|
395
|
+
│ 1.5B parameters │
|
|
396
|
+
└──────────────┬──────────────────────────┘
|
|
397
|
+
│
|
|
398
|
+
┌──────────────▼──────────────────────────┐
|
|
399
|
+
│ Output Cleaning │
|
|
400
|
+
│ - Strip markdown/backticks │
|
|
401
|
+
│ - Remove model prefixes │
|
|
402
|
+
│ - Multi-line pipe/chain handling │
|
|
403
|
+
└──────────────┬──────────────────────────┘
|
|
404
|
+
│
|
|
405
|
+
┌──────────────▼──────────────────────────┐
|
|
406
|
+
│ Safety Check │
|
|
407
|
+
│ - rm -rf protection │
|
|
408
|
+
│ - sudo warning │
|
|
409
|
+
│ - pipe-to-shell detection │
|
|
410
|
+
└──────────────┬──────────────────────────┘
|
|
411
|
+
│
|
|
412
|
+
┌──────────────▼──────────────────────────┐
|
|
413
|
+
│ Shell Command Output │
|
|
414
|
+
└───────────────────────────────────────────┘
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
## Project Structure
|
|
418
|
+
|
|
419
|
+
```
|
|
420
|
+
shell-whisperer/
|
|
421
|
+
├── pyproject.toml
|
|
422
|
+
├── README.md
|
|
423
|
+
├── src/shell_whisperer/
|
|
424
|
+
│ ├── __init__.py # Package init + exports
|
|
425
|
+
│ ├── prompts.py # OS-specific system prompts + safety rules
|
|
426
|
+
│ ├── data_extractor.py # Fable5 trace extraction + quality filtering
|
|
427
|
+
│ ├── trainer.py # LoRA/Full fine-tuning on Qwen3-1.5B
|
|
428
|
+
│ ├── exporter.py # ONNX, GGUF, 4-bit/8-bit export + memory estimation
|
|
429
|
+
│ ├── inference.py # Multi-backend inference (Transformers, ONNX, llama.cpp)
|
|
430
|
+
│ ├── server.py # FastAPI server (REST + WebSocket)
|
|
431
|
+
│ └── cli.py # CLI: sw predict, train, export, serve
|
|
432
|
+
└── tests/
|
|
433
|
+
├── test_data_extractor.py
|
|
434
|
+
└── test_inference.py
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
## Development
|
|
438
|
+
|
|
439
|
+
```bash
|
|
440
|
+
# Install dev dependencies
|
|
441
|
+
pip install -e ".[dev]"
|
|
442
|
+
|
|
443
|
+
# Run tests
|
|
444
|
+
pytest
|
|
445
|
+
|
|
446
|
+
# Lint
|
|
447
|
+
ruff check src/ tests/
|
|
448
|
+
|
|
449
|
+
# Type check
|
|
450
|
+
mypy src/
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
## License
|
|
454
|
+
|
|
455
|
+
MIT
|
|
456
|
+
|
|
457
|
+
## Ecosystem
|
|
458
|
+
|
|
459
|
+
Part of the [FableForge](../) ecosystem — 21 open-source projects built from 210K real agent traces:
|
|
460
|
+
|
|
461
|
+
| Project | Description |
|
|
462
|
+
| --- | --- |
|
|
463
|
+
| **[Anvil](../anvil)** | Self-verified coding agent |
|
|
464
|
+
| **[VerifyLoop](../verifyloop)** | Plan→Execute→Verify→Recover framework |
|
|
465
|
+
| **[ErrorRecovery](../error-recovery)** | Self-healing middleware (3,725 error patterns) |
|
|
466
|
+
| **[FableForge-14B](../fableforge-14b)** | The fine-tuned 14B model (4-stage training) |
|
|
467
|
+
| **[ShellWhisperer](../shell-whisperer)** | 1.5B edge agent (phone/RPi, 50ms) |
|
|
468
|
+
| **[ReasonCritic](../reason-critic)** | Verification model (130 benchmark tasks) |
|
|
469
|
+
| **[TraceCompiler](../trace-compiler)** | Compile traces → LoRA skills |
|
|
470
|
+
| **[AgentRuntime](../agent-runtime)** | Persistent agent daemon (systemd for AI) |
|
|
471
|
+
| **[AgentSwarm](../agent-swarm)** | Multi-agent from real trace transitions |
|
|
472
|
+
| **[AgentTelemetry](../agent-telemetry)** | Datadog for agents (token tracking, costs) |
|
|
473
|
+
| **[BenchAgent](../bench-agent)** | HumanEval for tool-use (107 tasks) |
|
|
474
|
+
| **[AgentDev](../agent-dev)** | VSCode extension with verification |
|
|
475
|
+
| **[TraceViz](../trace-viz)** | Trace replay visualizer (Next.js) |
|
|
476
|
+
| **[AgentSkills](../agent-skills)** | npm for agent behaviors |
|
|
477
|
+
| **[AgentCurriculum](../agent-curriculum)** | 5-stage progressive training |
|
|
478
|
+
| **[AgentFuzzer](../agent-fuzzer)** | Adversarial testing for agents |
|
|
479
|
+
| **[AgentConstitution](../agent-constitution)** | Safety guardrails from traces |
|
|
480
|
+
| **[CostOptimizer](../cost-optimizer)** | Token cost reduction (50-80%) |
|
|
481
|
+
| **[AgentProfiler](../agent-profiler)** | Behavioral fingerprinting |
|
|
482
|
+
| **[TrajectoryDistiller](../trajectory-distiller)** | Trace→training data pipeline |
|
|
483
|
+
| **[Fable5-Dataset](../fable5-dataset)** | HuggingFace dataset release |
|