wrencode 0.1.4__tar.gz → 0.1.4.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.
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wrencode
|
|
3
|
-
Version: 0.1.4
|
|
3
|
+
Version: 0.1.4.1
|
|
4
4
|
Summary: A minimal agentic coding assistant in a single Python file
|
|
5
5
|
Project-URL: Homepage, https://github.com/almostly/wrencode
|
|
6
6
|
Project-URL: Repository, https://github.com/almostly/wrencode
|
|
7
7
|
Project-URL: Issues, https://github.com/almostly/wrencode/issues
|
|
8
|
-
Author:
|
|
8
|
+
Author: Almostly
|
|
9
9
|
License-Expression: MIT
|
|
10
10
|
Keywords: agent,ai,anthropic,cli,coding-assistant,llm,mlx,ollama,openai
|
|
11
11
|
Classifier: Development Status :: 4 - Beta
|
|
@@ -55,6 +55,12 @@ saved choice, e.g. for CI.
|
|
|
55
55
|
The standalone binary can't bundle the heavy ML stack, so the local-weights
|
|
56
56
|
backends (`mlx`, `transformers`) are only offered when running from source.
|
|
57
57
|
|
|
58
|
+
The default local models are
|
|
59
|
+
[`deburky/gpt-oss-claude-code`](https://huggingface.co/deburky/gpt-oss-claude-code)
|
|
60
|
+
(transformers) and
|
|
61
|
+
[`deburky/gpt-oss-claude-mlx`](https://huggingface.co/deburky/gpt-oss-claude-mlx)
|
|
62
|
+
(MLX) — override either with `MODEL=...`.
|
|
63
|
+
|
|
58
64
|
## Tools
|
|
59
65
|
|
|
60
66
|
The agent has access to seven tools:
|
|
@@ -260,4 +266,8 @@ To clear history: use `/c` in the session, or delete `~/.wrencode/history.json`
|
|
|
260
266
|
|
|
261
267
|
## License
|
|
262
268
|
|
|
263
|
-
MIT - Copyright 2026
|
|
269
|
+
MIT - Copyright 2026 Almostly.
|
|
270
|
+
|
|
271
|
+
-----
|
|
272
|
+
|
|
273
|
+

|
|
@@ -32,6 +32,12 @@ saved choice, e.g. for CI.
|
|
|
32
32
|
The standalone binary can't bundle the heavy ML stack, so the local-weights
|
|
33
33
|
backends (`mlx`, `transformers`) are only offered when running from source.
|
|
34
34
|
|
|
35
|
+
The default local models are
|
|
36
|
+
[`deburky/gpt-oss-claude-code`](https://huggingface.co/deburky/gpt-oss-claude-code)
|
|
37
|
+
(transformers) and
|
|
38
|
+
[`deburky/gpt-oss-claude-mlx`](https://huggingface.co/deburky/gpt-oss-claude-mlx)
|
|
39
|
+
(MLX) — override either with `MODEL=...`.
|
|
40
|
+
|
|
35
41
|
## Tools
|
|
36
42
|
|
|
37
43
|
The agent has access to seven tools:
|
|
@@ -237,4 +243,8 @@ To clear history: use `/c` in the session, or delete `~/.wrencode/history.json`
|
|
|
237
243
|
|
|
238
244
|
## License
|
|
239
245
|
|
|
240
|
-
MIT - Copyright 2026
|
|
246
|
+
MIT - Copyright 2026 Almostly.
|
|
247
|
+
|
|
248
|
+
-----
|
|
249
|
+
|
|
250
|
+

|
|
@@ -8,14 +8,17 @@ HuggingFace Transformers, Anthropic, OpenAI, OpenRouter, and local proxy.
|
|
|
8
8
|
Provides a tool-calling agent loop with file read/write/edit, glob, grep,
|
|
9
9
|
and bash — enough to autonomously navigate and modify a codebase.
|
|
10
10
|
|
|
11
|
-
Copyright 2026
|
|
11
|
+
Copyright (c) 2026 Almostly
|
|
12
12
|
|
|
13
13
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
14
14
|
of this software and associated documentation files (the "Software"), to deal
|
|
15
15
|
in the Software without restriction, including without limitation the rights
|
|
16
16
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
17
17
|
copies of the Software, and to permit persons to whom the Software is
|
|
18
|
-
furnished to do so
|
|
18
|
+
furnished to do so, subject to the following conditions:
|
|
19
|
+
|
|
20
|
+
The above copyright notice and this permission notice shall be included in all
|
|
21
|
+
copies or substantial portions of the Software.
|
|
19
22
|
|
|
20
23
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
24
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
@@ -67,7 +70,7 @@ for _dir in (os.path.dirname(os.path.abspath(__file__)), os.getcwd()):
|
|
|
67
70
|
# -----------------------------------------------------------------------------------------------
|
|
68
71
|
# Backend Configuration
|
|
69
72
|
# -----------------------------------------------------------------------------------------------
|
|
70
|
-
WRENCODE_VERSION = "0.1.4"
|
|
73
|
+
WRENCODE_VERSION = "0.1.4.1"
|
|
71
74
|
|
|
72
75
|
# Per-backend defaults. "kind" controls how a backend is treated:
|
|
73
76
|
# api - hosted HTTP API, needs an API key
|
|
@@ -80,42 +83,42 @@ BACKEND_SPECS: dict[str, dict[str, str]] = {
|
|
|
80
83
|
"model": "claude-haiku-4-5-20251001",
|
|
81
84
|
"key_env": "ANTHROPIC_API_KEY",
|
|
82
85
|
"api_base": "https://api.anthropic.com/v1/messages",
|
|
83
|
-
"label": "Anthropic Claude
|
|
86
|
+
"label": "Anthropic Claude (API key)",
|
|
84
87
|
},
|
|
85
88
|
"openai": {
|
|
86
89
|
"kind": "api",
|
|
87
90
|
"model": "gpt-4o-mini",
|
|
88
91
|
"key_env": "OPENAI_API_KEY",
|
|
89
92
|
"api_base": "https://api.openai.com/v1/chat/completions",
|
|
90
|
-
"label": "OpenAI GPT
|
|
93
|
+
"label": "OpenAI GPT (API key)",
|
|
91
94
|
},
|
|
92
95
|
"openrouter": {
|
|
93
96
|
"kind": "api",
|
|
94
97
|
"model": "anthropic/claude-3-haiku",
|
|
95
98
|
"key_env": "OPENROUTER_API_KEY",
|
|
96
99
|
"api_base": "https://openrouter.ai/api/v1/chat/completions",
|
|
97
|
-
"label": "OpenRouter — any model
|
|
100
|
+
"label": "OpenRouter — any model (API key)",
|
|
98
101
|
},
|
|
99
102
|
"local": {
|
|
100
103
|
"kind": "local-proxy",
|
|
101
104
|
"model": "gpt-oss-20b",
|
|
102
105
|
"key_env": "LOCAL_API_KEY",
|
|
103
|
-
"label": "Local proxy
|
|
106
|
+
"label": "Local proxy (Anthropic-compatible server on localhost)",
|
|
104
107
|
},
|
|
105
108
|
"ollama": {
|
|
106
109
|
"kind": "local-proxy",
|
|
107
110
|
"model": "llama3.2",
|
|
108
|
-
"label": "Ollama
|
|
111
|
+
"label": "Ollama (local models via `ollama serve`)",
|
|
109
112
|
},
|
|
110
113
|
"transformers": {
|
|
111
114
|
"kind": "local-ml",
|
|
112
115
|
"model": "deburky/gpt-oss-claude-code",
|
|
113
|
-
"label": "HuggingFace Transformers
|
|
116
|
+
"label": "HuggingFace Transformers (CPU/GPU, source install)",
|
|
114
117
|
},
|
|
115
118
|
"mlx": {
|
|
116
119
|
"kind": "local-ml",
|
|
117
|
-
"model": "
|
|
118
|
-
"label": "Apple Silicon via MLX
|
|
120
|
+
"model": "deburky/gpt-oss-claude-mlx",
|
|
121
|
+
"label": "Apple Silicon via MLX (source install)",
|
|
119
122
|
},
|
|
120
123
|
}
|
|
121
124
|
|
|
@@ -575,9 +578,54 @@ def flatten_content(content: Any) -> str:
|
|
|
575
578
|
return "\n".join(parts)
|
|
576
579
|
|
|
577
580
|
|
|
581
|
+
# Lightweight, language-agnostic code coloring — no pygments, keeps the binary lean.
|
|
582
|
+
_CODE_TOKEN = re.compile(
|
|
583
|
+
r"(?P<comment>#[^\n]*|//[^\n]*)"
|
|
584
|
+
r"|(?P<string>\"[^\"\n]*\"|'[^'\n]*'|`[^`\n]*`)"
|
|
585
|
+
r"|(?P<num>\b\d[\d_.]*\b)"
|
|
586
|
+
r"|(?P<kw>\b(?:def|class|return|import|from|if|elif|else|for|while|try|except|"
|
|
587
|
+
r"finally|with|as|in|not|and|or|is|lambda|yield|async|await|pass|break|continue|"
|
|
588
|
+
r"raise|global|nonlocal|assert|True|False|None|const|let|var|function|export|"
|
|
589
|
+
r"default|new|this|fn|match|struct|enum|pub|use|mut|public|private|static|void)\b)"
|
|
590
|
+
)
|
|
591
|
+
|
|
592
|
+
|
|
593
|
+
def _highlight_code(code: str) -> str:
|
|
594
|
+
"""Apply light ANSI syntax coloring to a code block (best-effort, any language)."""
|
|
595
|
+
|
|
596
|
+
def color(m: "re.Match[str]") -> str:
|
|
597
|
+
g = m.lastgroup
|
|
598
|
+
if g == "comment":
|
|
599
|
+
return f"{DIM}{m.group()}{RESET}"
|
|
600
|
+
if g == "string":
|
|
601
|
+
return f"{GREEN}{m.group()}{RESET}"
|
|
602
|
+
if g == "num":
|
|
603
|
+
return f"{YELLOW}{m.group()}{RESET}"
|
|
604
|
+
if g == "kw":
|
|
605
|
+
return f"{BLUE}{m.group()}{RESET}"
|
|
606
|
+
return m.group()
|
|
607
|
+
|
|
608
|
+
return _CODE_TOKEN.sub(color, code)
|
|
609
|
+
|
|
610
|
+
|
|
578
611
|
def render_markdown(text: str) -> str:
|
|
579
|
-
"""Render
|
|
580
|
-
|
|
612
|
+
"""Render fenced code blocks (lightly highlighted), inline code, and bold."""
|
|
613
|
+
blocks: list[str] = []
|
|
614
|
+
|
|
615
|
+
def stash(m: "re.Match[str]") -> str:
|
|
616
|
+
lang = m.group(1) or ""
|
|
617
|
+
body = _highlight_code(m.group(2).rstrip("\n"))
|
|
618
|
+
head = f"{DIM}┌─ {lang}{RESET}\n" if lang else f"{DIM}┌─{RESET}\n"
|
|
619
|
+
bordered = "\n".join(f"{DIM}│{RESET} {ln}" for ln in body.split("\n"))
|
|
620
|
+
blocks.append(f"\n{head}{bordered}\n{DIM}└─{RESET}")
|
|
621
|
+
return f"\x00B{len(blocks) - 1}\x00"
|
|
622
|
+
|
|
623
|
+
text = re.sub(r"```(\w*)\n?(.*?)```", stash, text, flags=re.DOTALL)
|
|
624
|
+
text = re.sub(r"`([^`\n]+)`", f"{CYAN}\\1{RESET}", text)
|
|
625
|
+
text = re.sub(r"\*\*(.+?)\*\*", f"{BOLD}\\1{RESET}", text)
|
|
626
|
+
for i, b in enumerate(blocks):
|
|
627
|
+
text = text.replace(f"\x00B{i}\x00", b)
|
|
628
|
+
return text
|
|
581
629
|
|
|
582
630
|
|
|
583
631
|
# -----------------------------------------------------------------------------------------------
|
|
@@ -1486,6 +1534,29 @@ def load_model() -> Optional[tuple[Any, Any]]:
|
|
|
1486
1534
|
if BACKEND == "local":
|
|
1487
1535
|
print(f"{DIM}Local proxy at {API_BASE}{RESET}\n")
|
|
1488
1536
|
return None
|
|
1537
|
+
if BACKEND == "ollama":
|
|
1538
|
+
base = os.environ.get("OLLAMA_HOST", "http://localhost:11434").rstrip("/")
|
|
1539
|
+
try:
|
|
1540
|
+
with urllib.request.urlopen(f"{base}/api/tags", timeout=3) as r:
|
|
1541
|
+
installed = {
|
|
1542
|
+
m.get("name", "")
|
|
1543
|
+
for m in json.loads(r.read().decode()).get("models", [])
|
|
1544
|
+
}
|
|
1545
|
+
want = MODEL if ":" in MODEL else f"{MODEL}:latest"
|
|
1546
|
+
if not (
|
|
1547
|
+
MODEL in installed
|
|
1548
|
+
or want in installed
|
|
1549
|
+
or any(n.split(":")[0] == MODEL for n in installed)
|
|
1550
|
+
):
|
|
1551
|
+
have = ", ".join(sorted(installed)) or "none"
|
|
1552
|
+
print(f"{YELLOW}⚠ Model '{MODEL}' isn't pulled into Ollama.{RESET}")
|
|
1553
|
+
print(f"{DIM} Run: ollama pull {MODEL} (installed: {have}){RESET}")
|
|
1554
|
+
except Exception:
|
|
1555
|
+
print(
|
|
1556
|
+
f"{YELLOW}⚠ Couldn't reach Ollama at {base} — is `ollama serve` running?{RESET}"
|
|
1557
|
+
)
|
|
1558
|
+
print(f"{DIM}{BACKEND} ({MODEL}){RESET}\n")
|
|
1559
|
+
return None
|
|
1489
1560
|
# Hosted API backends — all require a key.
|
|
1490
1561
|
if not API_KEY:
|
|
1491
1562
|
key_env = BACKEND_SPECS[BACKEND]["key_env"]
|
|
@@ -1539,6 +1610,7 @@ def main() -> None:
|
|
|
1539
1610
|
)
|
|
1540
1611
|
resolve_configuration(force_chooser=force)
|
|
1541
1612
|
|
|
1613
|
+
sys.stdout.write("\033]0;wrencode\007") # set terminal tab/window title
|
|
1542
1614
|
print(WREN_BANNER)
|
|
1543
1615
|
print(f"{BOLD}wrencode{RESET} 🐦 | {DIM}{BACKEND}:{MODEL}{RESET}\n")
|
|
1544
1616
|
mlx_state = load_model()
|
|
@@ -1550,7 +1622,7 @@ def main() -> None:
|
|
|
1550
1622
|
|
|
1551
1623
|
while True:
|
|
1552
1624
|
try:
|
|
1553
|
-
user_input = input(f"{
|
|
1625
|
+
user_input = input(f"{BRIGHT_CYAN}❯{RESET} ").strip()
|
|
1554
1626
|
if not user_input:
|
|
1555
1627
|
continue
|
|
1556
1628
|
action = handle_slash_command(user_input, messages, mlx_state)
|
|
@@ -1568,8 +1640,17 @@ def main() -> None:
|
|
|
1568
1640
|
except EOFError:
|
|
1569
1641
|
break
|
|
1570
1642
|
except Exception as err:
|
|
1571
|
-
|
|
1572
|
-
|
|
1643
|
+
msg = str(err)
|
|
1644
|
+
print(f"{RED}Error: {msg}{RESET}")
|
|
1645
|
+
if BACKEND == "ollama" and ("not found" in msg.lower() or "404" in msg):
|
|
1646
|
+
print(
|
|
1647
|
+
f"{YELLOW}Model '{MODEL}' isn't pulled. "
|
|
1648
|
+
f"Run: ollama pull {MODEL} (or `ollama list`).{RESET}"
|
|
1649
|
+
)
|
|
1650
|
+
if os.environ.get("WRENCODE_DEBUG"):
|
|
1651
|
+
traceback.print_exc()
|
|
1652
|
+
else:
|
|
1653
|
+
print(f"{DIM}(set WRENCODE_DEBUG=1 for the full traceback){RESET}")
|
|
1573
1654
|
|
|
1574
1655
|
|
|
1575
1656
|
if __name__ == "__main__":
|
|
File without changes
|