termlings 0.1.1 → 0.1.2

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.
@@ -0,0 +1,646 @@
1
+ #!/usr/bin/env node
2
+
3
+ // packages/termlings/src/index.ts
4
+ var F = ["_", "f", "f", "f", "f", "f", "f", "f", "_"];
5
+ var EYES = [
6
+ ["_", "f", "e", "f", "f", "f", "e", "f", "_"],
7
+ ["_", "e", "f", "f", "f", "f", "f", "e", "_"],
8
+ ["_", "f", "f", "e", "f", "e", "f", "f", "_"],
9
+ ["_", "f", "e", "f", "f", "f", "e", "f", "_"],
10
+ ["_", "e", "e", "f", "f", "f", "e", "e", "_"],
11
+ ["_", "f", "e", "e", "f", "e", "e", "f", "_"],
12
+ ["_", "f", "s", "f", "f", "f", "s", "f", "_"],
13
+ ["_", "s", "f", "f", "f", "f", "f", "s", "_"],
14
+ ["_", "f", "n", "f", "f", "f", "n", "f", "_"],
15
+ ["_", "n", "f", "f", "f", "f", "f", "n", "_"],
16
+ ["_", "f", "f", "n", "f", "n", "f", "f", "_"]
17
+ ];
18
+ var MOUTHS = [
19
+ [
20
+ ["_", "f", "q", "f", "f", "f", "r", "f", "_"],
21
+ ["_", "f", "f", "m", "m", "m", "f", "f", "_"]
22
+ ],
23
+ [
24
+ ["_", "f", "q", "f", "f", "f", "f", "f", "_"],
25
+ ["_", "f", "f", "m", "m", "m", "f", "f", "_"]
26
+ ],
27
+ [
28
+ ["_", "f", "f", "f", "f", "f", "r", "f", "_"],
29
+ ["_", "f", "f", "m", "m", "m", "f", "f", "_"]
30
+ ],
31
+ [
32
+ ["_", "f", "f", "q", "f", "r", "f", "f", "_"],
33
+ ["_", "f", "f", "f", "m", "f", "f", "f", "_"]
34
+ ],
35
+ [
36
+ ["_", "q", "f", "f", "f", "f", "f", "r", "_"],
37
+ ["_", "f", "m", "m", "m", "m", "m", "f", "_"]
38
+ ],
39
+ [
40
+ ["_", "q", "f", "f", "f", "f", "f", "f", "_"],
41
+ ["_", "f", "m", "m", "m", "m", "m", "f", "_"]
42
+ ],
43
+ [
44
+ ["_", "f", "f", "f", "f", "f", "f", "r", "_"],
45
+ ["_", "f", "m", "m", "m", "m", "m", "f", "_"]
46
+ ]
47
+ ];
48
+ var HATS = [
49
+ [],
50
+ [
51
+ ["_", "_", "_", "h", "h", "h", "_", "_", "_"],
52
+ ["_", "_", "_", "h", "h", "h", "_", "_", "_"],
53
+ ["_", "h", "h", "h", "h", "h", "h", "h", "_"]
54
+ ],
55
+ [
56
+ ["_", "_", "_", "h", "h", "h", "_", "_", "_"],
57
+ ["_", "_", "h", "h", "h", "h", "h", "_", "_"]
58
+ ],
59
+ [
60
+ ["_", "_", "h", "_", "h", "_", "h", "_", "_"],
61
+ ["_", "_", "h", "h", "h", "h", "h", "_", "_"]
62
+ ],
63
+ [
64
+ ["_", "_", "h", "h", "h", "h", "h", "_", "_"],
65
+ ["h", "h", "h", "h", "h", "h", "h", "_", "_"]
66
+ ],
67
+ [
68
+ ["_", "h", "_", "_", "_", "_", "_", "h", "_"],
69
+ ["_", "h", "h", "_", "_", "_", "h", "h", "_"]
70
+ ],
71
+ [
72
+ ["_", "_", "_", "_", "h", "_", "_", "_", "_"],
73
+ ["_", "_", "_", "h", "h", "h", "_", "_", "_"]
74
+ ],
75
+ [
76
+ ["_", "_", "_", "_", "h", "_", "_", "_", "_"],
77
+ ["_", "_", "_", "_", "h", "_", "_", "_", "_"]
78
+ ],
79
+ [
80
+ ["_", "_", "_", "h", "h", "h", "_", "_", "_"]
81
+ ],
82
+ [
83
+ ["_", "f", "f", "f", "f", "f", "f", "f", "_"],
84
+ ["_", "h", "h", "h", "h", "h", "h", "h", "_"]
85
+ ],
86
+ [
87
+ ["_", "_", "h", "h", "h", "h", "h", "_", "_"],
88
+ ["h", "h", "h", "h", "h", "h", "h", "h", "h"]
89
+ ],
90
+ [
91
+ ["_", "_", "_", "_", "k", "_", "_", "_", "_"],
92
+ ["_", "_", "_", "_", "h", "_", "_", "_", "_"],
93
+ ["_", "_", "_", "h", "h", "h", "_", "_", "_"]
94
+ ],
95
+ [
96
+ ["_", "h", "h", "_", "_", "_", "h", "h", "_"]
97
+ ],
98
+ [
99
+ ["_", "h", "_", "h", "_", "h", "_", "h", "_"],
100
+ ["_", "h", "h", "h", "h", "h", "h", "h", "_"]
101
+ ],
102
+ [
103
+ ["_", "_", "_", "_", "h", "_", "_", "_", "_"],
104
+ ["_", "_", "_", "h", "h", "h", "_", "_", "_"],
105
+ ["_", "_", "h", "h", "h", "h", "h", "_", "_"]
106
+ ],
107
+ [
108
+ ["_", "h", "h", "h", "h", "h", "h", "h", "_"],
109
+ ["_", "h", "h", "h", "h", "h", "h", "h", "_"]
110
+ ],
111
+ [
112
+ ["_", "_", "k", "h", "h", "h", "k", "_", "_"],
113
+ ["_", "k", "h", "h", "h", "h", "h", "k", "_"]
114
+ ],
115
+ [
116
+ ["_", "_", "_", "_", "h", "h", "h", "k", "_"],
117
+ ["_", "h", "h", "h", "h", "h", "h", "h", "_"]
118
+ ],
119
+ [
120
+ ["_", "_", "h", "h", "h", "h", "h", "_", "_"],
121
+ ["_", "_", "h", "h", "h", "h", "h", "_", "_"],
122
+ ["h", "h", "h", "h", "h", "h", "h", "h", "h"]
123
+ ],
124
+ [
125
+ ["_", "_", "_", "h", "h", "h", "_", "_", "_"],
126
+ ["_", "_", "h", "h", "h", "h", "h", "_", "_"],
127
+ ["_", "h", "f", "h", "f", "h", "f", "h", "_"]
128
+ ],
129
+ [
130
+ ["h", "h", "_", "_", "_", "_", "_", "h", "h"],
131
+ ["h", "h", "h", "_", "_", "_", "h", "h", "h"]
132
+ ],
133
+ [
134
+ ["_", "h", "h", "h", "h", "h", "h", "h", "_"],
135
+ ["_", "h", "h", "h", "h", "h", "h", "h", "_"],
136
+ ["_", "h", "h", "h", "h", "h", "h", "h", "_"],
137
+ ["_", "d", "d", "d", "d", "d", "d", "d", "_"],
138
+ ["h", "h", "h", "h", "h", "h", "h", "h", "h"]
139
+ ]
140
+ ];
141
+ var BODIES = [
142
+ [
143
+ ["_", "f", "f", "f", "f", "f", "f", "f", "_"],
144
+ ["_", "f", "f", "f", "f", "f", "f", "f", "_"]
145
+ ],
146
+ [
147
+ ["a", "f", "f", "f", "f", "f", "f", "f", "a"],
148
+ ["_", "f", "f", "f", "f", "f", "f", "f", "_"]
149
+ ],
150
+ [
151
+ ["_", "_", "f", "f", "f", "f", "f", "_", "_"],
152
+ ["_", "_", "f", "f", "f", "f", "f", "_", "_"]
153
+ ],
154
+ [
155
+ ["_", "a", "f", "f", "f", "f", "f", "a", "_"],
156
+ ["_", "_", "f", "f", "f", "f", "f", "_", "_"]
157
+ ],
158
+ [
159
+ ["_", "f", "f", "f", "f", "f", "f", "f", "_"],
160
+ ["_", "_", "f", "f", "f", "f", "f", "_", "_"]
161
+ ],
162
+ [
163
+ ["a", "f", "f", "f", "f", "f", "f", "f", "a"],
164
+ ["_", "_", "f", "f", "f", "f", "f", "_", "_"]
165
+ ]
166
+ ];
167
+ var LEGS = [
168
+ [
169
+ ["_", "_", "f", "_", "_", "f", "_", "_", "_"],
170
+ ["_", "f", "_", "_", "_", "_", "f", "_", "_"]
171
+ ],
172
+ [
173
+ ["_", "_", "l", "_", "_", "_", "_", "l", "_"],
174
+ ["_", "_", "_", "l", "_", "l", "_", "_", "_"]
175
+ ],
176
+ [
177
+ ["_", "f", "_", "l", "_", "l", "_", "f", "_"],
178
+ ["_", "f", "_", "f", "_", "l", "_", "f", "_"],
179
+ ["_", "f", "_", "l", "_", "f", "_", "f", "_"]
180
+ ],
181
+ [
182
+ ["_", "_", "l", "_", "_", "_", "l", "_", "_"],
183
+ ["_", "_", "_", "l", "_", "l", "_", "_", "_"]
184
+ ],
185
+ [
186
+ ["_", "f", "_", "_", "_", "_", "_", "f", "_"],
187
+ ["_", "_", "f", "_", "_", "_", "f", "_", "_"]
188
+ ],
189
+ [
190
+ ["_", "_", "_", "l", "_", "l", "_", "_", "_"],
191
+ ["_", "_", "l", "_", "_", "_", "l", "_", "_"]
192
+ ]
193
+ ];
194
+ var SLOTS = {
195
+ eyes: 12,
196
+ mouths: 12,
197
+ hats: 24,
198
+ bodies: 8,
199
+ legs: 8,
200
+ hues: 12
201
+ };
202
+ function encodeDNA(traits) {
203
+ let n = traits.hatHue;
204
+ n += traits.faceHue * SLOTS.hues;
205
+ n += traits.legs * SLOTS.hues * SLOTS.hues;
206
+ n += traits.body * SLOTS.legs * SLOTS.hues * SLOTS.hues;
207
+ n += traits.hat * SLOTS.bodies * SLOTS.legs * SLOTS.hues * SLOTS.hues;
208
+ n += traits.mouth * SLOTS.hats * SLOTS.bodies * SLOTS.legs * SLOTS.hues * SLOTS.hues;
209
+ n += traits.eyes * SLOTS.mouths * SLOTS.hats * SLOTS.bodies * SLOTS.legs * SLOTS.hues * SLOTS.hues;
210
+ return n.toString(16).padStart(7, "0");
211
+ }
212
+ function decodeDNA(hex) {
213
+ let n = parseInt(hex, 16);
214
+ const hatHue = n % SLOTS.hues;
215
+ n = Math.floor(n / SLOTS.hues);
216
+ const faceHue = n % SLOTS.hues;
217
+ n = Math.floor(n / SLOTS.hues);
218
+ const legs = n % SLOTS.legs;
219
+ n = Math.floor(n / SLOTS.legs);
220
+ const body = n % SLOTS.bodies;
221
+ n = Math.floor(n / SLOTS.bodies);
222
+ const hat = n % SLOTS.hats;
223
+ n = Math.floor(n / SLOTS.hats);
224
+ const mouth = n % SLOTS.mouths;
225
+ n = Math.floor(n / SLOTS.mouths);
226
+ const eyes = n % SLOTS.eyes;
227
+ return {
228
+ eyes: eyes % EYES.length,
229
+ mouth: mouth % MOUTHS.length,
230
+ hat: hat % HATS.length,
231
+ body: body % BODIES.length,
232
+ legs: legs % LEGS.length,
233
+ faceHue: faceHue % SLOTS.hues,
234
+ hatHue: hatHue % SLOTS.hues
235
+ };
236
+ }
237
+ function traitsFromName(name) {
238
+ let hash = 0;
239
+ for (let i = 0;i < name.length; i++) {
240
+ hash = name.charCodeAt(i) + ((hash << 5) - hash);
241
+ }
242
+ hash = Math.imul(hash ^ hash >>> 16, 73244475);
243
+ hash = Math.imul(hash ^ hash >>> 13, 73244475);
244
+ hash = hash ^ hash >>> 16;
245
+ const h = Math.abs(hash);
246
+ return {
247
+ eyes: h % EYES.length,
248
+ mouth: (h >>> 4) % MOUTHS.length,
249
+ hat: (h >>> 8) % HATS.length,
250
+ body: (h >>> 14) % BODIES.length,
251
+ legs: (h >>> 18) % LEGS.length,
252
+ faceHue: (h >>> 22) % 12,
253
+ hatHue: (h >>> 26) % 12
254
+ };
255
+ }
256
+ function generateRandomDNA() {
257
+ return encodeDNA({
258
+ eyes: Math.floor(Math.random() * EYES.length),
259
+ mouth: Math.floor(Math.random() * MOUTHS.length),
260
+ hat: Math.floor(Math.random() * HATS.length),
261
+ body: Math.floor(Math.random() * BODIES.length),
262
+ legs: Math.floor(Math.random() * LEGS.length),
263
+ faceHue: Math.floor(Math.random() * SLOTS.hues),
264
+ hatHue: Math.floor(Math.random() * SLOTS.hues)
265
+ });
266
+ }
267
+ var WAVE_FRAMES = [
268
+ [
269
+ ["a", "f", "f", "f", "f", "f", "f", "f", "_"],
270
+ ["_", "f", "f", "f", "f", "f", "f", "f", "a"]
271
+ ],
272
+ [
273
+ ["_", "f", "f", "f", "f", "f", "f", "f", "a"],
274
+ ["a", "f", "f", "f", "f", "f", "f", "f", "_"]
275
+ ]
276
+ ];
277
+ var TALK_FRAMES = [
278
+ [
279
+ ["_", "f", "f", "f", "f", "f", "f", "f", "_"],
280
+ ["_", "f", "f", "d", "d", "d", "f", "f", "_"]
281
+ ]
282
+ ];
283
+ function generateGrid(traits, frame = 0, talkFrame = 0, waveFrame = 0) {
284
+ const legFrames = LEGS[traits.legs];
285
+ const legRow = legFrames[frame % legFrames.length];
286
+ const mouthRows = talkFrame === 0 ? MOUTHS[traits.mouth] : TALK_FRAMES[(talkFrame - 1) % TALK_FRAMES.length];
287
+ const bodyRows = waveFrame === 0 ? BODIES[traits.body] : WAVE_FRAMES[(waveFrame - 1) % WAVE_FRAMES.length];
288
+ return [
289
+ ...HATS[traits.hat],
290
+ F,
291
+ EYES[traits.eyes],
292
+ ...mouthRows,
293
+ ...bodyRows,
294
+ legRow
295
+ ];
296
+ }
297
+ function hslToRgb(h, s, l) {
298
+ const c = (1 - Math.abs(2 * l - 1)) * s;
299
+ const x = c * (1 - Math.abs(h / 60 % 2 - 1));
300
+ const m = l - c / 2;
301
+ let r1, g1, b1;
302
+ if (h < 60) {
303
+ [r1, g1, b1] = [c, x, 0];
304
+ } else if (h < 120) {
305
+ [r1, g1, b1] = [x, c, 0];
306
+ } else if (h < 180) {
307
+ [r1, g1, b1] = [0, c, x];
308
+ } else if (h < 240) {
309
+ [r1, g1, b1] = [0, x, c];
310
+ } else if (h < 300) {
311
+ [r1, g1, b1] = [x, 0, c];
312
+ } else {
313
+ [r1, g1, b1] = [c, 0, x];
314
+ }
315
+ return [
316
+ Math.round((r1 + m) * 255),
317
+ Math.round((g1 + m) * 255),
318
+ Math.round((b1 + m) * 255)
319
+ ];
320
+ }
321
+ function renderSVG(dna, pixelSize = 10, frame = 0, background = "#000") {
322
+ const traits = decodeDNA(dna);
323
+ const grid = generateGrid(traits, frame);
324
+ const faceHueDeg = traits.faceHue * 30;
325
+ const hatHueDeg = traits.hatHue * 30;
326
+ const faceRgb = hslToRgb(faceHueDeg, 0.5, 0.5);
327
+ const darkRgb = hslToRgb(faceHueDeg, 0.5, 0.28);
328
+ const hatRgb = hslToRgb(hatHueDeg, 0.5, 0.5);
329
+ const toHex = (r, g, b) => `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
330
+ const faceHex = toHex(...faceRgb);
331
+ const darkHex = toHex(...darkRgb);
332
+ const hatHex = toHex(...hatRgb);
333
+ const cols = 9;
334
+ const rows = grid.length;
335
+ const pad = 1;
336
+ const w = (cols + pad * 2) * pixelSize;
337
+ const h = (rows + pad * 2) * pixelSize;
338
+ const half = Math.round(pixelSize / 2);
339
+ const quarter = Math.round(pixelSize / 4);
340
+ const rects = [];
341
+ for (let y = 0;y < rows; y++) {
342
+ for (let x = 0;x < cols; x++) {
343
+ const cell = grid[y][x];
344
+ const rx = (x + pad) * pixelSize;
345
+ const ry = (y + pad) * pixelSize;
346
+ if (cell === "f") {
347
+ rects.push(`<rect x="${rx}" y="${ry}" width="${pixelSize}" height="${pixelSize}" fill="${faceHex}"/>`);
348
+ } else if (cell === "l") {
349
+ rects.push(`<rect x="${rx}" y="${ry}" width="${half}" height="${pixelSize}" fill="${faceHex}"/>`);
350
+ } else if (cell === "a") {
351
+ rects.push(`<rect x="${rx}" y="${ry + half}" width="${pixelSize}" height="${half}" fill="${faceHex}"/>`);
352
+ } else if (cell === "e" || cell === "d") {
353
+ rects.push(`<rect x="${rx}" y="${ry}" width="${pixelSize}" height="${pixelSize}" fill="${darkHex}"/>`);
354
+ } else if (cell === "s") {
355
+ rects.push(`<rect x="${rx}" y="${ry}" width="${pixelSize}" height="${pixelSize}" fill="${faceHex}"/>`);
356
+ rects.push(`<rect x="${rx}" y="${ry + half}" width="${pixelSize}" height="${half}" fill="${darkHex}"/>`);
357
+ } else if (cell === "n") {
358
+ rects.push(`<rect x="${rx}" y="${ry}" width="${pixelSize}" height="${pixelSize}" fill="${faceHex}"/>`);
359
+ rects.push(`<rect x="${rx + quarter}" y="${ry}" width="${half}" height="${pixelSize}" fill="${darkHex}"/>`);
360
+ } else if (cell === "m") {
361
+ rects.push(`<rect x="${rx}" y="${ry}" width="${pixelSize}" height="${pixelSize}" fill="${faceHex}"/>`);
362
+ rects.push(`<rect x="${rx}" y="${ry}" width="${pixelSize}" height="${half}" fill="${darkHex}"/>`);
363
+ } else if (cell === "q") {
364
+ rects.push(`<rect x="${rx}" y="${ry}" width="${pixelSize}" height="${pixelSize}" fill="${faceHex}"/>`);
365
+ rects.push(`<rect x="${rx + half}" y="${ry + half}" width="${half}" height="${half}" fill="${darkHex}"/>`);
366
+ } else if (cell === "r") {
367
+ rects.push(`<rect x="${rx}" y="${ry}" width="${pixelSize}" height="${pixelSize}" fill="${faceHex}"/>`);
368
+ rects.push(`<rect x="${rx}" y="${ry + half}" width="${half}" height="${half}" fill="${darkHex}"/>`);
369
+ } else if (cell === "h") {
370
+ rects.push(`<rect x="${rx}" y="${ry}" width="${pixelSize}" height="${pixelSize}" fill="${hatHex}"/>`);
371
+ } else if (cell === "k") {
372
+ rects.push(`<rect x="${rx}" y="${ry}" width="${pixelSize}" height="${pixelSize}" fill="${hatHex}"/>`);
373
+ }
374
+ }
375
+ }
376
+ const bg = background ? `<rect width="${w}" height="${h}" fill="${background}"/>
377
+ ` : "";
378
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}" shape-rendering="crispEdges">
379
+ ${bg}${rects.join(`
380
+ `)}
381
+ </svg>`;
382
+ }
383
+ function renderTerminal(dna, frame = 0) {
384
+ const traits = decodeDNA(dna);
385
+ const grid = generateGrid(traits, frame);
386
+ const faceHueDeg = traits.faceHue * 30;
387
+ const hatHueDeg = traits.hatHue * 30;
388
+ const faceRgb = hslToRgb(faceHueDeg, 0.5, 0.5);
389
+ const darkRgb = hslToRgb(faceHueDeg, 0.5, 0.28);
390
+ const hatRgb = hslToRgb(hatHueDeg, 0.5, 0.5);
391
+ const faceAnsi = `\x1B[38;2;${faceRgb[0]};${faceRgb[1]};${faceRgb[2]}m`;
392
+ const darkAnsi = `\x1B[38;2;${darkRgb[0]};${darkRgb[1]};${darkRgb[2]}m`;
393
+ const hatAnsi = `\x1B[38;2;${hatRgb[0]};${hatRgb[1]};${hatRgb[2]}m`;
394
+ const reset = "\x1B[0m";
395
+ const faceBg = `\x1B[48;2;${faceRgb[0]};${faceRgb[1]};${faceRgb[2]}m`;
396
+ const lines = [];
397
+ for (const row of grid) {
398
+ let line = "";
399
+ for (const cell of row) {
400
+ if (cell === "_") {
401
+ line += " ";
402
+ } else if (cell === "f") {
403
+ line += `${faceAnsi}██${reset}`;
404
+ } else if (cell === "l") {
405
+ line += `${faceAnsi}▌${reset} `;
406
+ } else if (cell === "e" || cell === "d") {
407
+ line += `${darkAnsi}██${reset}`;
408
+ } else if (cell === "s") {
409
+ line += `${darkAnsi}${faceBg}▄▄${reset}`;
410
+ } else if (cell === "n") {
411
+ line += `${darkAnsi}${faceBg}▐▌${reset}`;
412
+ } else if (cell === "m") {
413
+ line += `${darkAnsi}${faceBg}▀▀${reset}`;
414
+ } else if (cell === "q") {
415
+ line += `${darkAnsi}${faceBg} ▗${reset}`;
416
+ } else if (cell === "r") {
417
+ line += `${darkAnsi}${faceBg}▖ ${reset}`;
418
+ } else if (cell === "a") {
419
+ line += `${faceAnsi}▄▄${reset}`;
420
+ } else if (cell === "h") {
421
+ line += `${hatAnsi}██${reset}`;
422
+ } else if (cell === "k") {
423
+ line += `${hatAnsi}▐▌${reset}`;
424
+ }
425
+ }
426
+ lines.push(line);
427
+ }
428
+ return lines.join(`
429
+ `);
430
+ }
431
+ function renderTerminalSmall(dna, frame = 0) {
432
+ const traits = decodeDNA(dna);
433
+ const grid = generateGrid(traits, frame);
434
+ const faceHueDeg = traits.faceHue * 30;
435
+ const hatHueDeg = traits.hatHue * 30;
436
+ const faceRgb = hslToRgb(faceHueDeg, 0.5, 0.5);
437
+ const darkRgb = hslToRgb(faceHueDeg, 0.5, 0.28);
438
+ const hatRgb = hslToRgb(hatHueDeg, 0.5, 0.5);
439
+ function cellRgb(cell) {
440
+ if (cell === "f" || cell === "l" || cell === "a" || cell === "q" || cell === "r")
441
+ return faceRgb;
442
+ if (cell === "e" || cell === "s" || cell === "n" || cell === "d" || cell === "m")
443
+ return darkRgb;
444
+ if (cell === "h" || cell === "k")
445
+ return hatRgb;
446
+ return null;
447
+ }
448
+ const reset = "\x1B[0m";
449
+ const lines = [];
450
+ for (let r = 0;r < grid.length; r += 2) {
451
+ const topRow = grid[r];
452
+ const botRow = r + 1 < grid.length ? grid[r + 1] : null;
453
+ let line = "";
454
+ for (let c = 0;c < topRow.length; c++) {
455
+ const top = cellRgb(topRow[c]);
456
+ const bot = botRow ? cellRgb(botRow[c]) : null;
457
+ if (top && bot) {
458
+ line += `\x1B[38;2;${top[0]};${top[1]};${top[2]}m\x1B[48;2;${bot[0]};${bot[1]};${bot[2]}m▀${reset}`;
459
+ } else if (top) {
460
+ line += `\x1B[38;2;${top[0]};${top[1]};${top[2]}m▀${reset}`;
461
+ } else if (bot) {
462
+ line += `\x1B[38;2;${bot[0]};${bot[1]};${bot[2]}m▄${reset}`;
463
+ } else {
464
+ line += " ";
465
+ }
466
+ }
467
+ lines.push(line);
468
+ }
469
+ return lines.join(`
470
+ `);
471
+ }
472
+
473
+ // packages/termlings/src/cli.ts
474
+ var args = process.argv.slice(2);
475
+ var flags = new Set;
476
+ var input;
477
+ for (const arg of args) {
478
+ if (arg.startsWith("--")) {
479
+ flags.add(arg.slice(2));
480
+ } else if (arg === "-h") {
481
+ flags.add("help");
482
+ } else if (!input) {
483
+ input = arg;
484
+ }
485
+ }
486
+ if (flags.has("help") || flags.has("h")) {
487
+ console.log(`Usage: termlings [dna-or-name] [options]
488
+
489
+ Render a termling in your terminal.
490
+
491
+ Arguments:
492
+ <dna> 7-char hex DNA string (e.g. 0a3f201)
493
+ <name> Any string — generates deterministic avatar
494
+
495
+ Options:
496
+ --walk Animate walking
497
+ --talk Animate talking
498
+ --wave Animate waving
499
+ --compact Half-height rendering
500
+ --random Generate a random termling
501
+ --svg Output SVG instead of terminal art
502
+ --info Show DNA traits info
503
+ --help Show this help`);
504
+ process.exit(0);
505
+ }
506
+ if (flags.has("random") || !input) {
507
+ input = generateRandomDNA();
508
+ }
509
+ var isDNA = /^[0-9a-f]{6,7}$/i.test(input);
510
+ var dna = isDNA ? input : encodeDNA(traitsFromName(input));
511
+ var DIM = "\x1B[2m";
512
+ var RESET = "\x1B[0m";
513
+ if (!isDNA) {
514
+ console.log(`${DIM}"${input}" → dna: ${dna}${RESET}
515
+ `);
516
+ } else {
517
+ console.log(`${DIM}dna: ${dna}${RESET}
518
+ `);
519
+ }
520
+ if (flags.has("info")) {
521
+ const traits = decodeDNA(dna);
522
+ console.log("Traits:", JSON.stringify(traits, null, 2));
523
+ console.log();
524
+ }
525
+ if (flags.has("svg")) {
526
+ console.log(renderSVG(dna, 10, 0, null));
527
+ process.exit(0);
528
+ }
529
+ var animate = flags.has("walk") || flags.has("talk") || flags.has("wave");
530
+ var compact = flags.has("compact");
531
+ if (!animate) {
532
+ const output = compact ? renderTerminalSmall(dna) : renderTerminal(dna);
533
+ console.log(output);
534
+ process.exit(0);
535
+ }
536
+ var traits = decodeDNA(dna);
537
+ var legFrameCount = LEGS[traits.legs].length;
538
+ var faceRgb = hslToRgb(traits.faceHue * 30, 0.5, 0.5);
539
+ var darkRgb = hslToRgb(traits.faceHue * 30, 0.5, 0.28);
540
+ var hatRgb = hslToRgb(traits.hatHue * 30, 0.5, 0.5);
541
+ var walkFrame = 0;
542
+ var talkFrame = 0;
543
+ var waveFrame = 0;
544
+ var tick = 0;
545
+ process.stdout.write("\x1B[?25l");
546
+ function cleanup() {
547
+ process.stdout.write(`\x1B[?25h
548
+ `);
549
+ process.exit(0);
550
+ }
551
+ process.on("SIGINT", cleanup);
552
+ process.on("SIGTERM", cleanup);
553
+ var firstGrid = generateGrid(traits, 0, 0, 0);
554
+ var firstOutput = compact ? renderSmallFromGrid(firstGrid) : renderFromGrid(firstGrid);
555
+ var lineCount = firstOutput.split(`
556
+ `).length;
557
+ process.stdout.write(firstOutput + `
558
+ `);
559
+ setInterval(() => {
560
+ tick++;
561
+ if (flags.has("walk"))
562
+ walkFrame = tick % legFrameCount;
563
+ if (flags.has("talk"))
564
+ talkFrame = tick % 2;
565
+ if (flags.has("wave"))
566
+ waveFrame = tick % 2 + 1;
567
+ const grid = generateGrid(traits, walkFrame, talkFrame, waveFrame);
568
+ const output = compact ? renderSmallFromGrid(grid) : renderFromGrid(grid);
569
+ process.stdout.write(`\x1B[${lineCount}A`);
570
+ process.stdout.write(output + `
571
+ `);
572
+ }, 300);
573
+ function renderFromGrid(grid) {
574
+ const faceAnsi = `\x1B[38;2;${faceRgb[0]};${faceRgb[1]};${faceRgb[2]}m`;
575
+ const darkAnsi = `\x1B[38;2;${darkRgb[0]};${darkRgb[1]};${darkRgb[2]}m`;
576
+ const hatAnsi = `\x1B[38;2;${hatRgb[0]};${hatRgb[1]};${hatRgb[2]}m`;
577
+ const reset = "\x1B[0m";
578
+ const faceBg = `\x1B[48;2;${faceRgb[0]};${faceRgb[1]};${faceRgb[2]}m`;
579
+ const lines = [];
580
+ for (const row of grid) {
581
+ let line = "";
582
+ for (const cell of row) {
583
+ if (cell === "_")
584
+ line += " ";
585
+ else if (cell === "f")
586
+ line += `${faceAnsi}██${reset}`;
587
+ else if (cell === "l")
588
+ line += `${faceAnsi}▌${reset} `;
589
+ else if (cell === "e" || cell === "d")
590
+ line += `${darkAnsi}██${reset}`;
591
+ else if (cell === "s")
592
+ line += `${darkAnsi}${faceBg}▄▄${reset}`;
593
+ else if (cell === "n")
594
+ line += `${darkAnsi}${faceBg}▐▌${reset}`;
595
+ else if (cell === "m")
596
+ line += `${darkAnsi}${faceBg}▀▀${reset}`;
597
+ else if (cell === "q")
598
+ line += `${darkAnsi}${faceBg} ▗${reset}`;
599
+ else if (cell === "r")
600
+ line += `${darkAnsi}${faceBg}▖ ${reset}`;
601
+ else if (cell === "a")
602
+ line += `${faceAnsi}▄▄${reset}`;
603
+ else if (cell === "h")
604
+ line += `${hatAnsi}██${reset}`;
605
+ else if (cell === "k")
606
+ line += `${hatAnsi}▐▌${reset}`;
607
+ }
608
+ lines.push(line);
609
+ }
610
+ return lines.join(`
611
+ `);
612
+ }
613
+ function renderSmallFromGrid(grid) {
614
+ const reset = "\x1B[0m";
615
+ function cellRgb(cell) {
616
+ if (cell === "f" || cell === "l" || cell === "a" || cell === "q" || cell === "r")
617
+ return faceRgb;
618
+ if (cell === "e" || cell === "s" || cell === "n" || cell === "d" || cell === "m")
619
+ return darkRgb;
620
+ if (cell === "h" || cell === "k")
621
+ return hatRgb;
622
+ return null;
623
+ }
624
+ const lines = [];
625
+ for (let r = 0;r < grid.length; r += 2) {
626
+ const topRow = grid[r];
627
+ const botRow = r + 1 < grid.length ? grid[r + 1] : null;
628
+ let line = "";
629
+ for (let c = 0;c < topRow.length; c++) {
630
+ const top = cellRgb(topRow[c]);
631
+ const bot = botRow ? cellRgb(botRow[c]) : null;
632
+ if (top && bot) {
633
+ line += `\x1B[38;2;${top[0]};${top[1]};${top[2]}m\x1B[48;2;${bot[0]};${bot[1]};${bot[2]}m▀${reset}`;
634
+ } else if (top) {
635
+ line += `\x1B[38;2;${top[0]};${top[1]};${top[2]}m▀${reset}`;
636
+ } else if (bot) {
637
+ line += `\x1B[38;2;${bot[0]};${bot[1]};${bot[2]}m▄${reset}`;
638
+ } else {
639
+ line += " ";
640
+ }
641
+ }
642
+ lines.push(line);
643
+ }
644
+ return lines.join(`
645
+ `);
646
+ }
package/package.json CHANGED
@@ -1,15 +1,18 @@
1
1
  {
2
2
  "name": "termlings",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Pixel art avatar system. 32M+ unique characters from a 7-char hex DNA string.",
7
+ "bin": {
8
+ "termlings": "./bin/termlings.js"
9
+ },
7
10
  "repository": {
8
11
  "type": "git",
9
12
  "url": "https://github.com/tomtev/touchgrass",
10
- "directory": "packages/avatar"
13
+ "directory": "packages/termlings"
11
14
  },
12
- "keywords": ["avatar", "pixel-art", "svelte", "react", "vue", "terminal", "ink"],
15
+ "keywords": ["avatar", "pixel-art", "svelte", "react", "vue", "terminal", "ink", "cli"],
13
16
  "exports": {
14
17
  ".": "./src/index.ts",
15
18
  "./svelte": "./src/svelte/index.ts",
@@ -17,5 +20,5 @@
17
20
  "./vue": "./src/vue/index.ts",
18
21
  "./ink": "./src/ink/index.ts"
19
22
  },
20
- "files": ["src"]
23
+ "files": ["src", "bin"]
21
24
  }
