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: Denis Burakov
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 Denis Burakov.
269
+ MIT - Copyright 2026 Almostly.
270
+
271
+ -----
272
+
273
+ ![Almostly](assets/almostly-badge.svg)
@@ -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 Denis Burakov.
246
+ MIT - Copyright 2026 Almostly.
247
+
248
+ -----
249
+
250
+ ![Almostly](assets/almostly-badge.svg)
@@ -9,7 +9,7 @@ description = "A minimal agentic coding assistant in a single Python file"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
11
11
  license = "MIT"
12
- authors = [{ name = "Denis Burakov" }]
12
+ authors = [{ name = "Almostly" }]
13
13
  keywords = [
14
14
  "agent",
15
15
  "llm",
@@ -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 Denis Burakov. All Rights Reserved.
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 (API key)",
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 (API key)",
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 (API key)",
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 (Anthropic-compatible server on localhost)",
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 (local models via `ollama serve`)",
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 (CPU/GPU, source install)",
116
+ "label": "HuggingFace Transformers (CPU/GPU, source install)",
114
117
  },
115
118
  "mlx": {
116
119
  "kind": "local-ml",
117
- "model": "mlx-community/Qwen2.5-3B-Instruct-4bit",
118
- "label": "Apple Silicon via MLX (source install)",
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 basic markdown bold syntax to terminal bold escape codes."""
580
- return re.sub(r"\*\*(.+?)\*\*", f"{BOLD}\\1{RESET}", text)
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"{BOLD}{BLUE}❯{RESET} ").strip()
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
- print(f"{RED}Error: {err}{RESET}")
1572
- traceback.print_exc()
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