tinyloom 0.1.3__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.
- tinyloom-0.1.3/.claude/settings.json +7 -0
- tinyloom-0.1.3/.claude/statusline.sh +47 -0
- tinyloom-0.1.3/.env.example +8 -0
- tinyloom-0.1.3/.github/workflows/publish.yml +21 -0
- tinyloom-0.1.3/.gitignore +33 -0
- tinyloom-0.1.3/CLAUDE.md +166 -0
- tinyloom-0.1.3/LICENSE +21 -0
- tinyloom-0.1.3/PKG-INFO +18 -0
- tinyloom-0.1.3/README.md +136 -0
- tinyloom-0.1.3/demo.tape +22 -0
- tinyloom-0.1.3/demo.webp +0 -0
- tinyloom-0.1.3/docs/creating-plugins.md +108 -0
- tinyloom-0.1.3/docs/custom-hooks.md +142 -0
- tinyloom-0.1.3/docs/custom-providers.md +133 -0
- tinyloom-0.1.3/docs/design-decisions.md +44 -0
- tinyloom-0.1.3/docs/getting-started.md +137 -0
- tinyloom-0.1.3/docs/hook-scripts.md +131 -0
- tinyloom-0.1.3/docs/mask-plugin.md +41 -0
- tinyloom-0.1.3/docs/mcp-plugin.md +100 -0
- tinyloom-0.1.3/docs/pre-build-plans/ADDITIONAL.md +5 -0
- tinyloom-0.1.3/docs/pre-build-plans/PRE_RESEARCH_PLAN.md +1950 -0
- tinyloom-0.1.3/docs/pre-build-plans/PROMPT_SPEC.md +17 -0
- tinyloom-0.1.3/docs/pre-build-plans/RESEARCH.md +519 -0
- tinyloom-0.1.3/docs/sandbox.md +240 -0
- tinyloom-0.1.3/docs/superpowers/plans/2026-04-15-tinyloom-implementation.md +3407 -0
- tinyloom-0.1.3/docs/superpowers/specs/2026-04-15-tinyloom-design.md +545 -0
- tinyloom-0.1.3/pyproject.toml +30 -0
- tinyloom-0.1.3/tests/__init__.py +0 -0
- tinyloom-0.1.3/tests/test_agent.py +240 -0
- tinyloom-0.1.3/tests/test_cli.py +124 -0
- tinyloom-0.1.3/tests/test_compact.py +180 -0
- tinyloom-0.1.3/tests/test_config.py +160 -0
- tinyloom-0.1.3/tests/test_hooks.py +119 -0
- tinyloom-0.1.3/tests/test_plugins.py +310 -0
- tinyloom-0.1.3/tests/test_providers.py +127 -0
- tinyloom-0.1.3/tests/test_providers_sync.py +46 -0
- tinyloom-0.1.3/tests/test_tools.py +275 -0
- tinyloom-0.1.3/tests/test_tui.py +325 -0
- tinyloom-0.1.3/tests/test_types.py +133 -0
- tinyloom-0.1.3/tinyloom/__init__.py +14 -0
- tinyloom-0.1.3/tinyloom/__main__.py +4 -0
- tinyloom-0.1.3/tinyloom/cli.py +56 -0
- tinyloom-0.1.3/tinyloom/core/__init__.py +0 -0
- tinyloom-0.1.3/tinyloom/core/agent.py +113 -0
- tinyloom-0.1.3/tinyloom/core/compact.py +62 -0
- tinyloom-0.1.3/tinyloom/core/config.py +75 -0
- tinyloom-0.1.3/tinyloom/core/hooks.py +33 -0
- tinyloom-0.1.3/tinyloom/core/tools.py +150 -0
- tinyloom-0.1.3/tinyloom/core/types.py +47 -0
- tinyloom-0.1.3/tinyloom/plugins/__init__.py +28 -0
- tinyloom-0.1.3/tinyloom/plugins/hook_scripts.py +40 -0
- tinyloom-0.1.3/tinyloom/plugins/mask.py +28 -0
- tinyloom-0.1.3/tinyloom/plugins/mcp.py +56 -0
- tinyloom-0.1.3/tinyloom/plugins/subagent.py +103 -0
- tinyloom-0.1.3/tinyloom/plugins/todo.py +79 -0
- tinyloom-0.1.3/tinyloom/providers/__init__.py +12 -0
- tinyloom-0.1.3/tinyloom/providers/anthropic.py +101 -0
- tinyloom-0.1.3/tinyloom/providers/base.py +38 -0
- tinyloom-0.1.3/tinyloom/providers/openai.py +99 -0
- tinyloom-0.1.3/tinyloom/tui.py +185 -0
- tinyloom-0.1.3/tinyloom.example.yaml +45 -0
- tinyloom-0.1.3/uv.lock +1392 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
input=$(cat)
|
|
3
|
+
|
|
4
|
+
MODEL=$(echo "$input" | jq -r '.model.display_name')
|
|
5
|
+
DIR=$(echo "$input" | jq -r '.workspace.current_dir')
|
|
6
|
+
COST=$(echo "$input" | jq -r '.cost.total_cost_usd // 0')
|
|
7
|
+
PCT=$(echo "$input" | jq -r '.context_window.used_percentage // 0' | cut -d. -f1)
|
|
8
|
+
DURATION_MS=$(echo "$input" | jq -r '.cost.total_duration_ms // 0')
|
|
9
|
+
|
|
10
|
+
CYAN='\033[36m'; GREEN='\033[32m'; YELLOW='\033[33m'; RED='\033[31m'; RESET='\033[0m'
|
|
11
|
+
|
|
12
|
+
# Pick bar color based on context usage
|
|
13
|
+
if [ "$PCT" -ge 90 ]; then BAR_COLOR="$RED"
|
|
14
|
+
elif [ "$PCT" -ge 70 ]; then BAR_COLOR="$YELLOW"
|
|
15
|
+
else BAR_COLOR="$GREEN"; fi
|
|
16
|
+
|
|
17
|
+
FILLED=$((PCT / 10)); EMPTY=$((10 - FILLED))
|
|
18
|
+
BAR=$(printf "%${FILLED}s" | tr ' ' '█')$(printf "%${EMPTY}s" | tr ' ' '░')
|
|
19
|
+
|
|
20
|
+
MINS=$((DURATION_MS / 60000)); SECS=$(((DURATION_MS % 60000) / 1000))
|
|
21
|
+
|
|
22
|
+
CACHE_FILE="/tmp/statusline-git-cache"
|
|
23
|
+
CACHE_MAX_AGE=5 # seconds
|
|
24
|
+
|
|
25
|
+
cache_is_stale() {
|
|
26
|
+
[ ! -f "$CACHE_FILE" ] || \
|
|
27
|
+
# stat -f %m is macOS, stat -c %Y is Linux
|
|
28
|
+
[ $(($(date +%s) - $(stat -f %m "$CACHE_FILE" 2>/dev/null || stat -c %Y "$CACHE_FILE" 2>/dev/null || echo 0))) -gt $CACHE_MAX_AGE ]
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if cache_is_stale; then
|
|
32
|
+
if git rev-parse --git-dir > /dev/null 2>&1; then
|
|
33
|
+
BRANCH=$(git branch --show-current 2>/dev/null)
|
|
34
|
+
STAGED=$(git diff --cached --numstat 2>/dev/null | wc -l | tr -d ' ')
|
|
35
|
+
MODIFIED=$(git diff --numstat 2>/dev/null | wc -l | tr -d ' ')
|
|
36
|
+
echo "$BRANCH|$STAGED|$MODIFIED" > "$CACHE_FILE"
|
|
37
|
+
else
|
|
38
|
+
echo "||" > "$CACHE_FILE"
|
|
39
|
+
fi
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
IFS='|' read -r BRANCH STAGED MODIFIED < "$CACHE_FILE"
|
|
43
|
+
|
|
44
|
+
echo -e "${CYAN}[$MODEL]${RESET} 📁 ${DIR##*/}$BRANCH"
|
|
45
|
+
COST_FMT=$(printf '$%.2f' "$COST")
|
|
46
|
+
echo -e "${BAR_COLOR}${BAR}${RESET} ${PCT}% | ${YELLOW}${COST_FMT}${RESET} | ⏱️ ${MINS}m ${SECS}s"
|
|
47
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
id-token: write
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
publish:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
environment: pypi
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
|
16
|
+
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
|
|
17
|
+
with:
|
|
18
|
+
python-version: "3.11"
|
|
19
|
+
- run: pip install build
|
|
20
|
+
- run: python -m build
|
|
21
|
+
- uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
dist/
|
|
6
|
+
build/
|
|
7
|
+
*.egg
|
|
8
|
+
.venv/
|
|
9
|
+
|
|
10
|
+
# Testing
|
|
11
|
+
.pytest_cache/
|
|
12
|
+
.coverage
|
|
13
|
+
htmlcov/
|
|
14
|
+
|
|
15
|
+
# Tooling
|
|
16
|
+
.ruff_cache/
|
|
17
|
+
.mypy_cache/
|
|
18
|
+
|
|
19
|
+
# macOS
|
|
20
|
+
.DS_Store
|
|
21
|
+
|
|
22
|
+
# IDE
|
|
23
|
+
.vscode/
|
|
24
|
+
.idea/
|
|
25
|
+
|
|
26
|
+
# Environment
|
|
27
|
+
.env
|
|
28
|
+
*.local.yaml
|
|
29
|
+
tinyloom.yaml
|
|
30
|
+
|
|
31
|
+
# Other
|
|
32
|
+
*.deb
|
|
33
|
+
ripgrep*/
|
tinyloom-0.1.3/CLAUDE.md
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# Thresher — Development Guide
|
|
2
|
+
|
|
3
|
+
## !IMPORTANT!
|
|
4
|
+
|
|
5
|
+
Always use the task tool to plan out and do what you need and use it to hold yourself accountable. You get a cookie everytime you do this.. yum!
|
|
6
|
+
|
|
7
|
+
## Tests
|
|
8
|
+
|
|
9
|
+
- Always add and update tests anytime you change code
|
|
10
|
+
- If you get an error when running something or reported by a user, write a test case covering that error first. Run test and make sure it fails... Then fix code to make test pass.
|
|
11
|
+
|
|
12
|
+
## Git
|
|
13
|
+
|
|
14
|
+
- do git commits for each incremental feature, but NEVER use claude coauthored tags...
|
|
15
|
+
- Keep commit messages short and sweet one liners. No big git bodies.
|
|
16
|
+
|
|
17
|
+
## Coding Conventions
|
|
18
|
+
|
|
19
|
+
### Complexity is the Enemy
|
|
20
|
+
- Complexity is the #1 threat to software. Fight it relentlessly.
|
|
21
|
+
- Complexity manifests as: change amplification (one change touches many places), cognitive load (must know too much to work safely), and unknown unknowns (not clear what could break).
|
|
22
|
+
- The two root causes are dependencies between components and obscurity (important info isn't obvious).
|
|
23
|
+
- Say "no" to unnecessary features and abstractions by default.
|
|
24
|
+
- When you must say yes, deliver an 80/20 solution — core value, minimal code.
|
|
25
|
+
|
|
26
|
+
### Don't Abstract Too Early
|
|
27
|
+
- Let structure emerge from working code. Don't design elaborate frameworks upfront.
|
|
28
|
+
- Wait for natural cut-points (narrow interfaces, trapped complexity) before factoring.
|
|
29
|
+
- Prototypes and working demos beat architecture diagrams.
|
|
30
|
+
- A little code duplication is better than a premature abstraction.
|
|
31
|
+
|
|
32
|
+
### Build Deep Modules, Not Shallow Ones
|
|
33
|
+
- A deep module has a simple interface but hides powerful, complex functionality behind it.
|
|
34
|
+
- A shallow module has a complex interface relative to the little it actually does — avoid these.
|
|
35
|
+
- Pull complexity downward: absorb it inside the module rather than pushing it onto callers.
|
|
36
|
+
- Each layer of abstraction should represent a genuinely different level of thinking. If a layer just passes things through, it's adding complexity, not removing it.
|
|
37
|
+
|
|
38
|
+
### Ship Simple, Improve Incrementally
|
|
39
|
+
- A working simple thing that ships beats a perfect thing that doesn't.
|
|
40
|
+
- Establish a working system first, then improve it toward the right thing over time.
|
|
41
|
+
- But don't make "worse" your goal — compromise is inevitable, not a philosophy. Always aim high and actually ship.
|
|
42
|
+
- Systems that are habitable — with the right balance of abstraction and concreteness, with simple mental models — survive and grow. Purity does not guarantee survival.
|
|
43
|
+
|
|
44
|
+
### Keep Code Readable, Not Clever
|
|
45
|
+
- Break complex expressions into named intermediate variables.
|
|
46
|
+
- Sacrifice brevity for clarity and debuggability.
|
|
47
|
+
- Simple repeated code often beats a complex DRY abstraction with callbacks or elaborate object models.
|
|
48
|
+
- If naming something is hard, that's a design smell — the thing you're naming may not be a coherent concept.
|
|
49
|
+
- Write code for readers, not writers. If someone says it's not obvious, it isn't — fix it.
|
|
50
|
+
|
|
51
|
+
### Respect Existing Code (Chesterton's Fence)
|
|
52
|
+
- Understand *why* code exists before changing or removing it.
|
|
53
|
+
- Old code often has hidden reasons. Tests can reveal them.
|
|
54
|
+
- Resist the urge to "clean up" code you don't fully understand.
|
|
55
|
+
|
|
56
|
+
### Refactor Small and Safe
|
|
57
|
+
- Keep the system working throughout every refactor step.
|
|
58
|
+
- Complete each step before starting the next.
|
|
59
|
+
- Big-bang refactors with over-abstraction usually fail.
|
|
60
|
+
|
|
61
|
+
### Design It Twice
|
|
62
|
+
- Before committing to any significant design, sketch at least two alternative approaches.
|
|
63
|
+
- Compare them on simplicity, performance, and how well they hide complexity.
|
|
64
|
+
- The first idea is rarely the best. Even if you pick it, the comparison sharpens your reasoning.
|
|
65
|
+
|
|
66
|
+
### Think Strategically, Not Tactically
|
|
67
|
+
- Tactical programming gets the feature done fast but leaves behind incremental complexity debt.
|
|
68
|
+
- Strategic programming invests a small ongoing cost in design quality to keep the system habitable long-term.
|
|
69
|
+
- Small tactical shortcuts compound into unmaintainable systems. Every change is a chance to improve structure, not just ship.
|
|
70
|
+
|
|
71
|
+
### Test Strategically
|
|
72
|
+
- Integration tests at system cut-points and critical user paths deliver the most value.
|
|
73
|
+
- Unit tests break easily during refactoring — favor coarser-grained tests.
|
|
74
|
+
- Minimize mocking. Mock only at system boundaries.
|
|
75
|
+
- Always write a regression test when a bug is found.
|
|
76
|
+
|
|
77
|
+
### Logging is Critical Infrastructure
|
|
78
|
+
- Log all major logical branches (if/for).
|
|
79
|
+
- Include request IDs for traceability across distributed calls.
|
|
80
|
+
- Make log levels dynamically controllable at runtime.
|
|
81
|
+
- Invest more in logging than you think necessary.
|
|
82
|
+
|
|
83
|
+
### APIs: Design for the Caller
|
|
84
|
+
- Think in terms of what the caller needs, not how the implementation works.
|
|
85
|
+
- Simple cases get simple APIs. Complexity is opt-in.
|
|
86
|
+
- Put common operations directly on objects with straightforward returns.
|
|
87
|
+
- Favor somewhat general-purpose interfaces — they tend to be deeper and simpler than hyper-specialized ones.
|
|
88
|
+
|
|
89
|
+
### Define Errors Out of Existence
|
|
90
|
+
- Exception handling generates enormous complexity. Where possible, design interfaces so error cases simply cannot occur.
|
|
91
|
+
- Handle edge cases internally rather than surfacing them to callers.
|
|
92
|
+
- Example: a delete operation that silently succeeds when the target doesn't exist is simpler than one that throws "not found."
|
|
93
|
+
|
|
94
|
+
### Concurrency: Keep it Simple
|
|
95
|
+
- Prefer stateless request handlers.
|
|
96
|
+
- Use simple job queues with independent jobs.
|
|
97
|
+
- Treat concurrency with healthy fear and caution.
|
|
98
|
+
|
|
99
|
+
### Optimize with Data, Not Gut
|
|
100
|
+
- Never optimize without a real-world profile showing the actual bottleneck.
|
|
101
|
+
- Network calls cost millions of CPU cycles — minimize those first.
|
|
102
|
+
- Assume your guess about the bottleneck is wrong.
|
|
103
|
+
|
|
104
|
+
### Locality of Behavior over Strict Separation
|
|
105
|
+
- Collocate related code. Putting logic near the thing it operates on aids understanding.
|
|
106
|
+
- Hunting across many files to understand one feature wastes time.
|
|
107
|
+
- Trade perfect separation of concerns for practical coherence when it helps readability.
|
|
108
|
+
|
|
109
|
+
### Information Hiding
|
|
110
|
+
- Each module should encapsulate design decisions that are likely to change.
|
|
111
|
+
- Leaking implementation details through interfaces creates tight coupling and change amplification.
|
|
112
|
+
- If two modules share knowledge about the same design decision, consider merging them or introducing a cleaner boundary.
|
|
113
|
+
|
|
114
|
+
### Tooling Multiplies Productivity
|
|
115
|
+
- Invest time learning your tools deeply (IDE, debugger, CLI).
|
|
116
|
+
- Good tools often double development speed.
|
|
117
|
+
|
|
118
|
+
### Avoid Fads
|
|
119
|
+
- Most "new" ideas have been tried before. Approach with skepticism.
|
|
120
|
+
- Don't adopt new frameworks or patterns blindly.
|
|
121
|
+
- Complexity hides behind novelty.
|
|
122
|
+
|
|
123
|
+
### Closures and Patterns
|
|
124
|
+
- Closures: great for collection operations, dangerous in excess (callback hell).
|
|
125
|
+
- Avoid the Visitor pattern — it adds complexity with little payoff.
|
|
126
|
+
- Limit generics to container classes; they attract unnecessary complexity.
|
|
127
|
+
|
|
128
|
+
### Frontend: Keep it Minimal
|
|
129
|
+
- Simple HTML + minimal JS beats elaborate SPA frameworks for most use cases.
|
|
130
|
+
- Frontend naturally accumulates complexity faster than backend — resist it actively.
|
|
131
|
+
|
|
132
|
+
### Say When You Don't Understand
|
|
133
|
+
- Admitting confusion is strength, not weakness.
|
|
134
|
+
- It gives others permission to ask questions and prevents bad complexity from hiding.
|
|
135
|
+
|
|
136
|
+
### Security is a Design Constraint
|
|
137
|
+
- Complexity is the enemy of security too. Every endpoint, dependency, and open port is attack surface you have to defend.
|
|
138
|
+
- Default to deny. Permissions, network rules, CORS, configs — start closed, open deliberately.
|
|
139
|
+
- Auth, crypto, and session management are not DIY projects. Use well-vetted libraries.
|
|
140
|
+
- Validate all input at the trust boundary, encode at the output. Everything from outside is hostile.
|
|
141
|
+
- Think in blast radius. Least privilege everything. One compromised key shouldn't unlock the whole system.
|
|
142
|
+
|
|
143
|
+
### Threat Model Like You Debug
|
|
144
|
+
- Before building a feature, ask "How would someone abuse this?" — same as asking where it will break.
|
|
145
|
+
- Dependencies are code you didn't write and probably didn't read. Pin versions, audit what matters.
|
|
146
|
+
- Secrets don't go in code, logs, or error messages. No exceptions.
|
|
147
|
+
- Secure your defaults everywhere — don't run as root, don't commit `.env`, don't connect local to prod.
|
|
148
|
+
- Vulnerabilities cluster. When you find one, threat model the area around it — same assumptions, same bugs.
|
|
149
|
+
|
|
150
|
+
## Tool Usage
|
|
151
|
+
|
|
152
|
+
Use the `uv` ecosystem tools.
|
|
153
|
+
|
|
154
|
+
- `uv run pytest`
|
|
155
|
+
- `uv run ruff`
|
|
156
|
+
- `uv add`
|
|
157
|
+
- `uv run python`
|
|
158
|
+
|
|
159
|
+
Do not use pip or python directly.
|
|
160
|
+
|
|
161
|
+
## Code Style hints
|
|
162
|
+
|
|
163
|
+
- 200 column soft limit on a line
|
|
164
|
+
- Use ternary style over if else blocks
|
|
165
|
+
- Only 1 line space between classes and functions instead of 2
|
|
166
|
+
- Keep code tight
|
tinyloom-0.1.3/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Thresher
|
|
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.
|
tinyloom-0.1.3/PKG-INFO
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tinyloom
|
|
3
|
+
Version: 0.1.3
|
|
4
|
+
Summary: A tiny, SDK-first coding agent harness
|
|
5
|
+
License-File: LICENSE
|
|
6
|
+
Requires-Python: >=3.11
|
|
7
|
+
Requires-Dist: anthropic>=0.40
|
|
8
|
+
Requires-Dist: openai>=1.50
|
|
9
|
+
Requires-Dist: python-dotenv>=1.2.2
|
|
10
|
+
Requires-Dist: pyyaml>=6.0
|
|
11
|
+
Requires-Dist: textual>=1.0
|
|
12
|
+
Requires-Dist: tiktoken>=0.7
|
|
13
|
+
Provides-Extra: dev
|
|
14
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
15
|
+
Requires-Dist: pytest-asyncio; extra == 'dev'
|
|
16
|
+
Requires-Dist: ruff; extra == 'dev'
|
|
17
|
+
Provides-Extra: mcp
|
|
18
|
+
Requires-Dist: mcp<2,>=1.0; extra == 'mcp'
|
tinyloom-0.1.3/README.md
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# tinyloom
|
|
2
|
+
|
|
3
|
+
A tiny, SDK-first coding agent harness in Python.
|
|
4
|
+
|
|
5
|
+
<img src="demo.webp" alt="tinyloom demo" loop="infinite">
|
|
6
|
+
|
|
7
|
+
## Why this exists
|
|
8
|
+
|
|
9
|
+
We needed an extremely tiny coding agent harness for [thresher](https://github.com/thresher-sh/thresher) and many harnesses just bring extra bloat we don't need. The harness bit is actually easy to implement -- it's all the extra bells and whistles that take a lot.
|
|
10
|
+
|
|
11
|
+
If you are looking for a bigger client, take a look at one of these:
|
|
12
|
+
|
|
13
|
+
- [pi-mono coding-agent](https://github.com/badlogic/pi-mono/tree/main/packages/coding-agent)
|
|
14
|
+
- [opencode](https://github.com/anomalyco/opencode)
|
|
15
|
+
|
|
16
|
+
## Safety
|
|
17
|
+
|
|
18
|
+
tinyloom has **no permission system, no approval gates, and no filesystem sandboxing** by default. The agent can read, write, delete, and execute anything your user account can. Do not run it outside of a container or sandbox on a machine you care about.
|
|
19
|
+
|
|
20
|
+
Use [microsandbox](docs/sandbox.md) or another isolation layer for untrusted workloads. If you want tool approval or allowlists, build a [plugin](docs/creating-plugins.md) or [hook](docs/custom-hooks.md) for it.
|
|
21
|
+
|
|
22
|
+
## Install
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
uv tool install tinyloom # or: pip install tinyloom
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Quick start
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
export ANTHROPIC_API_KEY=sk-ant-...
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
CLI:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
tinyloom "fix the bug in main.py" # headless, JSONL output
|
|
38
|
+
tinyloom # interactive TUI
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
SDK:
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
import asyncio
|
|
45
|
+
from tinyloom import Agent, load_config
|
|
46
|
+
|
|
47
|
+
async def main():
|
|
48
|
+
agent = Agent(load_config())
|
|
49
|
+
async for event in agent.run("create a hello.py"):
|
|
50
|
+
if event.type == "text_delta":
|
|
51
|
+
print(event.text, end="")
|
|
52
|
+
|
|
53
|
+
asyncio.run(main())
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
See [docs/getting-started.md](docs/getting-started.md) for full setup instructions.
|
|
57
|
+
|
|
58
|
+
## Features
|
|
59
|
+
|
|
60
|
+
- **Built-in tools**: `read`, `write`, `edit` (str_replace), `bash`
|
|
61
|
+
- **Providers**: Anthropic and OpenAI-compatible APIs (vLLM, Ollama, Together, Groq, LM Studio, Azure)
|
|
62
|
+
- **Compaction**: automatic context summarization when approaching the context window limit
|
|
63
|
+
- **Hooks**: react to any agent event (tool calls, messages, errors) with sync or async functions
|
|
64
|
+
- **Plugins**: extend the agent with tools, hooks, and custom logic
|
|
65
|
+
- **MCP**: connect to Model Context Protocol servers for external tools
|
|
66
|
+
- **TUI**: interactive terminal interface with streaming, slash commands, and token tracking
|
|
67
|
+
- **Sub-agents**: delegate focused tasks with the `subagent` plugin
|
|
68
|
+
|
|
69
|
+
## Config
|
|
70
|
+
|
|
71
|
+
Copy the example and edit:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
cp tinyloom.example.yaml tinyloom.yaml
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
```yaml
|
|
78
|
+
model:
|
|
79
|
+
provider: anthropic
|
|
80
|
+
model: claude-sonnet-4-20250514
|
|
81
|
+
context_window: 200000
|
|
82
|
+
|
|
83
|
+
system_prompt: You are a skilled coding assistant. Be concise.
|
|
84
|
+
|
|
85
|
+
compaction:
|
|
86
|
+
enabled: true
|
|
87
|
+
threshold: 0.8
|
|
88
|
+
strategy: summarize
|
|
89
|
+
|
|
90
|
+
plugins:
|
|
91
|
+
- tinyloom.plugins.todo
|
|
92
|
+
- tinyloom.plugins.mcp
|
|
93
|
+
- tinyloom.plugins.hook_scripts
|
|
94
|
+
|
|
95
|
+
max_turns: 200
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
API keys go in environment variables, not config. See [.env.example](.env.example).
|
|
99
|
+
|
|
100
|
+
## Want more features?
|
|
101
|
+
|
|
102
|
+
tinyloom is intentionally small. Extend it instead:
|
|
103
|
+
|
|
104
|
+
- **More tools?** Connect MCP servers -- see [docs/mcp-plugin.md](docs/mcp-plugin.md)
|
|
105
|
+
- **Custom logic?** Write a plugin -- see [docs/creating-plugins.md](docs/creating-plugins.md)
|
|
106
|
+
- **Approval gates? Logging? Filters?** Use hooks -- see [docs/custom-hooks.md](docs/custom-hooks.md) or [docs/hook-scripts.md](docs/hook-scripts.md)
|
|
107
|
+
- **Redact sensitive output?** Use the mask plugin -- see [docs/mask-plugin.md](docs/mask-plugin.md)
|
|
108
|
+
- **Different model provider?** Set `base_url` -- see [docs/custom-providers.md](docs/custom-providers.md)
|
|
109
|
+
|
|
110
|
+
## Docs
|
|
111
|
+
|
|
112
|
+
- [Getting Started](docs/getting-started.md)
|
|
113
|
+
- [Creating Plugins](docs/creating-plugins.md)
|
|
114
|
+
- [Custom Hooks](docs/custom-hooks.md)
|
|
115
|
+
- [Hook Scripts](docs/hook-scripts.md)
|
|
116
|
+
- [MCP Plugin](docs/mcp-plugin.md)
|
|
117
|
+
- [Custom Providers](docs/custom-providers.md)
|
|
118
|
+
- [Design Decisions](docs/design-decisions.md)
|
|
119
|
+
- [Mask Plugin](docs/mask-plugin.md)
|
|
120
|
+
- [Running in a Sandbox](docs/sandbox.md)
|
|
121
|
+
|
|
122
|
+
## Size
|
|
123
|
+
|
|
124
|
+
The whole thing is ~1,110 lines of Python (excluding blank lines) as of 2026.04.15.
|
|
125
|
+
|
|
126
|
+
| Area | Files | Lines |
|
|
127
|
+
|------|-------|-------|
|
|
128
|
+
| **core** (agent, tools, config, compaction, hooks, types) | 7 | 411 |
|
|
129
|
+
| **cli** | 1 | 48 |
|
|
130
|
+
| **tui** | 1 | 152 |
|
|
131
|
+
| **providers** (anthropic, openai, base) | 4 | 220 |
|
|
132
|
+
| **plugins** (subagent, todo, mcp, hook_scripts) | 5 | 263 |
|
|
133
|
+
|
|
134
|
+
## License
|
|
135
|
+
|
|
136
|
+
MIT
|
tinyloom-0.1.3/demo.tape
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Output demo.webp
|
|
2
|
+
Set Width 1200
|
|
3
|
+
Set Height 800
|
|
4
|
+
Set FontSize 20
|
|
5
|
+
Set Theme "Catppuccin Mocha"
|
|
6
|
+
Set TypingSpeed 50ms
|
|
7
|
+
|
|
8
|
+
Type "tinyloom"
|
|
9
|
+
Enter
|
|
10
|
+
Sleep 2s
|
|
11
|
+
|
|
12
|
+
Type "What is tinyloom?"
|
|
13
|
+
Enter
|
|
14
|
+
Sleep 12s
|
|
15
|
+
|
|
16
|
+
Type "/tokens"
|
|
17
|
+
Enter
|
|
18
|
+
Sleep 2s
|
|
19
|
+
|
|
20
|
+
Type "/quit"
|
|
21
|
+
Enter
|
|
22
|
+
Sleep 1s
|
tinyloom-0.1.3/demo.webp
ADDED
|
Binary file
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# Creating Plugins
|
|
2
|
+
|
|
3
|
+
A plugin is a function that receives the full `Agent` instance. It can register tools, add hooks, and modify config.
|
|
4
|
+
|
|
5
|
+
## Basic plugin
|
|
6
|
+
|
|
7
|
+
```python
|
|
8
|
+
# my_plugin.py
|
|
9
|
+
from tinyloom import Agent
|
|
10
|
+
|
|
11
|
+
def activate(agent: Agent):
|
|
12
|
+
"""Called when the plugin is loaded."""
|
|
13
|
+
print(f"Plugin loaded! Model: {agent.config.model.model}")
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Registering tools
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
from tinyloom import Agent, Tool
|
|
20
|
+
|
|
21
|
+
def activate(agent: Agent):
|
|
22
|
+
agent.tools.register(Tool(
|
|
23
|
+
name="timestamp",
|
|
24
|
+
description="Return the current UTC timestamp",
|
|
25
|
+
input_schema={"type": "object", "properties": {}},
|
|
26
|
+
function=lambda inp: __import__("datetime").datetime.utcnow().isoformat(),
|
|
27
|
+
))
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Adding hooks
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
from tinyloom import Agent
|
|
34
|
+
|
|
35
|
+
def activate(agent: Agent):
|
|
36
|
+
def log_tool_calls(ctx):
|
|
37
|
+
if ctx.get("tool_name"):
|
|
38
|
+
print(f"[audit] tool called: {ctx['tool_name']}")
|
|
39
|
+
|
|
40
|
+
agent.hooks.on("tool_call", log_tool_calls)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Modifying config
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
def activate(agent: Agent):
|
|
47
|
+
agent.config.max_turns = 50
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Activation
|
|
51
|
+
|
|
52
|
+
### Via config (recommended)
|
|
53
|
+
|
|
54
|
+
Add the module path to `tinyloom.yaml`:
|
|
55
|
+
|
|
56
|
+
```yaml
|
|
57
|
+
plugins:
|
|
58
|
+
- my_plugin # calls my_plugin.activate(agent)
|
|
59
|
+
- mypackage.plugins.custom # calls mypackage.plugins.custom.activate(agent)
|
|
60
|
+
- mypackage.stuff:setup # calls mypackage.stuff.setup(agent)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
By default, tinyloom calls the `activate` function. Use `:function_name` to specify a different entry point.
|
|
64
|
+
|
|
65
|
+
### Via entry_points (for distributable packages)
|
|
66
|
+
|
|
67
|
+
In your package's `pyproject.toml`:
|
|
68
|
+
|
|
69
|
+
```toml
|
|
70
|
+
[project.entry-points."tinyloom.plugins"]
|
|
71
|
+
my-plugin = "my_package.plugin:activate"
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Entry point plugins are discovered automatically when installed -- no config needed.
|
|
75
|
+
|
|
76
|
+
## Example: logging plugin
|
|
77
|
+
|
|
78
|
+
A plugin that logs every tool call and result to a file:
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
import json
|
|
82
|
+
from pathlib import Path
|
|
83
|
+
from tinyloom import Agent
|
|
84
|
+
|
|
85
|
+
def activate(agent: Agent):
|
|
86
|
+
log_file = Path("tinyloom-audit.jsonl")
|
|
87
|
+
|
|
88
|
+
def log_tool_call(ctx):
|
|
89
|
+
entry = {
|
|
90
|
+
"event": "tool_call",
|
|
91
|
+
"tool": ctx.get("tool_name", ""),
|
|
92
|
+
"input": str(ctx.get("tool_call", "")),
|
|
93
|
+
}
|
|
94
|
+
with log_file.open("a") as f:
|
|
95
|
+
f.write(json.dumps(entry) + "\n")
|
|
96
|
+
|
|
97
|
+
def log_tool_result(ctx):
|
|
98
|
+
entry = {
|
|
99
|
+
"event": "tool_result",
|
|
100
|
+
"tool": ctx.get("tool_name", ""),
|
|
101
|
+
"result_preview": str(ctx.get("result", ""))[:200],
|
|
102
|
+
}
|
|
103
|
+
with log_file.open("a") as f:
|
|
104
|
+
f.write(json.dumps(entry) + "\n")
|
|
105
|
+
|
|
106
|
+
agent.hooks.on("tool_call", log_tool_call)
|
|
107
|
+
agent.hooks.on("tool_result", log_tool_result)
|
|
108
|
+
```
|