vaal-code 0.6.910__tar.gz → 0.6.930__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.
- {vaal_code-0.6.910 → vaal_code-0.6.930}/PKG-INFO +1 -1
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/__init__.py +1 -1
- vaal_code-0.6.930/vaal/core/buddy.py +349 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/core/command_palette.py +1 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/core/repl.py +88 -2
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal_code.egg-info/PKG-INFO +1 -1
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal_code.egg-info/SOURCES.txt +1 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/README.md +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/pyproject.toml +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/setup.cfg +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/tests/test_all.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/__main__.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/core/__init__.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/core/agent.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/core/auto_fix.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/core/bootstrap.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/core/branching.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/core/bridge.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/core/changelog.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/core/clipboard.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/core/code_review.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/core/collaborative.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/core/config.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/core/context.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/core/cost_tracker.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/core/cot_display.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/core/deps.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/core/env_manager.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/core/error_explain.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/core/font_screen.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/core/generate_icon.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/core/history.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/core/hooks.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/core/image_input.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/core/init_project.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/core/interactive.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/core/keybindings.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/core/llm.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/core/login_screen.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/core/logout_screen.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/core/mcp_client.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/core/memory.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/core/migration.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/core/model_manager.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/core/multiline.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/core/notifications.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/core/oauth.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/core/output_styles.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/core/permissions.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/core/plans.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/core/plugins.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/core/preview_overlay.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/core/preview_server.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/core/progress.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/core/providers.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/core/raw_input.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/core/remote_control.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/core/scheduler.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/core/self_repair.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/core/server.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/core/session.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/core/skills.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/core/split_pane.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/core/ssh_remote.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/core/task_manager.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/core/terminal_profile.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/core/terminal_theme.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/core/theme.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/core/theme_screen.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/core/tool_executor.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/core/undo_system.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/core/user_config.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/core/vim_mode.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/core/voice.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/core/watch_mode.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/tools/__init__.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/tools/agent_tools.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/tools/api_tools.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/tools/bash_tool.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/tools/db_tools.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/tools/diff_tools.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/tools/file_tools.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/tools/git_tools.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/tools/interaction_tools.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/tools/lsp_tools.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/tools/memory_tools.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/tools/notebook_tools.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/tools/plugin_tools.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/tools/regex_tools.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/tools/registry.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/tools/search_tools.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/tools/sql_tools.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/tools/ssh_tools.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/tools/todo_tools.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal/tools/web_tools.py +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal_code.egg-info/dependency_links.txt +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal_code.egg-info/entry_points.txt +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal_code.egg-info/requires.txt +0 -0
- {vaal_code-0.6.910 → vaal_code-0.6.930}/vaal_code.egg-info/top_level.txt +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""Vaal — Local LLM coding assistant CLI."""
|
|
2
|
-
__version__ = "0.6.
|
|
2
|
+
__version__ = "0.6.930"
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
"""Vaal Buddy — procedurally generated ASCII companion with personality."""
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import json
|
|
5
|
+
import time
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from .config import VAAL_DIR
|
|
8
|
+
|
|
9
|
+
BUDDY_FILE = VAAL_DIR / "buddy.json"
|
|
10
|
+
|
|
11
|
+
RARITIES = ["common", "uncommon", "rare", "epic", "legendary"]
|
|
12
|
+
RARITY_WEIGHTS = {"common": 60, "uncommon": 25, "rare": 10, "epic": 4, "legendary": 1}
|
|
13
|
+
RARITY_STARS = {"common": "★", "uncommon": "★★", "rare": "★★★", "epic": "★★★★", "legendary": "★★★★★"}
|
|
14
|
+
RARITY_COLORS = {"common": "dim", "uncommon": "#5fd75f", "rare": "#5f87ff", "epic": "#af5fff", "legendary": "#d7d75f"}
|
|
15
|
+
|
|
16
|
+
SPECIES = [
|
|
17
|
+
"duck", "goose", "blob", "cat", "dragon", "octopus", "owl",
|
|
18
|
+
"penguin", "turtle", "snail", "ghost", "axolotl", "capybara",
|
|
19
|
+
"cactus", "robot", "rabbit", "mushroom", "chonk",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
EYES = ["·", "✦", "×", "◉", "@", "°"]
|
|
23
|
+
|
|
24
|
+
HATS = ["none", "crown", "tophat", "propeller", "halo", "wizard", "beanie", "tinyduck"]
|
|
25
|
+
|
|
26
|
+
STATS = ["DEBUGGING", "PATIENCE", "CHAOS", "WISDOM", "SNARK"]
|
|
27
|
+
|
|
28
|
+
# ASCII sprites — 5 lines tall, {E} = eye char
|
|
29
|
+
SPRITES = {
|
|
30
|
+
"duck": [' ', ' __ ', ' <({E} )___ ', ' ( ._> ', ' `--´ '],
|
|
31
|
+
"goose": [' ', ' ({E}> ', ' || ', ' _(__)_ ', ' ^^^^ '],
|
|
32
|
+
"blob": [' ', ' .----. ', ' ( {E} {E} ) ', ' ( ) ', ' `----´ '],
|
|
33
|
+
"cat": [' ', ' /\\_/\\ ', ' ( {E} {E}) ', ' ( ω ) ', ' (")_(") '],
|
|
34
|
+
"dragon": [' ', ' /^\\ /^\\ ', ' < {E} {E} > ', ' ( ~~ ) ', ' `-vvvv-´ '],
|
|
35
|
+
"octopus": [' ', ' .----. ', ' ( {E} {E} ) ', ' (______) ', ' /\\/\\/\\/\\ '],
|
|
36
|
+
"owl": [' ', ' /\\ /\\ ', ' (({E})({E})) ', ' ( >< ) ', ' `----´ '],
|
|
37
|
+
"penguin": [' ', ' .---. ', ' ({E}>{E}) ', ' /( )\\ ', ' `---´ '],
|
|
38
|
+
"turtle": [' ', ' _,--._ ', ' ( {E} {E} ) ', ' /[______]\\ ', ' `` `` '],
|
|
39
|
+
"snail": [' ', ' {E} .--. ', ' \\ ( @ ) ', ' \\_`--´ ', ' ~~~~~~~ '],
|
|
40
|
+
"ghost": [' ', ' .----. ', ' / {E} {E} \\ ', ' | | ', ' ~`~``~`~ '],
|
|
41
|
+
"axolotl": [' ', '}~(______)~{', '}~({E} .. {E})~{', ' ( .--. ) ', ' (_/ \\_) '],
|
|
42
|
+
"capybara": [' ', ' n______n ', ' ( {E} {E} ) ', ' ( oo ) ', ' `------´ '],
|
|
43
|
+
"cactus": [' ', ' n ____ n ', ' | |{E} {E}| | ', ' |_| |_| ', ' | | '],
|
|
44
|
+
"robot": [' ', ' .[||]. ', ' [ {E} {E} ] ', ' [ ==== ] ', ' `------´ '],
|
|
45
|
+
"rabbit": [' ', ' (\\__/) ', ' ( {E} {E} ) ', ' =( .. )= ', ' (")__(") '],
|
|
46
|
+
"mushroom": [' ', ' .-o-OO-o-. ', '(__________)', ' |{E} {E}| ', ' |____| '],
|
|
47
|
+
"chonk": [' ', ' /\\ /\\ ', ' ( {E} {E} ) ', ' ( .. ) ', ' `------´ '],
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
HAT_LINES = {
|
|
51
|
+
"none": "",
|
|
52
|
+
"crown": " \\^^^/ ",
|
|
53
|
+
"tophat": " [___] ",
|
|
54
|
+
"propeller":" -+- ",
|
|
55
|
+
"halo": " ( ) ",
|
|
56
|
+
"wizard": " /^\\ ",
|
|
57
|
+
"beanie": " (___) ",
|
|
58
|
+
"tinyduck": " ,> ",
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _mulberry32(seed: int):
|
|
63
|
+
"""Seeded PRNG."""
|
|
64
|
+
a = seed & 0xFFFFFFFF
|
|
65
|
+
def next_val():
|
|
66
|
+
nonlocal a
|
|
67
|
+
a = (a + 0x6D2B79F5) & 0xFFFFFFFF
|
|
68
|
+
t = ((a ^ (a >> 15)) * (1 | a)) & 0xFFFFFFFF
|
|
69
|
+
t = ((t + ((t ^ (t >> 7)) * (61 | t))) ^ t) & 0xFFFFFFFF
|
|
70
|
+
return ((t ^ (t >> 14)) & 0xFFFFFFFF) / 4294967296
|
|
71
|
+
return next_val
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _hash_string(s: str) -> int:
|
|
75
|
+
"""FNV-1a hash to match Claude Code's buddy generation."""
|
|
76
|
+
h = 2166136261
|
|
77
|
+
for ch in s:
|
|
78
|
+
h ^= ord(ch)
|
|
79
|
+
h = (h * 16777619) & 0xFFFFFFFF
|
|
80
|
+
return h
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _pick(rng, arr):
|
|
84
|
+
return arr[int(rng() * len(arr))]
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _roll_rarity(rng) -> str:
|
|
88
|
+
total = sum(RARITY_WEIGHTS.values())
|
|
89
|
+
roll = rng() * total
|
|
90
|
+
for r in RARITIES:
|
|
91
|
+
roll -= RARITY_WEIGHTS[r]
|
|
92
|
+
if roll < 0:
|
|
93
|
+
return r
|
|
94
|
+
return "common"
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _roll_stats(rng, rarity: str) -> dict:
|
|
98
|
+
floors = {"common": 5, "uncommon": 15, "rare": 25, "epic": 35, "legendary": 50}
|
|
99
|
+
floor = floors.get(rarity, 5)
|
|
100
|
+
peak = _pick(rng, STATS)
|
|
101
|
+
dump = _pick(rng, STATS)
|
|
102
|
+
while dump == peak:
|
|
103
|
+
dump = _pick(rng, STATS)
|
|
104
|
+
|
|
105
|
+
stats = {}
|
|
106
|
+
for name in STATS:
|
|
107
|
+
if name == peak:
|
|
108
|
+
stats[name] = min(100, floor + 50 + int(rng() * 30))
|
|
109
|
+
elif name == dump:
|
|
110
|
+
stats[name] = max(1, floor - 10 + int(rng() * 15))
|
|
111
|
+
else:
|
|
112
|
+
stats[name] = floor + int(rng() * 40)
|
|
113
|
+
return stats
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def roll_buddy(user_id: str = "anon") -> dict:
|
|
117
|
+
"""Generate deterministic buddy from user ID."""
|
|
118
|
+
seed = _hash_string(user_id + "friend-2026-401")
|
|
119
|
+
rng = _mulberry32(seed)
|
|
120
|
+
|
|
121
|
+
rarity = _roll_rarity(rng)
|
|
122
|
+
species = _pick(rng, SPECIES)
|
|
123
|
+
eye = _pick(rng, EYES)
|
|
124
|
+
hat = "none" if rarity == "common" else _pick(rng, HATS)
|
|
125
|
+
shiny = rng() < 0.01
|
|
126
|
+
stats = _roll_stats(rng, rarity)
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
"rarity": rarity,
|
|
130
|
+
"species": species,
|
|
131
|
+
"eye": eye,
|
|
132
|
+
"hat": hat,
|
|
133
|
+
"shiny": shiny,
|
|
134
|
+
"stats": stats,
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def render_sprite(buddy: dict) -> list[str]:
|
|
139
|
+
"""Render the buddy as ASCII lines."""
|
|
140
|
+
species = buddy.get("species", "blob")
|
|
141
|
+
eye = buddy.get("eye", "·")
|
|
142
|
+
hat = buddy.get("hat", "none")
|
|
143
|
+
|
|
144
|
+
body = SPRITES.get(species, SPRITES["blob"])
|
|
145
|
+
lines = [line.replace("{E}", eye) for line in body]
|
|
146
|
+
|
|
147
|
+
# Add hat
|
|
148
|
+
if hat != "none" and not lines[0].strip():
|
|
149
|
+
lines[0] = HAT_LINES.get(hat, "")
|
|
150
|
+
|
|
151
|
+
# Remove blank first line if no hat
|
|
152
|
+
if not lines[0].strip():
|
|
153
|
+
lines = lines[1:]
|
|
154
|
+
|
|
155
|
+
return lines
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def get_buddy() -> dict | None:
|
|
159
|
+
"""Get saved buddy, or None."""
|
|
160
|
+
if BUDDY_FILE.exists():
|
|
161
|
+
try:
|
|
162
|
+
return json.loads(BUDDY_FILE.read_text(encoding="utf-8"))
|
|
163
|
+
except Exception:
|
|
164
|
+
pass
|
|
165
|
+
return None
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def save_buddy(buddy: dict):
|
|
169
|
+
"""Save buddy to disk."""
|
|
170
|
+
VAAL_DIR.mkdir(exist_ok=True)
|
|
171
|
+
BUDDY_FILE.write_text(json.dumps(buddy, indent=2), encoding="utf-8")
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def hatch_buddy(user_id: str = "anon", name: str = "", personality: str = "") -> dict:
|
|
175
|
+
"""Hatch a new buddy."""
|
|
176
|
+
bones = roll_buddy(user_id)
|
|
177
|
+
buddy = {
|
|
178
|
+
**bones,
|
|
179
|
+
"name": name or bones["species"].title(),
|
|
180
|
+
"personality": personality or "A mysterious companion",
|
|
181
|
+
"hatched_at": int(time.time()),
|
|
182
|
+
}
|
|
183
|
+
save_buddy(buddy)
|
|
184
|
+
return buddy
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def play_hatch_animation(buddy: dict):
|
|
188
|
+
"""Play an egg hatching animation in the terminal."""
|
|
189
|
+
import sys
|
|
190
|
+
import time as _time
|
|
191
|
+
|
|
192
|
+
def _w(s):
|
|
193
|
+
sys.stdout.write(s)
|
|
194
|
+
sys.stdout.flush()
|
|
195
|
+
|
|
196
|
+
def _clear_lines(n):
|
|
197
|
+
for _ in range(n):
|
|
198
|
+
_w("\033[A\033[2K")
|
|
199
|
+
|
|
200
|
+
rarity = buddy.get("rarity", "common")
|
|
201
|
+
species = buddy.get("species", "blob")
|
|
202
|
+
eye = buddy.get("eye", "·")
|
|
203
|
+
rcolor_code = {"common": "37", "uncommon": "32", "rare": "34", "epic": "35", "legendary": "33"}.get(rarity, "37")
|
|
204
|
+
|
|
205
|
+
# Egg frames
|
|
206
|
+
egg_frames = [
|
|
207
|
+
[
|
|
208
|
+
" ",
|
|
209
|
+
" .--. ",
|
|
210
|
+
" / \\ ",
|
|
211
|
+
" | | ",
|
|
212
|
+
" \\ / ",
|
|
213
|
+
" `--´ ",
|
|
214
|
+
],
|
|
215
|
+
[
|
|
216
|
+
" ",
|
|
217
|
+
" .--. ",
|
|
218
|
+
" / · \\ ",
|
|
219
|
+
" | | ",
|
|
220
|
+
" \\ / ",
|
|
221
|
+
" `--´ ",
|
|
222
|
+
],
|
|
223
|
+
[
|
|
224
|
+
" ",
|
|
225
|
+
" .~--. ",
|
|
226
|
+
" / · \\ ",
|
|
227
|
+
" | · | ",
|
|
228
|
+
" \\ / ",
|
|
229
|
+
" `--´ ",
|
|
230
|
+
],
|
|
231
|
+
[
|
|
232
|
+
" * ",
|
|
233
|
+
" .~--~. ",
|
|
234
|
+
" / · · \\ ",
|
|
235
|
+
" | · | ",
|
|
236
|
+
" \\ · / ",
|
|
237
|
+
" `~--~´ ",
|
|
238
|
+
],
|
|
239
|
+
[
|
|
240
|
+
" \\|/ ",
|
|
241
|
+
" ,~--~. ",
|
|
242
|
+
" / · · \\ ",
|
|
243
|
+
" | · · | ",
|
|
244
|
+
" \\ · · / ",
|
|
245
|
+
" `~--~´ ",
|
|
246
|
+
],
|
|
247
|
+
[
|
|
248
|
+
" \\ | / ",
|
|
249
|
+
" --,~~.-- ",
|
|
250
|
+
" / \\ ",
|
|
251
|
+
" | CRACK| ",
|
|
252
|
+
" \\ / ",
|
|
253
|
+
" --`~~´-- ",
|
|
254
|
+
],
|
|
255
|
+
[
|
|
256
|
+
" * \\ | / * ",
|
|
257
|
+
" .~~~~. ",
|
|
258
|
+
" / /\\ \\ ",
|
|
259
|
+
" | / · \\| ",
|
|
260
|
+
" \\/ · / ",
|
|
261
|
+
" `~~~~´ ",
|
|
262
|
+
],
|
|
263
|
+
[
|
|
264
|
+
" * *\\|/* * ",
|
|
265
|
+
" ._/\\. ",
|
|
266
|
+
" / / \\ \\ ",
|
|
267
|
+
" |/ · · \\| ",
|
|
268
|
+
" / \\ ",
|
|
269
|
+
" `_/\\_´ ",
|
|
270
|
+
],
|
|
271
|
+
]
|
|
272
|
+
|
|
273
|
+
# Phase 1: Egg wobbling
|
|
274
|
+
_w("\n")
|
|
275
|
+
for i in range(12):
|
|
276
|
+
frame = egg_frames[min(i // 3, 2)]
|
|
277
|
+
for line in frame:
|
|
278
|
+
_w(f" \033[2m{line}\033[0m\n")
|
|
279
|
+
_time.sleep(0.25)
|
|
280
|
+
_clear_lines(len(frame))
|
|
281
|
+
|
|
282
|
+
# Phase 2: Cracking
|
|
283
|
+
for i in range(3, 6):
|
|
284
|
+
frame = egg_frames[min(i, len(egg_frames) - 1)]
|
|
285
|
+
for line in frame:
|
|
286
|
+
_w(f" \033[1;37m{line}\033[0m\n")
|
|
287
|
+
_time.sleep(0.4)
|
|
288
|
+
_clear_lines(len(frame))
|
|
289
|
+
|
|
290
|
+
# Phase 3: Breaking open
|
|
291
|
+
for i in range(6, len(egg_frames)):
|
|
292
|
+
frame = egg_frames[i]
|
|
293
|
+
for line in frame:
|
|
294
|
+
_w(f" \033[1;{rcolor_code}m{line}\033[0m\n")
|
|
295
|
+
_time.sleep(0.35)
|
|
296
|
+
_clear_lines(len(frame))
|
|
297
|
+
|
|
298
|
+
# Phase 4: Flash
|
|
299
|
+
_w(f"\033[1;{rcolor_code}m")
|
|
300
|
+
for _ in range(3):
|
|
301
|
+
_w(" ✦ ✦ ✦ ✦ ✦ ✦ ✦ ✦\n")
|
|
302
|
+
_time.sleep(0.1)
|
|
303
|
+
_clear_lines(1)
|
|
304
|
+
_time.sleep(0.1)
|
|
305
|
+
_w("\033[0m")
|
|
306
|
+
|
|
307
|
+
# Phase 5: Reveal the buddy
|
|
308
|
+
sprite = render_sprite(buddy)
|
|
309
|
+
stars = RARITY_STARS.get(rarity, "★")
|
|
310
|
+
name = buddy.get("name", "???")
|
|
311
|
+
|
|
312
|
+
_w(f"\n \033[1;{rcolor_code}m{name}\033[0m {stars}\n")
|
|
313
|
+
_w(f" \033[2m{rarity} {species}\033[0m\n\n")
|
|
314
|
+
for line in sprite:
|
|
315
|
+
_w(f" \033[1;{rcolor_code}m{line}\033[0m\n")
|
|
316
|
+
_w("\n")
|
|
317
|
+
|
|
318
|
+
if buddy.get("shiny"):
|
|
319
|
+
_w(f" \033[1;33m✧ ✧ ✧ S H I N Y ✧ ✧ ✧\033[0m\n\n")
|
|
320
|
+
|
|
321
|
+
_time.sleep(0.5)
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def format_buddy_card(buddy: dict) -> str:
|
|
325
|
+
"""Format buddy as a display card."""
|
|
326
|
+
rarity = buddy.get("rarity", "common")
|
|
327
|
+
stars = RARITY_STARS.get(rarity, "★")
|
|
328
|
+
sprite = render_sprite(buddy)
|
|
329
|
+
|
|
330
|
+
lines = []
|
|
331
|
+
lines.append(f" {buddy.get('name', '???')} {stars}")
|
|
332
|
+
lines.append(f" {rarity} {buddy.get('species', '???')}")
|
|
333
|
+
if buddy.get("shiny"):
|
|
334
|
+
lines.append(" ✧ SHINY ✧")
|
|
335
|
+
lines.append("")
|
|
336
|
+
for sl in sprite:
|
|
337
|
+
lines.append(f" {sl}")
|
|
338
|
+
lines.append("")
|
|
339
|
+
if buddy.get("hat", "none") != "none":
|
|
340
|
+
lines.append(f" Hat: {buddy['hat']}")
|
|
341
|
+
stats = buddy.get("stats", {})
|
|
342
|
+
if stats:
|
|
343
|
+
for stat, val in stats.items():
|
|
344
|
+
bar = "█" * (val // 10) + "░" * (10 - val // 10)
|
|
345
|
+
lines.append(f" {stat:10s} {bar} {val}")
|
|
346
|
+
if buddy.get("personality"):
|
|
347
|
+
lines.append(f" \"{buddy['personality']}\"")
|
|
348
|
+
|
|
349
|
+
return "\n".join(lines)
|
|
@@ -74,6 +74,7 @@ COMMANDS = [
|
|
|
74
74
|
("/ssh", "SSH remote execution (connect/run/disconnect)"),
|
|
75
75
|
("/explain", "Explain an error or stack trace"),
|
|
76
76
|
("/migrate", "Guided framework/language migration"),
|
|
77
|
+
("/buddy", "Your companion (hatch/show/reroll/name)"),
|
|
77
78
|
("/register", "Create a vaal account"),
|
|
78
79
|
("/remote", "Remote control from web browser"),
|
|
79
80
|
("/task", "Manage tasks (create/update/complete)"),
|
|
@@ -1148,8 +1148,31 @@ def run_repl(model: str = DEFAULT_MODEL, permission_mode: str = DEFAULT_PERMISSI
|
|
|
1148
1148
|
}
|
|
1149
1149
|
|
|
1150
1150
|
def print_prompt_line():
|
|
1151
|
-
"""Print the thin line above the prompt
|
|
1152
|
-
|
|
1151
|
+
"""Print the thin line above the prompt with buddy on the right."""
|
|
1152
|
+
from .buddy import get_buddy, render_sprite, RARITY_COLORS
|
|
1153
|
+
w = min(console.width or 80, 80)
|
|
1154
|
+
line_w = w - 14 # Leave room for buddy
|
|
1155
|
+
|
|
1156
|
+
buddy = get_buddy()
|
|
1157
|
+
if buddy:
|
|
1158
|
+
sprite = render_sprite(buddy)
|
|
1159
|
+
rcolor = RARITY_COLORS.get(buddy.get("rarity", "common"), "dim")
|
|
1160
|
+
bname = buddy.get("name", "")
|
|
1161
|
+
|
|
1162
|
+
# Print separator with buddy sprite on the right
|
|
1163
|
+
_write(f"\033[2m{'─' * line_w}\033[0m\n")
|
|
1164
|
+
# Print buddy sprite lines aligned to the right
|
|
1165
|
+
for i, sline in enumerate(sprite):
|
|
1166
|
+
# Cursor to column position for right-alignment
|
|
1167
|
+
col = line_w + 2
|
|
1168
|
+
_write(f"\033[{col}G\033[2m{sline}\033[0m\n")
|
|
1169
|
+
# Print buddy name under sprite
|
|
1170
|
+
_write(f"\033[{line_w + 4}G\033[2m{bname}\033[0m")
|
|
1171
|
+
# Move cursor back to start for the > prompt
|
|
1172
|
+
up = len(sprite) + 1
|
|
1173
|
+
_write(f"\033[{up}A\r")
|
|
1174
|
+
else:
|
|
1175
|
+
console.print(f"[dim]{'─' * line_w}[/dim]")
|
|
1153
1176
|
|
|
1154
1177
|
# Check if any models are installed — if not, open model selector
|
|
1155
1178
|
from .llm import list_models as _check_models
|
|
@@ -1651,6 +1674,7 @@ def handle_command(cmd: str, session: Session, active_model: list, permissions:
|
|
|
1651
1674
|
("/register", "Create a vaal account on e-e.tools"),
|
|
1652
1675
|
("/login vaal", "Login to your vaal account"),
|
|
1653
1676
|
("/remote start|stop", "Remote control from web browser"),
|
|
1677
|
+
("/buddy", "Your companion (hatch/show/reroll/name)"),
|
|
1654
1678
|
("/doctor", "System health diagnostics"),
|
|
1655
1679
|
("/preview <path>", "Live preview with interactive inspector"),
|
|
1656
1680
|
("/preview stop", "Stop preview server"),
|
|
@@ -2380,6 +2404,9 @@ def handle_command(cmd: str, session: Session, active_model: list, permissions:
|
|
|
2380
2404
|
elif command == "/remote":
|
|
2381
2405
|
_handle_remote_command(arg, active_model, session, components)
|
|
2382
2406
|
|
|
2407
|
+
elif command == "/buddy":
|
|
2408
|
+
_handle_buddy_command(arg)
|
|
2409
|
+
|
|
2383
2410
|
elif command == "/doctor":
|
|
2384
2411
|
_handle_doctor_command(active_model)
|
|
2385
2412
|
|
|
@@ -3242,6 +3269,65 @@ def get_vaal_user() -> dict | None:
|
|
|
3242
3269
|
return None
|
|
3243
3270
|
|
|
3244
3271
|
|
|
3272
|
+
def _handle_buddy_command(arg: str):
|
|
3273
|
+
"""Handle /buddy [hatch|show|reroll|name] — companion system."""
|
|
3274
|
+
from .buddy import get_buddy, hatch_buddy, format_buddy_card, roll_buddy, save_buddy, RARITY_COLORS
|
|
3275
|
+
|
|
3276
|
+
action = arg.strip().lower().split(maxsplit=1) if arg.strip() else ["show"]
|
|
3277
|
+
cmd = action[0]
|
|
3278
|
+
cmd_arg = action[1] if len(action) > 1 else ""
|
|
3279
|
+
|
|
3280
|
+
buddy = get_buddy()
|
|
3281
|
+
|
|
3282
|
+
if cmd == "hatch" or (cmd == "show" and not buddy):
|
|
3283
|
+
# Hatch a new buddy
|
|
3284
|
+
from .buddy import play_hatch_animation
|
|
3285
|
+
user = get_vaal_user()
|
|
3286
|
+
user_id = user["username"] if user else "anon"
|
|
3287
|
+
buddy = hatch_buddy(user_id)
|
|
3288
|
+
play_hatch_animation(buddy)
|
|
3289
|
+
# Show stats after animation
|
|
3290
|
+
stats_dict = buddy.get("stats", {})
|
|
3291
|
+
for stat, val in stats_dict.items():
|
|
3292
|
+
bar = "█" * (val // 10) + "░" * (10 - val // 10)
|
|
3293
|
+
console.print(f" [dim]{stat:10s}[/dim] [{VAAL_CYAN}]{bar}[/] {val}")
|
|
3294
|
+
console.print()
|
|
3295
|
+
|
|
3296
|
+
elif cmd == "show":
|
|
3297
|
+
rcolor = RARITY_COLORS.get(buddy["rarity"], "dim")
|
|
3298
|
+
console.print()
|
|
3299
|
+
console.print(f"[{rcolor}]{format_buddy_card(buddy)}[/]")
|
|
3300
|
+
console.print()
|
|
3301
|
+
|
|
3302
|
+
elif cmd == "reroll":
|
|
3303
|
+
from .buddy import play_hatch_animation
|
|
3304
|
+
import secrets
|
|
3305
|
+
seed = secrets.token_hex(8)
|
|
3306
|
+
bones = roll_buddy(seed)
|
|
3307
|
+
buddy = {**bones, "name": bones["species"].title(), "personality": "A rerolled companion", "hatched_at": int(time.time())}
|
|
3308
|
+
save_buddy(buddy)
|
|
3309
|
+
play_hatch_animation(buddy)
|
|
3310
|
+
stats_dict = buddy.get("stats", {})
|
|
3311
|
+
for stat, val in stats_dict.items():
|
|
3312
|
+
bar = "█" * (val // 10) + "░" * (10 - val // 10)
|
|
3313
|
+
console.print(f" [dim]{stat:10s}[/dim] [{VAAL_CYAN}]{bar}[/] {val}")
|
|
3314
|
+
console.print()
|
|
3315
|
+
|
|
3316
|
+
elif cmd == "name" and cmd_arg:
|
|
3317
|
+
if not buddy:
|
|
3318
|
+
console.print(f" [dim]No buddy yet. Use /buddy hatch[/dim]")
|
|
3319
|
+
return
|
|
3320
|
+
buddy["name"] = cmd_arg
|
|
3321
|
+
save_buddy(buddy)
|
|
3322
|
+
console.print(f" [{VAAL_GREEN}]Renamed to {cmd_arg}[/]")
|
|
3323
|
+
|
|
3324
|
+
else:
|
|
3325
|
+
console.print(f"[dim] /buddy — Show your buddy[/dim]")
|
|
3326
|
+
console.print(f"[dim] /buddy hatch — Hatch a new buddy[/dim]")
|
|
3327
|
+
console.print(f"[dim] /buddy reroll — Reroll (random seed)[/dim]")
|
|
3328
|
+
console.print(f"[dim] /buddy name X — Rename your buddy[/dim]")
|
|
3329
|
+
|
|
3330
|
+
|
|
3245
3331
|
def _handle_doctor_command(active_model: list):
|
|
3246
3332
|
"""Handle /doctor — system health diagnostics."""
|
|
3247
3333
|
import sys
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|