cli-nix 0.3.0__py3-none-any.whl
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.
- cli_nix-0.3.0.dist-info/METADATA +340 -0
- cli_nix-0.3.0.dist-info/RECORD +100 -0
- cli_nix-0.3.0.dist-info/WHEEL +5 -0
- cli_nix-0.3.0.dist-info/entry_points.txt +2 -0
- cli_nix-0.3.0.dist-info/licenses/LICENSE +18 -0
- cli_nix-0.3.0.dist-info/top_level.txt +1 -0
- nix/__init__.py +1 -0
- nix/__main__.py +4 -0
- nix/app.py +188 -0
- nix/avatar.py +518 -0
- nix/body.py +55 -0
- nix/brain.py +198 -0
- nix/cli.py +27 -0
- nix/commands.py +1733 -0
- nix/config.py +83 -0
- nix/daemon.py +210 -0
- nix/git.py +231 -0
- nix/i18n.py +734 -0
- nix/journal.py +59 -0
- nix/logger.py +24 -0
- nix/modules/__init__.py +46 -0
- nix/modules/bundled/__init__.py +0 -0
- nix/modules/bundled/c/__init__.py +0 -0
- nix/modules/bundled/c/blocks.json +21 -0
- nix/modules/bundled/c/gen/function.tmpl +4 -0
- nix/modules/bundled/c/module.json +6 -0
- nix/modules/bundled/c/ops.json +19 -0
- nix/modules/bundled/c/probe.json +9 -0
- nix/modules/bundled/cpp/__init__.py +0 -0
- nix/modules/bundled/cpp/blocks.json +30 -0
- nix/modules/bundled/cpp/gen/class.tmpl +4 -0
- nix/modules/bundled/cpp/gen/function.tmpl +4 -0
- nix/modules/bundled/cpp/module.json +6 -0
- nix/modules/bundled/cpp/ops.json +25 -0
- nix/modules/bundled/cpp/probe.json +14 -0
- nix/modules/bundled/csharp/__init__.py +0 -0
- nix/modules/bundled/csharp/blocks.json +30 -0
- nix/modules/bundled/csharp/gen/class.tmpl +4 -0
- nix/modules/bundled/csharp/gen/function.tmpl +4 -0
- nix/modules/bundled/csharp/module.json +6 -0
- nix/modules/bundled/csharp/ops.json +25 -0
- nix/modules/bundled/csharp/probe.json +14 -0
- nix/modules/bundled/go/__init__.py +0 -0
- nix/modules/bundled/go/blocks.json +17 -0
- nix/modules/bundled/go/gen/function.tmpl +4 -0
- nix/modules/bundled/go/module.json +6 -0
- nix/modules/bundled/go/ops.json +13 -0
- nix/modules/bundled/go/probe.json +14 -0
- nix/modules/bundled/java/__init__.py +0 -0
- nix/modules/bundled/java/blocks.json +30 -0
- nix/modules/bundled/java/gen/class.tmpl +4 -0
- nix/modules/bundled/java/gen/function.tmpl +4 -0
- nix/modules/bundled/java/module.json +6 -0
- nix/modules/bundled/java/ops.json +25 -0
- nix/modules/bundled/java/probe.json +14 -0
- nix/modules/bundled/javascript/__init__.py +0 -0
- nix/modules/bundled/javascript/blocks.json +30 -0
- nix/modules/bundled/javascript/gen/class.tmpl +4 -0
- nix/modules/bundled/javascript/gen/function.tmpl +4 -0
- nix/modules/bundled/javascript/module.json +6 -0
- nix/modules/bundled/javascript/ops.json +25 -0
- nix/modules/bundled/javascript/probe.json +19 -0
- nix/modules/bundled/php/__init__.py +0 -0
- nix/modules/bundled/php/blocks.json +30 -0
- nix/modules/bundled/php/gen/class.tmpl +4 -0
- nix/modules/bundled/php/gen/function.tmpl +4 -0
- nix/modules/bundled/php/module.json +6 -0
- nix/modules/bundled/php/ops.json +25 -0
- nix/modules/bundled/php/probe.json +14 -0
- nix/modules/bundled/python/__init__.py +0 -0
- nix/modules/bundled/python/blocks.json +30 -0
- nix/modules/bundled/python/gen/class.tmpl +3 -0
- nix/modules/bundled/python/gen/function.tmpl +3 -0
- nix/modules/bundled/python/gen/scaffold_crud.tmpl +35 -0
- nix/modules/bundled/python/gen/scaffold_model.tmpl +9 -0
- nix/modules/bundled/python/gen/scaffold_test.tmpl +41 -0
- nix/modules/bundled/python/gen/testgen.tmpl +13 -0
- nix/modules/bundled/python/module.json +1 -0
- nix/modules/bundled/python/ops.json +25 -0
- nix/modules/bundled/python/probe.json +14 -0
- nix/modules/bundled/rust/__init__.py +0 -0
- nix/modules/bundled/rust/blocks.json +21 -0
- nix/modules/bundled/rust/gen/function.tmpl +4 -0
- nix/modules/bundled/rust/module.json +6 -0
- nix/modules/bundled/rust/ops.json +19 -0
- nix/modules/bundled/rust/probe.json +14 -0
- nix/modules/bundled/typescript/__init__.py +0 -0
- nix/modules/bundled/typescript/blocks.json +30 -0
- nix/modules/bundled/typescript/gen/class.tmpl +4 -0
- nix/modules/bundled/typescript/gen/function.tmpl +4 -0
- nix/modules/bundled/typescript/module.json +6 -0
- nix/modules/bundled/typescript/ops.json +25 -0
- nix/modules/bundled/typescript/probe.json +19 -0
- nix/modules/engine.py +174 -0
- nix/modules/loader.py +172 -0
- nix/pet.py +103 -0
- nix/scaffold.py +251 -0
- nix/scanner.py +73 -0
- nix/state.py +63 -0
- nix/ui.py +1187 -0
nix/avatar.py
ADDED
|
@@ -0,0 +1,518 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import random
|
|
5
|
+
import time
|
|
6
|
+
|
|
7
|
+
from rich.text import Text
|
|
8
|
+
|
|
9
|
+
BG = "#050505"
|
|
10
|
+
|
|
11
|
+
PALETTES = {
|
|
12
|
+
"mint": {
|
|
13
|
+
"hi": "#8ef0b4", "lo": "#2f9d6e", "belly": "#d9ffe9",
|
|
14
|
+
"cheek": "#ffb3b3", "accent": "#eaffc9", "outline": "#1d5c44",
|
|
15
|
+
},
|
|
16
|
+
"sky": {
|
|
17
|
+
"hi": "#8fd6ff", "lo": "#3a7bd6", "belly": "#dff2ff",
|
|
18
|
+
"cheek": "#ffc2c2", "accent": "#ffffff", "outline": "#1f5a8a",
|
|
19
|
+
},
|
|
20
|
+
"grape": {
|
|
21
|
+
"hi": "#c9a6ff", "lo": "#7c4fd0", "belly": "#efe2ff",
|
|
22
|
+
"cheek": "#ffb3d6", "accent": "#ffe4ff", "outline": "#4a2f7a",
|
|
23
|
+
},
|
|
24
|
+
"rose": {
|
|
25
|
+
"hi": "#ffb3d9", "lo": "#d654a6", "belly": "#ffe4f2",
|
|
26
|
+
"cheek": "#ff8a8a", "accent": "#fff0f7", "outline": "#7a2f5a",
|
|
27
|
+
},
|
|
28
|
+
"slate": {
|
|
29
|
+
"hi": "#b9c4d9", "lo": "#5a6b8f", "belly": "#e8eef7",
|
|
30
|
+
"cheek": "#ffb3b3", "accent": "#cfe4ff", "outline": "#3a4660",
|
|
31
|
+
},
|
|
32
|
+
"acid": {
|
|
33
|
+
"hi": "#c9f56f", "lo": "#6aa520", "belly": "#e9ffb8",
|
|
34
|
+
"cheek": "#ffb3b3", "accent": "#f6ffd0", "outline": "#3d6b12",
|
|
35
|
+
},
|
|
36
|
+
"pink": {
|
|
37
|
+
"hi": "#ffc2e6", "lo": "#d65fa8", "belly": "#ffe9f6",
|
|
38
|
+
"cheek": "#ff8ab4", "accent": "#fff0fa", "outline": "#7a2f5e",
|
|
39
|
+
},
|
|
40
|
+
"slime": {
|
|
41
|
+
"hi": "#7fe8d9", "lo": "#2a9c8f", "belly": "#d0fbf3",
|
|
42
|
+
"cheek": "#ffb3a6", "accent": "#e6fffb", "outline": "#176a60",
|
|
43
|
+
},
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
# --- easter-egg / signature palettes -------------------------------
|
|
47
|
+
|
|
48
|
+
# The Orange Octopus - a nod to Claude Code's mascot.
|
|
49
|
+
OCTOPUS_PAL = {
|
|
50
|
+
"hi": "#ffad5c", "lo": "#e0551c", "belly": "#ffe3b8",
|
|
51
|
+
"cheek": "#ff7f4f", "accent": "#fff3d6", "outline": "#8a2b06",
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
# A cheerful orange one-eyed alien.
|
|
55
|
+
CYCLOPS_PAL = {
|
|
56
|
+
"hi": "#ffb84d", "lo": "#e86f1f", "belly": "#ffe8ad",
|
|
57
|
+
"cheek": "#ff7f4f", "accent": "#fff3c9", "outline": "#8a4506",
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
# Pou - the beige "piece of dough" virtual pet with stretched eyes.
|
|
61
|
+
POU_PAL = {
|
|
62
|
+
"hi": "#e0b386", "lo": "#a97a45", "belly": "#f5e3c0",
|
|
63
|
+
"cheek": "#ff9b7a", "accent": "#fff3d0", "outline": "#6b4a1f",
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
# My Pet Alien (Tommy) - warm brown round alien with big white eyes.
|
|
67
|
+
TOMMY_PAL = {
|
|
68
|
+
"hi": "#d99a63", "lo": "#96602e", "belly": "#f2d6b2",
|
|
69
|
+
"cheek": "#ff8a6b", "accent": "#ffe9c8", "outline": "#5e3a18",
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
# Mint-green slime.
|
|
73
|
+
SLIME_PAL = {
|
|
74
|
+
"hi": "#7fe8d9", "lo": "#2a9c8f", "belly": "#d0fbf3",
|
|
75
|
+
"cheek": "#ffb3a6", "accent": "#e6fffb", "outline": "#176a60",
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
# Lavender antenna alien.
|
|
79
|
+
ANTENNA_PAL = {
|
|
80
|
+
"hi": "#c9a6ff", "lo": "#8a5fd6", "belly": "#efe2ff",
|
|
81
|
+
"cheek": "#ffb3d6", "accent": "#ffe4ff", "outline": "#4a2f7a",
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
# Crimson cone.
|
|
85
|
+
CONE_PAL = {
|
|
86
|
+
"hi": "#ff8f8f", "lo": "#d6453d", "belly": "#ffe1de",
|
|
87
|
+
"cheek": "#ffb37a", "accent": "#fff0e6", "outline": "#7a1f1f",
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
# Teal pancake-creature.
|
|
91
|
+
LEGGY_PAL = {
|
|
92
|
+
"hi": "#4fd8d8", "lo": "#208f9f", "belly": "#d8fbfb",
|
|
93
|
+
"cheek": "#ffb3a6", "accent": "#e6fffb", "outline": "#145f6b",
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
EYES = {
|
|
97
|
+
"dark": "#23262e",
|
|
98
|
+
"amber": "#7a4a1f",
|
|
99
|
+
"blue": "#274b68",
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
W, H = 12, 16
|
|
103
|
+
EMPTY = "............"
|
|
104
|
+
|
|
105
|
+
# Hand-drawn 12x16 pixel masks. '#' = body, 'b' = belly, '.' = empty.
|
|
106
|
+
|
|
107
|
+
# Orange octopus. Claude Code's mascot came home.
|
|
108
|
+
OCTOPUS = [
|
|
109
|
+
"............",
|
|
110
|
+
"............",
|
|
111
|
+
"..########..",
|
|
112
|
+
".##########.",
|
|
113
|
+
"############",
|
|
114
|
+
"############",
|
|
115
|
+
"############",
|
|
116
|
+
"############",
|
|
117
|
+
"############",
|
|
118
|
+
".##########.",
|
|
119
|
+
".##########.",
|
|
120
|
+
"..########..",
|
|
121
|
+
"....####....",
|
|
122
|
+
"..##..##..##",
|
|
123
|
+
"..##..##..##",
|
|
124
|
+
"..##..##..##",
|
|
125
|
+
]
|
|
126
|
+
|
|
127
|
+
# A round orange alien with a single big eye.
|
|
128
|
+
CYCLOPS = [
|
|
129
|
+
"............",
|
|
130
|
+
"............",
|
|
131
|
+
"...######...",
|
|
132
|
+
"..########..",
|
|
133
|
+
".##########.",
|
|
134
|
+
"############",
|
|
135
|
+
"############",
|
|
136
|
+
"############",
|
|
137
|
+
"############",
|
|
138
|
+
"############",
|
|
139
|
+
".##########.",
|
|
140
|
+
"..########..",
|
|
141
|
+
"....####....",
|
|
142
|
+
"............",
|
|
143
|
+
"............",
|
|
144
|
+
"............",
|
|
145
|
+
]
|
|
146
|
+
|
|
147
|
+
# Pou: a beige blob, a piece of dough with small stretched eyes.
|
|
148
|
+
POU = [
|
|
149
|
+
"............",
|
|
150
|
+
"............",
|
|
151
|
+
".....##.....",
|
|
152
|
+
"....###.....",
|
|
153
|
+
"...#####....",
|
|
154
|
+
"..#######...",
|
|
155
|
+
".#########..",
|
|
156
|
+
"############",
|
|
157
|
+
"############",
|
|
158
|
+
"############",
|
|
159
|
+
"############",
|
|
160
|
+
"############",
|
|
161
|
+
".#########..",
|
|
162
|
+
"..#######...",
|
|
163
|
+
"............",
|
|
164
|
+
"............",
|
|
165
|
+
]
|
|
166
|
+
|
|
167
|
+
# My Pet Alien (Tommy): round brown head, big white eyes.
|
|
168
|
+
TOMMY = [
|
|
169
|
+
"............",
|
|
170
|
+
"............",
|
|
171
|
+
"....####....",
|
|
172
|
+
"...######...",
|
|
173
|
+
"..########..",
|
|
174
|
+
".##########.",
|
|
175
|
+
"############",
|
|
176
|
+
"############",
|
|
177
|
+
"############",
|
|
178
|
+
"############",
|
|
179
|
+
".##########.",
|
|
180
|
+
".##########.",
|
|
181
|
+
"..########..",
|
|
182
|
+
"...####.....",
|
|
183
|
+
"............",
|
|
184
|
+
"............",
|
|
185
|
+
]
|
|
186
|
+
|
|
187
|
+
# A wavy-bottomed slime with two goofy eyes.
|
|
188
|
+
SLIME = [
|
|
189
|
+
"............",
|
|
190
|
+
"............",
|
|
191
|
+
"...######...",
|
|
192
|
+
"..########..",
|
|
193
|
+
".##########.",
|
|
194
|
+
"############",
|
|
195
|
+
"############",
|
|
196
|
+
"############",
|
|
197
|
+
"############",
|
|
198
|
+
"..########..",
|
|
199
|
+
".####..####.",
|
|
200
|
+
".##......##.",
|
|
201
|
+
".##......##.",
|
|
202
|
+
"............",
|
|
203
|
+
"............",
|
|
204
|
+
"............",
|
|
205
|
+
]
|
|
206
|
+
|
|
207
|
+
# A green alien with two antennae sticking up.
|
|
208
|
+
ANTENNA = [
|
|
209
|
+
"..#......#..",
|
|
210
|
+
"...#....#...",
|
|
211
|
+
"..###..###..",
|
|
212
|
+
"..##....##..",
|
|
213
|
+
".##########.",
|
|
214
|
+
"############",
|
|
215
|
+
"############",
|
|
216
|
+
"############",
|
|
217
|
+
".####..####.",
|
|
218
|
+
".##########.",
|
|
219
|
+
"..########..",
|
|
220
|
+
"...######...",
|
|
221
|
+
"...######...",
|
|
222
|
+
"............",
|
|
223
|
+
"............",
|
|
224
|
+
"............",
|
|
225
|
+
]
|
|
226
|
+
|
|
227
|
+
# A strangely conical alien.
|
|
228
|
+
CONE = [
|
|
229
|
+
"............",
|
|
230
|
+
"............",
|
|
231
|
+
".....##.....",
|
|
232
|
+
".....###....",
|
|
233
|
+
"....#####...",
|
|
234
|
+
"...#######..",
|
|
235
|
+
"..#########.",
|
|
236
|
+
".##########.",
|
|
237
|
+
".##########.",
|
|
238
|
+
".####..####.",
|
|
239
|
+
".##########.",
|
|
240
|
+
"..########..",
|
|
241
|
+
"...######...",
|
|
242
|
+
"............",
|
|
243
|
+
"............",
|
|
244
|
+
"............",
|
|
245
|
+
]
|
|
246
|
+
|
|
247
|
+
# A weird floating pancake-creature with little legs.
|
|
248
|
+
LEGGY = [
|
|
249
|
+
"...######...",
|
|
250
|
+
"..########..",
|
|
251
|
+
".##########.",
|
|
252
|
+
".##########.",
|
|
253
|
+
"############",
|
|
254
|
+
".##########.",
|
|
255
|
+
".##########.",
|
|
256
|
+
"..########..",
|
|
257
|
+
"..####..####",
|
|
258
|
+
"..##..##..##",
|
|
259
|
+
"..##..##..##",
|
|
260
|
+
"............",
|
|
261
|
+
"............",
|
|
262
|
+
"............",
|
|
263
|
+
"............",
|
|
264
|
+
"............",
|
|
265
|
+
]
|
|
266
|
+
|
|
267
|
+
VARIANTS = [
|
|
268
|
+
{"kind": "octopus", "grid": OCTOPUS, "face_y": 6, "mouth_y": 9,
|
|
269
|
+
"palette": OCTOPUS_PAL},
|
|
270
|
+
{"kind": "cyclops", "grid": CYCLOPS, "face_y": 5, "mouth_y": 9,
|
|
271
|
+
"palette": CYCLOPS_PAL, "single_eye": True},
|
|
272
|
+
{"kind": "pou", "grid": POU, "face_y": 7, "mouth_y": 10,
|
|
273
|
+
"palette": POU_PAL, "pou_eyes": True},
|
|
274
|
+
{"kind": "tommy", "grid": TOMMY, "face_y": 5, "mouth_y": 9,
|
|
275
|
+
"palette": TOMMY_PAL},
|
|
276
|
+
{"kind": "slime", "grid": SLIME, "face_y": 5, "mouth_y": 8,
|
|
277
|
+
"palette": SLIME_PAL},
|
|
278
|
+
{"kind": "antenna", "grid": ANTENNA, "face_y": 5, "mouth_y": 8,
|
|
279
|
+
"palette": ANTENNA_PAL},
|
|
280
|
+
{"kind": "cone", "grid": CONE, "face_y": 6, "mouth_y": 9,
|
|
281
|
+
"palette": CONE_PAL},
|
|
282
|
+
{"kind": "leggy", "grid": LEGGY, "face_y": 3, "mouth_y": 7,
|
|
283
|
+
"palette": LEGGY_PAL},
|
|
284
|
+
]
|
|
285
|
+
|
|
286
|
+
VARIANT_KINDS = [v["kind"] for v in VARIANTS]
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def choose_skin(not_kind: str | None = None) -> str:
|
|
290
|
+
pool = [k for k in VARIANT_KINDS if k != not_kind]
|
|
291
|
+
return random.choice(pool or list(VARIANT_KINDS))
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def _lerp(a: str, b: str, t: float) -> str:
|
|
295
|
+
al = [int(a[i : i + 2], 16) for i in (1, 3, 5)]
|
|
296
|
+
bl = [int(b[i : i + 2], 16) for i in (1, 3, 5)]
|
|
297
|
+
return "#" + "".join(
|
|
298
|
+
f"{round(al[k] + (bl[k] - al[k]) * max(0.0, min(1.0, t))) & 255:02x}"
|
|
299
|
+
for k in range(3)
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def genes_for(root, pet: dict) -> dict:
|
|
304
|
+
seed = f"{root}:{pet.get('name', 'pet')}"
|
|
305
|
+
h = int.from_bytes(hashlib.sha256(seed.encode("utf-8")).digest()[:8], "big")
|
|
306
|
+
r = random.Random(h)
|
|
307
|
+
return {
|
|
308
|
+
"palette": r.choice(list(PALETTES)),
|
|
309
|
+
"variant": r.choice(VARIANTS)["kind"],
|
|
310
|
+
"eye": r.choice(list(EYES)),
|
|
311
|
+
"seed": seed,
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def _variant(kind: str) -> dict:
|
|
316
|
+
for v in VARIANTS:
|
|
317
|
+
if v["kind"] == kind:
|
|
318
|
+
return v
|
|
319
|
+
return VARIANTS[0]
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def mutation_count(pet: dict) -> int:
|
|
323
|
+
return min(
|
|
324
|
+
3,
|
|
325
|
+
pet.get("evolution_level", 0) + pet.get("age", 0) // 12,
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def _body_matrix(vm: dict, palette: dict, stage: str):
|
|
330
|
+
m = [["."] * W for _ in range(H)]
|
|
331
|
+
grid = vm["grid"]
|
|
332
|
+
|
|
333
|
+
def inside(x, y) -> bool:
|
|
334
|
+
return 0 <= x < W and 0 <= y < H and grid[y][x] != "."
|
|
335
|
+
|
|
336
|
+
for y in range(H):
|
|
337
|
+
row = grid[y] if len(grid) > y else EMPTY
|
|
338
|
+
for x in range(W):
|
|
339
|
+
ch = row[x]
|
|
340
|
+
if ch == ".":
|
|
341
|
+
continue
|
|
342
|
+
edge = not (
|
|
343
|
+
inside(x - 1, y)
|
|
344
|
+
and inside(x + 1, y)
|
|
345
|
+
and inside(x, y - 1)
|
|
346
|
+
and inside(x, y + 1)
|
|
347
|
+
)
|
|
348
|
+
if ch == "b":
|
|
349
|
+
m[y][x] = palette["outline"] if edge else palette["belly"]
|
|
350
|
+
else:
|
|
351
|
+
if edge:
|
|
352
|
+
m[y][x] = palette["outline"]
|
|
353
|
+
else:
|
|
354
|
+
t = (y - 2.5) / 13.0
|
|
355
|
+
m[y][x] = _lerp(palette["hi"], palette["lo"], t)
|
|
356
|
+
|
|
357
|
+
def paint(x, y, color):
|
|
358
|
+
if 0 <= x < W and 0 <= y < H:
|
|
359
|
+
m[y][x] = color
|
|
360
|
+
|
|
361
|
+
# stage accessory (floating above the head)
|
|
362
|
+
if stage == "sprout":
|
|
363
|
+
paint(5, 1, "#6ce0a0")
|
|
364
|
+
paint(6, 1, "#4ade80")
|
|
365
|
+
elif stage == "bloom":
|
|
366
|
+
paint(5, 0, "#ffd166")
|
|
367
|
+
paint(5, 1, "#ff9db4")
|
|
368
|
+
paint(6, 0, "#ff9db4")
|
|
369
|
+
paint(6, 1, "#ffd166")
|
|
370
|
+
elif stage == "mystic":
|
|
371
|
+
for x in range(4, 8):
|
|
372
|
+
paint(x, 0, "#ffd166")
|
|
373
|
+
paint(4, 1, "#aef3ff")
|
|
374
|
+
paint(7, 1, "#aef3ff")
|
|
375
|
+
return m
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
LOOKS = ("left", "straight", "right")
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def _paint_eyes(m, eyes, face_y, eye, glint, look, blink):
|
|
382
|
+
"""Draw open eyes as 2-tall sockets with a glinting pupil.
|
|
383
|
+
|
|
384
|
+
*eyes* is a list of (start_x, width); the pupil (glint) travels
|
|
385
|
+
inside each socket along the gaze direction, so the pet visibly
|
|
386
|
+
looks left / straight / right. Blink/tired/sleepy draw a closed
|
|
387
|
+
horizontal line instead.
|
|
388
|
+
"""
|
|
389
|
+
def paint(x, y, color):
|
|
390
|
+
if 0 <= x < W and 0 <= y < H:
|
|
391
|
+
m[y][x] = color
|
|
392
|
+
|
|
393
|
+
if blink:
|
|
394
|
+
for start, width in eyes:
|
|
395
|
+
for x in range(start, start + width):
|
|
396
|
+
paint(x, face_y + 1, eye)
|
|
397
|
+
return
|
|
398
|
+
|
|
399
|
+
for start, width in eyes:
|
|
400
|
+
for yy in (face_y, face_y + 1):
|
|
401
|
+
for x in range(start, start + width):
|
|
402
|
+
paint(x, yy, eye)
|
|
403
|
+
|
|
404
|
+
if look == "left":
|
|
405
|
+
for start, _width in eyes:
|
|
406
|
+
paint(start, face_y, glint)
|
|
407
|
+
elif look == "right":
|
|
408
|
+
for start, width in eyes:
|
|
409
|
+
paint(start + width - 1, face_y, glint)
|
|
410
|
+
else: # straight: wide open — both cells lit for 2-wide eyes
|
|
411
|
+
for start, width in eyes:
|
|
412
|
+
if width == 2:
|
|
413
|
+
paint(start, face_y, glint)
|
|
414
|
+
paint(start + width - 1, face_y, glint)
|
|
415
|
+
else:
|
|
416
|
+
paint(start + width // 2, face_y, glint)
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
def idle_look(pet_seed: str, now: float | None = None) -> str:
|
|
420
|
+
"""Deterministic per-pet glance schedule: the eyes dart side to
|
|
421
|
+
side every few seconds, mostly returning to straight ahead."""
|
|
422
|
+
if now is None:
|
|
423
|
+
now = time.time()
|
|
424
|
+
h = int.from_bytes(hashlib.sha256(pet_seed.encode("utf-8")).digest()[:8],
|
|
425
|
+
"big")
|
|
426
|
+
step = 4.0 + (h % 40) / 10.0
|
|
427
|
+
bucket = int(now // step)
|
|
428
|
+
r = random.Random(h ^ (bucket * 0x9E3779B9 & 0xFFFFFFFF))
|
|
429
|
+
return r.choice(("left", "straight", "straight", "right", "straight"))
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
def _face(m, vm: dict, genes: dict, pal: dict, mood: str, blink: bool,
|
|
433
|
+
look: str = "straight"):
|
|
434
|
+
eye = EYES[genes["eye"]]
|
|
435
|
+
glint = "#ffffff"
|
|
436
|
+
face_y, mouth_y = vm["face_y"], vm["mouth_y"]
|
|
437
|
+
cheeks_y = mouth_y - 1
|
|
438
|
+
closed = blink or mood in ("tired", "sleepy")
|
|
439
|
+
|
|
440
|
+
def paint(x, y, color):
|
|
441
|
+
if 0 <= x < W and 0 <= y < H:
|
|
442
|
+
m[y][x] = color
|
|
443
|
+
|
|
444
|
+
if vm.get("single_eye"):
|
|
445
|
+
_paint_eyes(m, [(4, 4)], face_y, eye, glint, look, closed)
|
|
446
|
+
paint(2, cheeks_y, pal["cheek"])
|
|
447
|
+
paint(9, cheeks_y, pal["cheek"])
|
|
448
|
+
for x in (5, 6):
|
|
449
|
+
paint(x, mouth_y, pal["outline"])
|
|
450
|
+
return
|
|
451
|
+
|
|
452
|
+
_paint_eyes(m, [(3, 2), (8, 2)], face_y, eye, glint, look, closed)
|
|
453
|
+
paint(1, cheeks_y, pal["cheek"])
|
|
454
|
+
paint(10, cheeks_y, pal["cheek"])
|
|
455
|
+
|
|
456
|
+
mouth = {
|
|
457
|
+
"happy": [3, 4, 5, 6, 7, 8],
|
|
458
|
+
"content": [5, 6],
|
|
459
|
+
"focused": [4, 5, 6, 7],
|
|
460
|
+
"alert": [4, 5, 6, 7],
|
|
461
|
+
"determined": [4, 5, 6],
|
|
462
|
+
"thoughtful": [4, 5, 6],
|
|
463
|
+
"curious": [4, 5, 6],
|
|
464
|
+
"cautious": [5, 6],
|
|
465
|
+
"worried": [5, 6],
|
|
466
|
+
"anxious": [5, 6],
|
|
467
|
+
"relieved": [5, 6],
|
|
468
|
+
"tired": [5],
|
|
469
|
+
}.get(mood, [5, 6])
|
|
470
|
+
for x in mouth:
|
|
471
|
+
paint(x, mouth_y, pal["outline"])
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
def _to_text(m, scale: int) -> Text:
|
|
475
|
+
text = Text()
|
|
476
|
+
for y in range(0, H, 2):
|
|
477
|
+
top = m[y]
|
|
478
|
+
bot = m[y + 1] if y + 1 < H else None
|
|
479
|
+
for x in range(W):
|
|
480
|
+
t = top[x]
|
|
481
|
+
b = (bot[x] if bot is not None else ".")
|
|
482
|
+
if t != "." and b != ".":
|
|
483
|
+
glyph, seg = "\u2580", f"{t} on {b}" # top half = fg
|
|
484
|
+
elif t != ".":
|
|
485
|
+
glyph, seg = "\u2580", f"{t} on {BG}" # top half only
|
|
486
|
+
elif b != ".":
|
|
487
|
+
glyph, seg = "\u2584", f"{b} on {BG}" # bottom half only
|
|
488
|
+
else:
|
|
489
|
+
glyph, seg = " ", ""
|
|
490
|
+
if scale >= 2:
|
|
491
|
+
if seg:
|
|
492
|
+
text.append(glyph * 2, style=seg)
|
|
493
|
+
else:
|
|
494
|
+
text.append(" ")
|
|
495
|
+
else:
|
|
496
|
+
if seg:
|
|
497
|
+
text.append(glyph, style=seg)
|
|
498
|
+
else:
|
|
499
|
+
text.append(" ", style="")
|
|
500
|
+
text.append("\n")
|
|
501
|
+
return text
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
def render(pet: dict, root, scale: int = 1, blink: bool = False,
|
|
505
|
+
look: str = "straight") -> Text:
|
|
506
|
+
genes = genes_for(root, pet)
|
|
507
|
+
variant = pet.get("skin")
|
|
508
|
+
if variant not in VARIANT_KINDS:
|
|
509
|
+
variant = genes["variant"]
|
|
510
|
+
stage = pet.get("body_pattern", "seed")
|
|
511
|
+
if stage not in ("seed", "sprout", "bloom", "mystic"):
|
|
512
|
+
stage = "seed"
|
|
513
|
+
mood = pet.get("mood", "curious")
|
|
514
|
+
vm = _variant(variant)
|
|
515
|
+
palette = vm.get("palette") or PALETTES[genes["palette"]]
|
|
516
|
+
m = _body_matrix(vm, palette, stage)
|
|
517
|
+
_face(m, vm, genes, palette, mood, blink, look)
|
|
518
|
+
return _to_text(m, scale)
|
nix/body.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Function body inference for the gen command.
|
|
2
|
+
|
|
3
|
+
Given a function's name, parameter list, and return annotation, this
|
|
4
|
+
derives a sensible body for the honest, conventional cases:
|
|
5
|
+
constructors, getters, and setters. Anything it is not sure about
|
|
6
|
+
stays ``pass`` (the gen default).
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import re
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _param_names(params: str) -> list[str]:
|
|
14
|
+
"""Extract plain parameter names from a comma-joined spec.
|
|
15
|
+
|
|
16
|
+
Drops ``self``/``cls`` and variadic parameters (``*args``,
|
|
17
|
+
``**kwargs``). Handles ``name: str = "x"`` and ``age: int = 0``.
|
|
18
|
+
"""
|
|
19
|
+
names: list[str] = []
|
|
20
|
+
for part in params.split(","):
|
|
21
|
+
p = part.strip()
|
|
22
|
+
if not p:
|
|
23
|
+
continue
|
|
24
|
+
base = re.split(r"[:=]", p)[0].strip()
|
|
25
|
+
if not base or base in ("self", "cls") or base.startswith(("*", "**")):
|
|
26
|
+
continue
|
|
27
|
+
names.append(base)
|
|
28
|
+
return names
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def infer_body(name: str, params: str, ret: str = "") -> str:
|
|
32
|
+
"""Suggest a body for a function, or ``pass`` when unsure.
|
|
33
|
+
|
|
34
|
+
The returned string uses 4-space inner indentation so it can be
|
|
35
|
+
dropped straight into a `` {{body}}`` template line.
|
|
36
|
+
"""
|
|
37
|
+
if name == "__init__":
|
|
38
|
+
assigned = _param_names(params)
|
|
39
|
+
if not assigned:
|
|
40
|
+
return "pass"
|
|
41
|
+
return "\n ".join(f"self.{p} = {p}" for p in assigned)
|
|
42
|
+
|
|
43
|
+
if name.startswith("get_"):
|
|
44
|
+
field = name[4:].strip("_")
|
|
45
|
+
if field:
|
|
46
|
+
return f"return self._{field}"
|
|
47
|
+
|
|
48
|
+
if name.startswith("set_"):
|
|
49
|
+
field = name[4:].strip("_")
|
|
50
|
+
if field:
|
|
51
|
+
args = _param_names(params)
|
|
52
|
+
value = args[0] if args else field
|
|
53
|
+
return f"self._{field} = {value}"
|
|
54
|
+
|
|
55
|
+
return "pass"
|