greedy-token 0.2.1__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.
- greedy_token-0.2.1/LICENSE +21 -0
- greedy_token-0.2.1/PKG-INFO +151 -0
- greedy_token-0.2.1/README.md +135 -0
- greedy_token-0.2.1/pyproject.toml +30 -0
- greedy_token-0.2.1/setup.cfg +4 -0
- greedy_token-0.2.1/src/greedy_token/__init__.py +3 -0
- greedy_token-0.2.1/src/greedy_token/__main__.py +4 -0
- greedy_token-0.2.1/src/greedy_token/cli.py +205 -0
- greedy_token-0.2.1/src/greedy_token/config/routes.yaml +156 -0
- greedy_token-0.2.1/src/greedy_token/context_audit.py +105 -0
- greedy_token-0.2.1/src/greedy_token/estimator.py +97 -0
- greedy_token-0.2.1/src/greedy_token/executors.py +98 -0
- greedy_token-0.2.1/src/greedy_token/paths.py +32 -0
- greedy_token-0.2.1/src/greedy_token/prompt_compress.py +105 -0
- greedy_token-0.2.1/src/greedy_token/rag_search.py +128 -0
- greedy_token-0.2.1/src/greedy_token/router.py +340 -0
- greedy_token-0.2.1/src/greedy_token/tokens.py +121 -0
- greedy_token-0.2.1/src/greedy_token/wrappers.py +116 -0
- greedy_token-0.2.1/src/greedy_token.egg-info/PKG-INFO +151 -0
- greedy_token-0.2.1/src/greedy_token.egg-info/SOURCES.txt +22 -0
- greedy_token-0.2.1/src/greedy_token.egg-info/dependency_links.txt +1 -0
- greedy_token-0.2.1/src/greedy_token.egg-info/entry_points.txt +2 -0
- greedy_token-0.2.1/src/greedy_token.egg-info/requires.txt +2 -0
- greedy_token-0.2.1/src/greedy_token.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Stanislav Vasenkov
|
|
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,151 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: greedy-token
|
|
3
|
+
Version: 0.2.1
|
|
4
|
+
Summary: Route dev tasks through tool → python → ollama → RAG before escalating to Cursor
|
|
5
|
+
Author: Stanislav Vasenkov
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/svasenkov/greedy-token
|
|
8
|
+
Project-URL: Repository, https://github.com/svasenkov/greedy-token
|
|
9
|
+
Keywords: cli,llm,cursor,token-optimization,rag,ollama
|
|
10
|
+
Requires-Python: >=3.10
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: PyYAML>=6.0
|
|
14
|
+
Requires-Dist: tiktoken>=0.7
|
|
15
|
+
Dynamic: license-file
|
|
16
|
+
|
|
17
|
+
# greedy-token
|
|
18
|
+
|
|
19
|
+
Route dev tasks through **tool → python → ollama → RAG** before escalating to Cursor/Claude.
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
Your task → greedy-token → rg/jq | scripts | Ollama | docs/rag | Cursor
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Cursor vs greedy-token (token comparison)
|
|
26
|
+
|
|
27
|
+
Measured with `greedy-token audit-context` and `greedy-token estimate` against [zero-design-system](https://github.com/svasenkov/zero-design-system) (tiktoken `cl100k_base`; order of magnitude, not API billing).
|
|
28
|
+
|
|
29
|
+
### Cursor context overhead (every new chat)
|
|
30
|
+
|
|
31
|
+
| Context | Tokens | Charged when |
|
|
32
|
+
|---------|-------:|--------------|
|
|
33
|
+
| Always-on rules (`.cursor/rules/*.mdc`) | 2,524 | every chat |
|
|
34
|
+
| Skills on disk (`.cursor/skills/*/SKILL.md`) | 26,386 | if agent loads skill |
|
|
35
|
+
| Sampled docs (`CONTEXT.md`, `migration-prompts.md`) | 3,349 | if referenced |
|
|
36
|
+
| **Sampled set total** | **32,259** | full agent context |
|
|
37
|
+
| Naive agent baseline (`rules + 6k overhead + task`) | ~8,530 | default Cursor path |
|
|
38
|
+
|
|
39
|
+
Rules ≥ 1024 tokens → stable prefix is cache-friendly for Claude API prompt caching.
|
|
40
|
+
|
|
41
|
+
### Task routing: naive Cursor vs greedy-token
|
|
42
|
+
|
|
43
|
+
| Task | Naive Cursor | greedy-token route | Est. tokens | Saved vs Cursor | Savings | Command |
|
|
44
|
+
|------|-------------:|--------------------|------------:|----------------:|--------:|---------|
|
|
45
|
+
| `какой -D flag для baseUrl в e2e config` | ~8,534 | **rag** (95%) | 1,810 | ~6,724 | ~79% | `greedy-token rag "baseUrl -D flag"` |
|
|
46
|
+
| `ADR 002 baseUrl pattern` | ~8,530 | **rag** (61%) | 1,806 | ~6,724 | ~79% | `greedy-token rag "ADR 002 baseUrl"` |
|
|
47
|
+
| `find baseUrl in e2e properties` | ~8,532 | **tool** (59%) | 0 | ~8,532 | ~100% | `greedy-token run "…" --execute` → `rg` |
|
|
48
|
+
| `sync phase-manifest и skills-map` | ~8,532 | **python** (65%) | 0 | ~8,532 | ~100% | `greedy-token scripts --run check-meta-sync --execute` |
|
|
49
|
+
| `rsync template-project в monorepo` | ~8,533 | **python** (60%) | 0 | ~8,533 | ~100% | dry-run script; run manually |
|
|
50
|
+
| `batch inventory template-project` | ~8,532 | **ollama** (66%) | 0 cloud | ~8,532 | ~100% cloud | `scripts/ollama/batch-inventory.sh` (local LLM) |
|
|
51
|
+
| `refactor header layout and wire nav links` | ~8,535 | **cursor** (82%) | 8,535 | 0 | — | new Cursor chat + skill from `docs/skills-map.md` |
|
|
52
|
+
|
|
53
|
+
**Tier order:** `tool (rg/jq) → python → ollama → rag → cursor` — first match wins. Ollama tier is skipped when unavailable.
|
|
54
|
+
|
|
55
|
+
**Takeaway:** lookup / search / sync / bulk tasks save **~6.7k–8.5k tokens per request**; wiring and architecture correctly stay on Cursor.
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
greedy-token audit-context # your workspace overhead
|
|
59
|
+
greedy-token estimate "your task here" # route + savings before opening a chat
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Install once, point at your workspace root, route every task through the cheapest tier that can handle it.
|
|
63
|
+
|
|
64
|
+
## Install
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
pip install greedy-token
|
|
68
|
+
# or editable: pip install -e .
|
|
69
|
+
# or from git: pip install git+https://github.com/svasenkov/greedy-token.git
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`tiktoken` (exact BPE counts via `cl100k_base`) is a required dependency. If install fails
|
|
73
|
+
on an unsupported platform, use a Python version with a prebuilt `tiktoken` wheel or install
|
|
74
|
+
from source with a Rust toolchain.
|
|
75
|
+
|
|
76
|
+
### PyPI publish (maintainer)
|
|
77
|
+
|
|
78
|
+
1. Create project **greedy-token** on [pypi.org](https://pypi.org/manage/projects/)
|
|
79
|
+
2. Add trusted publisher: Owner `svasenkov`, repo `greedy-token`, workflow `publish.yml`
|
|
80
|
+
3. Publish: GitHub → Releases → re-run workflow or new tag
|
|
81
|
+
|
|
82
|
+
## Workspace root
|
|
83
|
+
|
|
84
|
+
`greedy-token` runs against a project directory (monorepo, app repo, etc.):
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
export GREEDY_TOKEN_ROOT=/path/to/your-workspace
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Auto-detect works when the workspace has `docs/phase-manifest.json` and `scripts/check-meta-sync.sh` (e.g. [zero-design-system](https://github.com/svasenkov/zero-design-system)). Otherwise set `GREEDY_TOKEN_ROOT` explicitly.
|
|
91
|
+
|
|
92
|
+
## Commands
|
|
93
|
+
|
|
94
|
+
| Command | Purpose |
|
|
95
|
+
|---------|---------|
|
|
96
|
+
| `greedy-token route "…"` | Recommend: tool \| python \| ollama \| rag \| cursor + scoring |
|
|
97
|
+
| `greedy-token estimate "…"` | Token-aware estimate: complexity, est_tokens, tier scan |
|
|
98
|
+
| `greedy-token run "…" [--execute]` | Route + dry-run / **read-only** execute |
|
|
99
|
+
| `greedy-token scripts --list` | List workspace script wrappers |
|
|
100
|
+
| `greedy-token scripts --run ID [--execute]` | Dry-run / execute read-only wrapper |
|
|
101
|
+
| `greedy-token audit-context` | Size of always-on rules/skills (tokens) |
|
|
102
|
+
| `greedy-token tokens PATH…` | Count tokens in files/directories |
|
|
103
|
+
| `greedy-token rag QUERY` | Search chunks in `docs/rag/` |
|
|
104
|
+
| `greedy-token compress` | Short prompt version (stdin; `--ollama` for LLM) |
|
|
105
|
+
|
|
106
|
+
## Tier order
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
tool (rg/jq) → python → ollama → rag → cursor
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
First matching tier wins. Ollama is **optional** — if unavailable, the tier is skipped.
|
|
113
|
+
|
|
114
|
+
## Examples
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
greedy-token route "find baseUrl"
|
|
118
|
+
# → tool (rg)
|
|
119
|
+
|
|
120
|
+
greedy-token estimate "refactor header layout"
|
|
121
|
+
# → cursor, complexity=high
|
|
122
|
+
|
|
123
|
+
greedy-token route "batch inventory template-project"
|
|
124
|
+
# → ollama
|
|
125
|
+
|
|
126
|
+
greedy-token route "sync phase-manifest and skills-map"
|
|
127
|
+
# → python
|
|
128
|
+
|
|
129
|
+
greedy-token route "ADR 002 baseUrl pattern"
|
|
130
|
+
# → rag
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Environment
|
|
134
|
+
|
|
135
|
+
| Var | Default |
|
|
136
|
+
|-----|---------|
|
|
137
|
+
| `GREEDY_TOKEN_ROOT` | auto-detect or required |
|
|
138
|
+
| `OLLAMA_URL` | `http://localhost:11434` |
|
|
139
|
+
| `OLLAMA_MODEL` | `qwen2.5-coder:14b` |
|
|
140
|
+
|
|
141
|
+
## `--execute`
|
|
142
|
+
|
|
143
|
+
Read-only only: `rg`, `jq`, `check-meta-sync.sh`. Rsync/migrate/ollama — dry-run; run manually.
|
|
144
|
+
|
|
145
|
+
## Route config
|
|
146
|
+
|
|
147
|
+
`src/greedy_token/config/routes.yaml` — customize patterns and commands for your workspace.
|
|
148
|
+
|
|
149
|
+
## License
|
|
150
|
+
|
|
151
|
+
MIT
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# greedy-token
|
|
2
|
+
|
|
3
|
+
Route dev tasks through **tool → python → ollama → RAG** before escalating to Cursor/Claude.
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
Your task → greedy-token → rg/jq | scripts | Ollama | docs/rag | Cursor
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Cursor vs greedy-token (token comparison)
|
|
10
|
+
|
|
11
|
+
Measured with `greedy-token audit-context` and `greedy-token estimate` against [zero-design-system](https://github.com/svasenkov/zero-design-system) (tiktoken `cl100k_base`; order of magnitude, not API billing).
|
|
12
|
+
|
|
13
|
+
### Cursor context overhead (every new chat)
|
|
14
|
+
|
|
15
|
+
| Context | Tokens | Charged when |
|
|
16
|
+
|---------|-------:|--------------|
|
|
17
|
+
| Always-on rules (`.cursor/rules/*.mdc`) | 2,524 | every chat |
|
|
18
|
+
| Skills on disk (`.cursor/skills/*/SKILL.md`) | 26,386 | if agent loads skill |
|
|
19
|
+
| Sampled docs (`CONTEXT.md`, `migration-prompts.md`) | 3,349 | if referenced |
|
|
20
|
+
| **Sampled set total** | **32,259** | full agent context |
|
|
21
|
+
| Naive agent baseline (`rules + 6k overhead + task`) | ~8,530 | default Cursor path |
|
|
22
|
+
|
|
23
|
+
Rules ≥ 1024 tokens → stable prefix is cache-friendly for Claude API prompt caching.
|
|
24
|
+
|
|
25
|
+
### Task routing: naive Cursor vs greedy-token
|
|
26
|
+
|
|
27
|
+
| Task | Naive Cursor | greedy-token route | Est. tokens | Saved vs Cursor | Savings | Command |
|
|
28
|
+
|------|-------------:|--------------------|------------:|----------------:|--------:|---------|
|
|
29
|
+
| `какой -D flag для baseUrl в e2e config` | ~8,534 | **rag** (95%) | 1,810 | ~6,724 | ~79% | `greedy-token rag "baseUrl -D flag"` |
|
|
30
|
+
| `ADR 002 baseUrl pattern` | ~8,530 | **rag** (61%) | 1,806 | ~6,724 | ~79% | `greedy-token rag "ADR 002 baseUrl"` |
|
|
31
|
+
| `find baseUrl in e2e properties` | ~8,532 | **tool** (59%) | 0 | ~8,532 | ~100% | `greedy-token run "…" --execute` → `rg` |
|
|
32
|
+
| `sync phase-manifest и skills-map` | ~8,532 | **python** (65%) | 0 | ~8,532 | ~100% | `greedy-token scripts --run check-meta-sync --execute` |
|
|
33
|
+
| `rsync template-project в monorepo` | ~8,533 | **python** (60%) | 0 | ~8,533 | ~100% | dry-run script; run manually |
|
|
34
|
+
| `batch inventory template-project` | ~8,532 | **ollama** (66%) | 0 cloud | ~8,532 | ~100% cloud | `scripts/ollama/batch-inventory.sh` (local LLM) |
|
|
35
|
+
| `refactor header layout and wire nav links` | ~8,535 | **cursor** (82%) | 8,535 | 0 | — | new Cursor chat + skill from `docs/skills-map.md` |
|
|
36
|
+
|
|
37
|
+
**Tier order:** `tool (rg/jq) → python → ollama → rag → cursor` — first match wins. Ollama tier is skipped when unavailable.
|
|
38
|
+
|
|
39
|
+
**Takeaway:** lookup / search / sync / bulk tasks save **~6.7k–8.5k tokens per request**; wiring and architecture correctly stay on Cursor.
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
greedy-token audit-context # your workspace overhead
|
|
43
|
+
greedy-token estimate "your task here" # route + savings before opening a chat
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Install once, point at your workspace root, route every task through the cheapest tier that can handle it.
|
|
47
|
+
|
|
48
|
+
## Install
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pip install greedy-token
|
|
52
|
+
# or editable: pip install -e .
|
|
53
|
+
# or from git: pip install git+https://github.com/svasenkov/greedy-token.git
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
`tiktoken` (exact BPE counts via `cl100k_base`) is a required dependency. If install fails
|
|
57
|
+
on an unsupported platform, use a Python version with a prebuilt `tiktoken` wheel or install
|
|
58
|
+
from source with a Rust toolchain.
|
|
59
|
+
|
|
60
|
+
### PyPI publish (maintainer)
|
|
61
|
+
|
|
62
|
+
1. Create project **greedy-token** on [pypi.org](https://pypi.org/manage/projects/)
|
|
63
|
+
2. Add trusted publisher: Owner `svasenkov`, repo `greedy-token`, workflow `publish.yml`
|
|
64
|
+
3. Publish: GitHub → Releases → re-run workflow or new tag
|
|
65
|
+
|
|
66
|
+
## Workspace root
|
|
67
|
+
|
|
68
|
+
`greedy-token` runs against a project directory (monorepo, app repo, etc.):
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
export GREEDY_TOKEN_ROOT=/path/to/your-workspace
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Auto-detect works when the workspace has `docs/phase-manifest.json` and `scripts/check-meta-sync.sh` (e.g. [zero-design-system](https://github.com/svasenkov/zero-design-system)). Otherwise set `GREEDY_TOKEN_ROOT` explicitly.
|
|
75
|
+
|
|
76
|
+
## Commands
|
|
77
|
+
|
|
78
|
+
| Command | Purpose |
|
|
79
|
+
|---------|---------|
|
|
80
|
+
| `greedy-token route "…"` | Recommend: tool \| python \| ollama \| rag \| cursor + scoring |
|
|
81
|
+
| `greedy-token estimate "…"` | Token-aware estimate: complexity, est_tokens, tier scan |
|
|
82
|
+
| `greedy-token run "…" [--execute]` | Route + dry-run / **read-only** execute |
|
|
83
|
+
| `greedy-token scripts --list` | List workspace script wrappers |
|
|
84
|
+
| `greedy-token scripts --run ID [--execute]` | Dry-run / execute read-only wrapper |
|
|
85
|
+
| `greedy-token audit-context` | Size of always-on rules/skills (tokens) |
|
|
86
|
+
| `greedy-token tokens PATH…` | Count tokens in files/directories |
|
|
87
|
+
| `greedy-token rag QUERY` | Search chunks in `docs/rag/` |
|
|
88
|
+
| `greedy-token compress` | Short prompt version (stdin; `--ollama` for LLM) |
|
|
89
|
+
|
|
90
|
+
## Tier order
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
tool (rg/jq) → python → ollama → rag → cursor
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
First matching tier wins. Ollama is **optional** — if unavailable, the tier is skipped.
|
|
97
|
+
|
|
98
|
+
## Examples
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
greedy-token route "find baseUrl"
|
|
102
|
+
# → tool (rg)
|
|
103
|
+
|
|
104
|
+
greedy-token estimate "refactor header layout"
|
|
105
|
+
# → cursor, complexity=high
|
|
106
|
+
|
|
107
|
+
greedy-token route "batch inventory template-project"
|
|
108
|
+
# → ollama
|
|
109
|
+
|
|
110
|
+
greedy-token route "sync phase-manifest and skills-map"
|
|
111
|
+
# → python
|
|
112
|
+
|
|
113
|
+
greedy-token route "ADR 002 baseUrl pattern"
|
|
114
|
+
# → rag
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Environment
|
|
118
|
+
|
|
119
|
+
| Var | Default |
|
|
120
|
+
|-----|---------|
|
|
121
|
+
| `GREEDY_TOKEN_ROOT` | auto-detect or required |
|
|
122
|
+
| `OLLAMA_URL` | `http://localhost:11434` |
|
|
123
|
+
| `OLLAMA_MODEL` | `qwen2.5-coder:14b` |
|
|
124
|
+
|
|
125
|
+
## `--execute`
|
|
126
|
+
|
|
127
|
+
Read-only only: `rg`, `jq`, `check-meta-sync.sh`. Rsync/migrate/ollama — dry-run; run manually.
|
|
128
|
+
|
|
129
|
+
## Route config
|
|
130
|
+
|
|
131
|
+
`src/greedy_token/config/routes.yaml` — customize patterns and commands for your workspace.
|
|
132
|
+
|
|
133
|
+
## License
|
|
134
|
+
|
|
135
|
+
MIT
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "greedy-token"
|
|
7
|
+
version = "0.2.1"
|
|
8
|
+
description = "Route dev tasks through tool → python → ollama → RAG before escalating to Cursor"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Stanislav Vasenkov" }]
|
|
13
|
+
keywords = ["cli", "llm", "cursor", "token-optimization", "rag", "ollama"]
|
|
14
|
+
dependencies = [
|
|
15
|
+
"PyYAML>=6.0",
|
|
16
|
+
"tiktoken>=0.7",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
[project.scripts]
|
|
20
|
+
greedy-token = "greedy_token.cli:main"
|
|
21
|
+
|
|
22
|
+
[project.urls]
|
|
23
|
+
Homepage = "https://github.com/svasenkov/greedy-token"
|
|
24
|
+
Repository = "https://github.com/svasenkov/greedy-token"
|
|
25
|
+
|
|
26
|
+
[tool.setuptools.packages.find]
|
|
27
|
+
where = ["src"]
|
|
28
|
+
|
|
29
|
+
[tool.setuptools.package-data]
|
|
30
|
+
greedy_token = ["config/*.yaml"]
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
from greedy_token.context_audit import audit_context, render_audit
|
|
7
|
+
from greedy_token.estimator import estimate_task, format_estimate
|
|
8
|
+
from greedy_token.executors import execute_plan, plan_run
|
|
9
|
+
from greedy_token.paths import find_monorepo_root
|
|
10
|
+
from greedy_token.prompt_compress import compress_prompt, format_dual
|
|
11
|
+
from greedy_token.rag_search import format_hits, search_rag
|
|
12
|
+
from greedy_token.router import format_decision, route_task
|
|
13
|
+
from greedy_token.tokens import TokenEstimate, collect_paths, count_files, format_size_table
|
|
14
|
+
from greedy_token.wrappers import WRAPPERS, ollama_status_line, resolve_wrapper_command
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def cmd_route(args: argparse.Namespace) -> int:
|
|
18
|
+
root = find_monorepo_root()
|
|
19
|
+
decision = route_task(args.task, root)
|
|
20
|
+
print(format_decision(decision, args.task, root))
|
|
21
|
+
return 0
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def cmd_estimate(args: argparse.Namespace) -> int:
|
|
25
|
+
root = find_monorepo_root()
|
|
26
|
+
estimate = estimate_task(args.task, root)
|
|
27
|
+
print(format_estimate(estimate, args.task, root))
|
|
28
|
+
return 0
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def cmd_run(args: argparse.Namespace) -> int:
|
|
32
|
+
root = find_monorepo_root()
|
|
33
|
+
decision = route_task(args.task, root)
|
|
34
|
+
plan = plan_run(decision, args.task, root)
|
|
35
|
+
print(f"Route: {decision.target} ({decision.route_id})")
|
|
36
|
+
print(f"Complexity: {decision.complexity} Est. tokens: {decision.est_tokens:,}")
|
|
37
|
+
print()
|
|
38
|
+
if args.execute:
|
|
39
|
+
code, out = execute_plan(plan)
|
|
40
|
+
print(out)
|
|
41
|
+
return code
|
|
42
|
+
print(plan.dry_run_output)
|
|
43
|
+
if plan.command:
|
|
44
|
+
if plan.executable:
|
|
45
|
+
print("\n(read-only — add --execute to run)")
|
|
46
|
+
else:
|
|
47
|
+
print("\n(not read-only — dry-run only)")
|
|
48
|
+
return 0
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def cmd_audit_context(_: argparse.Namespace) -> int:
|
|
52
|
+
items = audit_context()
|
|
53
|
+
print(render_audit(items))
|
|
54
|
+
return 0
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def cmd_tokens(args: argparse.Namespace) -> int:
|
|
58
|
+
root = find_monorepo_root()
|
|
59
|
+
paths = collect_paths(args.paths, root)
|
|
60
|
+
if not paths:
|
|
61
|
+
print("No files found.", file=sys.stderr)
|
|
62
|
+
return 1
|
|
63
|
+
estimates = count_files(paths)
|
|
64
|
+
rows = []
|
|
65
|
+
total_chars = 0
|
|
66
|
+
total_tokens = 0
|
|
67
|
+
method = "heuristic/4"
|
|
68
|
+
for p, est in zip(paths, estimates):
|
|
69
|
+
rel = str(p.relative_to(root)) if p.is_relative_to(root) else str(p)
|
|
70
|
+
rows.append((rel, est))
|
|
71
|
+
total_chars += est.chars
|
|
72
|
+
total_tokens += est.tokens
|
|
73
|
+
method = est.method
|
|
74
|
+
rows.sort(key=lambda r: -r[1].tokens)
|
|
75
|
+
total = TokenEstimate(tokens=total_tokens, chars=total_chars, method=method)
|
|
76
|
+
print(format_size_table(rows, total))
|
|
77
|
+
return 0
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def cmd_rag(args: argparse.Namespace) -> int:
|
|
81
|
+
root = find_monorepo_root()
|
|
82
|
+
domains = args.domain.split(",") if args.domain else None
|
|
83
|
+
hits = search_rag(args.query, root, domains=domains, limit=args.limit)
|
|
84
|
+
print(format_hits(args.query, hits))
|
|
85
|
+
return 0
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def cmd_compress(args: argparse.Namespace) -> int:
|
|
89
|
+
text = sys.stdin.read()
|
|
90
|
+
if not text.strip():
|
|
91
|
+
print("Read prompt from stdin.", file=sys.stderr)
|
|
92
|
+
return 1
|
|
93
|
+
short = compress_prompt(text, use_ollama=args.ollama)
|
|
94
|
+
if args.raw:
|
|
95
|
+
print(short)
|
|
96
|
+
else:
|
|
97
|
+
print(format_dual(text, short))
|
|
98
|
+
return 0
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def cmd_scripts(args: argparse.Namespace) -> int:
|
|
102
|
+
root = find_monorepo_root()
|
|
103
|
+
if args.list:
|
|
104
|
+
lines = ["Script wrappers (scripts/ollama | migrate | check-meta-sync):", ""]
|
|
105
|
+
for wrapper in WRAPPERS.values():
|
|
106
|
+
ro = "read-only" if wrapper.read_only else "writes"
|
|
107
|
+
oll = " +ollama" if wrapper.requires_ollama else ""
|
|
108
|
+
lines.append(f" {wrapper.id:<20} [{wrapper.category}] {ro}{oll}")
|
|
109
|
+
lines.append(f" {wrapper.path}")
|
|
110
|
+
if wrapper.note:
|
|
111
|
+
lines.append(f" {wrapper.note}")
|
|
112
|
+
lines.append("")
|
|
113
|
+
lines.append(ollama_status_line())
|
|
114
|
+
print("\n".join(lines))
|
|
115
|
+
return 0
|
|
116
|
+
if args.run:
|
|
117
|
+
try:
|
|
118
|
+
cmd = resolve_wrapper_command(args.run, root, extra_args=args.args or "")
|
|
119
|
+
except (KeyError, FileNotFoundError) as exc:
|
|
120
|
+
print(exc, file=sys.stderr)
|
|
121
|
+
return 1
|
|
122
|
+
wrapper = WRAPPERS[args.run]
|
|
123
|
+
if args.execute:
|
|
124
|
+
if not wrapper.read_only:
|
|
125
|
+
print(
|
|
126
|
+
f"Refusing --execute: {args.run} is not read-only.",
|
|
127
|
+
file=sys.stderr,
|
|
128
|
+
)
|
|
129
|
+
return 1
|
|
130
|
+
import subprocess
|
|
131
|
+
|
|
132
|
+
proc = subprocess.run(cmd, shell=True)
|
|
133
|
+
return proc.returncode
|
|
134
|
+
print(cmd)
|
|
135
|
+
if wrapper.read_only:
|
|
136
|
+
print("\n(read-only — add --execute to run)")
|
|
137
|
+
else:
|
|
138
|
+
print("\n(not read-only — dry-run only)")
|
|
139
|
+
return 0
|
|
140
|
+
print("Use scripts --list or scripts --run ID", file=sys.stderr)
|
|
141
|
+
return 1
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
145
|
+
p = argparse.ArgumentParser(
|
|
146
|
+
prog="greedy-token",
|
|
147
|
+
description="Task orchestrator: tool | Python | Ollama | RAG | Cursor",
|
|
148
|
+
)
|
|
149
|
+
sub = p.add_subparsers(dest="command", required=True)
|
|
150
|
+
|
|
151
|
+
r = sub.add_parser("route", help="Recommend executor for a task")
|
|
152
|
+
r.add_argument("task", help="Natural language task description")
|
|
153
|
+
r.set_defaults(func=cmd_route)
|
|
154
|
+
|
|
155
|
+
est = sub.add_parser("estimate", help="Token-aware route estimate with tier scan")
|
|
156
|
+
est.add_argument("task", help="Task description")
|
|
157
|
+
est.set_defaults(func=cmd_estimate)
|
|
158
|
+
|
|
159
|
+
run = sub.add_parser("run", help="Route and show/run command")
|
|
160
|
+
run.add_argument("task", help="Task description")
|
|
161
|
+
run.add_argument(
|
|
162
|
+
"--execute",
|
|
163
|
+
action="store_true",
|
|
164
|
+
help="Execute read-only tool/python commands only",
|
|
165
|
+
)
|
|
166
|
+
run.set_defaults(func=cmd_run)
|
|
167
|
+
|
|
168
|
+
sub.add_parser("audit-context", help="Token audit of rules/skills").set_defaults(
|
|
169
|
+
func=cmd_audit_context
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
t = sub.add_parser("tokens", help="Count tokens in paths")
|
|
173
|
+
t.add_argument("paths", nargs="+", help="Files or directories")
|
|
174
|
+
t.set_defaults(func=cmd_tokens)
|
|
175
|
+
|
|
176
|
+
rag = sub.add_parser("rag", help="Search docs/rag chunks")
|
|
177
|
+
rag.add_argument("query", help="Search query")
|
|
178
|
+
rag.add_argument("--domain", help="Comma-separated domains filter")
|
|
179
|
+
rag.add_argument("--limit", type=int, default=5)
|
|
180
|
+
rag.set_defaults(func=cmd_rag)
|
|
181
|
+
|
|
182
|
+
c = sub.add_parser("compress", help="Short agent prompt from stdin")
|
|
183
|
+
c.add_argument("--ollama", action="store_true", help="Use local Ollama")
|
|
184
|
+
c.add_argument("--raw", action="store_true", help="Print short text only")
|
|
185
|
+
c.set_defaults(func=cmd_compress)
|
|
186
|
+
|
|
187
|
+
scr = sub.add_parser("scripts", help="Wrappers for monorepo scripts")
|
|
188
|
+
scr.add_argument("--list", action="store_true", help="List script wrappers")
|
|
189
|
+
scr.add_argument("--run", metavar="ID", help="Wrapper id (e.g. check-meta-sync)")
|
|
190
|
+
scr.add_argument("args", nargs="?", default="", help="Extra args for script")
|
|
191
|
+
scr.add_argument(
|
|
192
|
+
"--execute",
|
|
193
|
+
action="store_true",
|
|
194
|
+
help="Run read-only wrapper only",
|
|
195
|
+
)
|
|
196
|
+
scr.set_defaults(func=cmd_scripts)
|
|
197
|
+
|
|
198
|
+
return p
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def main(argv: list[str] | None = None) -> None:
|
|
202
|
+
parser = build_parser()
|
|
203
|
+
args = parser.parse_args(argv)
|
|
204
|
+
code = args.func(args)
|
|
205
|
+
raise SystemExit(code)
|