package/src/cli.ts ADDED
@@ -0,0 +1,196 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ renderTerminal,
4
+ renderTerminalSmall,
5
+ renderSVG,
6
+ decodeDNA,
7
+ encodeDNA,
8
+ generateRandomDNA,
9
+ traitsFromName,
10
+ generateGrid,
11
+ hslToRgb,
12
+ LEGS,
13
+ } from "./index.js";
14
+ import type { Pixel } from "./index.js";
15
+
16
+ const args = process.argv.slice(2);
17
+ const flags = new Set<string>();
18
+ let input: string | undefined;
19
+
20
+ for (const arg of args) {
21
+ if (arg.startsWith("--")) {
22
+ flags.add(arg.slice(2));
23
+ } else if (arg === "-h") {
24
+ flags.add("help");
25
+ } else if (!input) {
26
+ input = arg;
27
+ }
28
+ }
29
+
30
+ if (flags.has("help") || flags.has("h")) {
31
+ console.log(`Usage: termlings [dna-or-name] [options]
32
+
33
+ Render a termling in your terminal.
34
+
35
+ Arguments:
36
+ <dna> 7-char hex DNA string (e.g. 0a3f201)
37
+ <name> Any string — generates deterministic avatar
38
+
39
+ Options:
40
+ --walk Animate walking
41
+ --talk Animate talking
42
+ --wave Animate waving
43
+ --compact Half-height rendering
44
+ --random Generate a random termling
45
+ --svg Output SVG instead of terminal art
46
+ --info Show DNA traits info
47
+ --help Show this help`);
48
+ process.exit(0);
49
+ }
50
+
51
+ if (flags.has("random") || !input) {
52
+ input = generateRandomDNA();
53
+ }
54
+
55
+ // Resolve DNA: hex string or name
56
+ const isDNA = /^[0-9a-f]{6,7}$/i.test(input);
57
+ const dna = isDNA ? input : encodeDNA(traitsFromName(input));
58
+
59
+ const DIM = "\x1b[2m";
60
+ const RESET = "\x1b[0m";
61
+
62
+ if (!isDNA) {
63
+ console.log(`${DIM}"${input}" → dna: ${dna}${RESET}\n`);
64
+ } else {
65
+ console.log(`${DIM}dna: ${dna}${RESET}\n`);
66
+ }
67
+
68
+ if (flags.has("info")) {
69
+ const traits = decodeDNA(dna);
70
+ console.log("Traits:", JSON.stringify(traits, null, 2));
71
+ console.log();
72
+ }
73
+
74
+ if (flags.has("svg")) {
75
+ console.log(renderSVG(dna, 10, 0, null));
76
+ process.exit(0);
77
+ }
78
+
79
+ const animate = flags.has("walk") || flags.has("talk") || flags.has("wave");
80
+ const compact = flags.has("compact");
81
+
82
+ if (!animate) {
83
+ // Static render using ANSI colors
84
+ const output = compact ? renderTerminalSmall(dna) : renderTerminal(dna);
85
+ console.log(output);
86
+ process.exit(0);
87
+ }
88
+
89
+ // --- Animated render with ANSI escape codes ---
90
+ const traits = decodeDNA(dna);
91
+ const legFrameCount = LEGS[traits.legs].length;
92
+ const faceRgb = hslToRgb(traits.faceHue * 30, 0.5, 0.5);
93
+ const darkRgb = hslToRgb(traits.faceHue * 30, 0.5, 0.28);
94
+ const hatRgb = hslToRgb(traits.hatHue * 30, 0.5, 0.5);
95
+
96
+ let walkFrame = 0;
97
+ let talkFrame = 0;
98
+ let waveFrame = 0;
99
+ let tick = 0;
100
+
101
+ // Hide cursor
102
+ process.stdout.write("\x1b[?25l");
103
+
104
+ function cleanup() {
105
+ process.stdout.write("\x1b[?25h\n");
106
+ process.exit(0);
107
+ }
108
+ process.on("SIGINT", cleanup);
109
+ process.on("SIGTERM", cleanup);
110
+
111
+ // Draw first frame
112
+ const firstGrid = generateGrid(traits, 0, 0, 0);
113
+ const firstOutput = compact
114
+ ? renderSmallFromGrid(firstGrid)
115
+ : renderFromGrid(firstGrid);
116
+ const lineCount = firstOutput.split("\n").length;
117
+ process.stdout.write(firstOutput + "\n");
118
+
119
+ setInterval(() => {
120
+ tick++;
121
+
122
+ if (flags.has("walk")) walkFrame = tick % legFrameCount;
123
+ if (flags.has("talk")) talkFrame = tick % 2;
124
+ if (flags.has("wave")) waveFrame = (tick % 2) + 1;
125
+
126
+ const grid = generateGrid(traits, walkFrame, talkFrame, waveFrame);
127
+ const output = compact
128
+ ? renderSmallFromGrid(grid)
129
+ : renderFromGrid(grid);
130
+
131
+ // Move cursor up to overwrite previous frame
132
+ process.stdout.write(`\x1b[${lineCount}A`);
133
+ process.stdout.write(output + "\n");
134
+ }, 300);
135
+
136
+ function renderFromGrid(grid: Pixel[][]): string {
137
+ const faceAnsi = `\x1b[38;2;${faceRgb[0]};${faceRgb[1]};${faceRgb[2]}m`;
138
+ const darkAnsi = `\x1b[38;2;${darkRgb[0]};${darkRgb[1]};${darkRgb[2]}m`;
139
+ const hatAnsi = `\x1b[38;2;${hatRgb[0]};${hatRgb[1]};${hatRgb[2]}m`;
140
+ const reset = "\x1b[0m";
141
+ const faceBg = `\x1b[48;2;${faceRgb[0]};${faceRgb[1]};${faceRgb[2]}m`;
142
+
143
+ const lines: string[] = [];
144
+ for (const row of grid) {
145
+ let line = "";
146
+ for (const cell of row) {
147
+ if (cell === "_") line += " ";
148
+ else if (cell === "f") line += `${faceAnsi}██${reset}`;
149
+ else if (cell === "l") line += `${faceAnsi}▌${reset} `;
150
+ else if (cell === "e" || cell === "d") line += `${darkAnsi}██${reset}`;
151
+ else if (cell === "s") line += `${darkAnsi}${faceBg}▄▄${reset}`;
152
+ else if (cell === "n") line += `${darkAnsi}${faceBg}▐▌${reset}`;
153
+ else if (cell === "m") line += `${darkAnsi}${faceBg}▀▀${reset}`;
154
+ else if (cell === "q") line += `${darkAnsi}${faceBg} ▗${reset}`;
155
+ else if (cell === "r") line += `${darkAnsi}${faceBg}▖ ${reset}`;
156
+ else if (cell === "a") line += `${faceAnsi}▄▄${reset}`;
157
+ else if (cell === "h") line += `${hatAnsi}██${reset}`;
158
+ else if (cell === "k") line += `${hatAnsi}▐▌${reset}`;
159
+ }
160
+ lines.push(line);
161
+ }
162
+ return lines.join("\n");
163
+ }
164
+
165
+ function renderSmallFromGrid(grid: Pixel[][]): string {
166
+ const reset = "\x1b[0m";
167
+
168
+ function cellRgb(cell: Pixel): [number, number, number] | null {
169
+ if (cell === "f" || cell === "l" || cell === "a" || cell === "q" || cell === "r") return faceRgb;
170
+ if (cell === "e" || cell === "s" || cell === "n" || cell === "d" || cell === "m") return darkRgb;
171
+ if (cell === "h" || cell === "k") return hatRgb;
172
+ return null;
173
+ }
174
+
175
+ const lines: string[] = [];
176
+ for (let r = 0; r < grid.length; r += 2) {
177
+ const topRow = grid[r];
178
+ const botRow = r + 1 < grid.length ? grid[r + 1] : null;
179
+ let line = "";
180
+ for (let c = 0; c < topRow.length; c++) {
181
+ const top = cellRgb(topRow[c]);
182
+ const bot = botRow ? cellRgb(botRow[c]) : null;
183
+ if (top && bot) {
184
+ line += `\x1b[38;2;${top[0]};${top[1]};${top[2]}m\x1b[48;2;${bot[0]};${bot[1]};${bot[2]}m▀${reset}`;
185
+ } else if (top) {
186
+ line += `\x1b[38;2;${top[0]};${top[1]};${top[2]}m▀${reset}`;
187
+ } else if (bot) {
188
+ line += `\x1b[38;2;${bot[0]};${bot[1]};${bot[2]}m▄${reset}`;
189
+ } else {
190
+ line += " ";
191
+ }
192
+ }
193
+ lines.push(line);
194
+ }
195
+ return lines.join("\n");
196
+ }