vibedino 0.1.0

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.
package/dist/cli.cjs ADDED
@@ -0,0 +1,1365 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/cli.ts
21
+ var cli_exports = {};
22
+ __export(cli_exports, {
23
+ cli: () => cli
24
+ });
25
+ module.exports = __toCommonJS(cli_exports);
26
+
27
+ // src/engine/GameLoop.ts
28
+ var GameLoop = class {
29
+ constructor(onUpdate, onRender, fps = 30) {
30
+ this.onUpdate = onUpdate;
31
+ this.onRender = onRender;
32
+ this.tickMs = Math.floor(1e3 / fps);
33
+ }
34
+ onUpdate;
35
+ onRender;
36
+ intervalId = null;
37
+ running = false;
38
+ tickMs;
39
+ start() {
40
+ if (this.running) return;
41
+ this.running = true;
42
+ this.intervalId = setInterval(() => {
43
+ if (!this.running) return;
44
+ this.onUpdate(1);
45
+ this.onRender();
46
+ }, this.tickMs);
47
+ }
48
+ stop() {
49
+ this.running = false;
50
+ if (this.intervalId) {
51
+ clearInterval(this.intervalId);
52
+ this.intervalId = null;
53
+ }
54
+ }
55
+ isRunning() {
56
+ return this.running;
57
+ }
58
+ };
59
+
60
+ // src/engine/Renderer.ts
61
+ var ESC = "\x1B";
62
+ var CSI = `${ESC}[`;
63
+ var Color = {
64
+ reset: `${CSI}0m`,
65
+ // Foreground
66
+ fg: (n) => `${CSI}38;5;${n}m`,
67
+ // Background
68
+ bg: (n) => `${CSI}48;5;${n}m`,
69
+ // RGB foreground
70
+ fgRgb: (r, g, b) => `${CSI}38;2;${r};${g};${b}m`,
71
+ // RGB background
72
+ bgRgb: (r, g, b) => `${CSI}48;2;${r};${g};${b}m`,
73
+ bold: `${CSI}1m`,
74
+ dim: `${CSI}2m`
75
+ };
76
+ var Renderer = class {
77
+ buffer = [];
78
+ width = 0;
79
+ height = 0;
80
+ defaultBg = "";
81
+ defaultFg = "";
82
+ constructor() {
83
+ this.width = process.stdout.columns || 80;
84
+ this.height = process.stdout.rows || 24;
85
+ this.clearBuffer();
86
+ process.stdout.write(`${CSI}?1049h${CSI}2J${CSI}?25l`);
87
+ process.stdout.on("resize", () => {
88
+ this.width = process.stdout.columns || 80;
89
+ this.height = process.stdout.rows || 24;
90
+ this.clearBuffer();
91
+ });
92
+ }
93
+ getWidth() {
94
+ return this.width;
95
+ }
96
+ getHeight() {
97
+ return this.height;
98
+ }
99
+ setDefaultColors(fg, bg) {
100
+ this.defaultFg = fg;
101
+ this.defaultBg = bg;
102
+ }
103
+ clearBuffer() {
104
+ this.buffer = [];
105
+ for (let y = 0; y < this.height; y++) {
106
+ this.buffer[y] = [];
107
+ for (let x = 0; x < this.width; x++) {
108
+ this.buffer[y][x] = { char: " ", fg: this.defaultFg, bg: this.defaultBg };
109
+ }
110
+ }
111
+ }
112
+ drawChar(x, y, ch, fg = "", bg = "") {
113
+ const rx = Math.round(x);
114
+ const ry = Math.round(y);
115
+ if (ry >= 0 && ry < this.height && rx >= 0 && rx < this.width) {
116
+ this.buffer[ry][rx] = { char: ch, fg: fg || this.defaultFg, bg: bg || this.defaultBg };
117
+ }
118
+ }
119
+ drawSprite(x, y, sprite, fg = "", bg = "") {
120
+ for (let row = 0; row < sprite.length; row++) {
121
+ const line = sprite[row];
122
+ for (let col = 0; col < line.length; col++) {
123
+ const ch = line[col];
124
+ if (ch !== " ") {
125
+ this.drawChar(x + col, y + row, ch, fg, bg);
126
+ }
127
+ }
128
+ }
129
+ }
130
+ drawText(x, y, text, fg = "", bg = "") {
131
+ for (let i = 0; i < text.length; i++) {
132
+ this.drawChar(x + i, y, text[i], fg, bg);
133
+ }
134
+ }
135
+ // Fill a rectangular area with a background color
136
+ fillRect(x, y, w, h, bg) {
137
+ for (let row = 0; row < h; row++) {
138
+ for (let col = 0; col < w; col++) {
139
+ const rx = Math.round(x + col);
140
+ const ry = Math.round(y + row);
141
+ if (ry >= 0 && ry < this.height && rx >= 0 && rx < this.width) {
142
+ this.buffer[ry][rx].bg = bg;
143
+ }
144
+ }
145
+ }
146
+ }
147
+ // Fill entire row with background color
148
+ fillRow(y, bg) {
149
+ if (y >= 0 && y < this.height) {
150
+ for (let x = 0; x < this.width; x++) {
151
+ this.buffer[y][x].bg = bg;
152
+ }
153
+ }
154
+ }
155
+ present() {
156
+ let out = "";
157
+ for (let y = 0; y < this.height; y++) {
158
+ out += `${CSI}${y + 1};1H`;
159
+ let lastFg = "";
160
+ let lastBg = "";
161
+ for (let x = 0; x < this.width; x++) {
162
+ const cell = this.buffer[y][x];
163
+ if (cell.fg !== lastFg || cell.bg !== lastBg) {
164
+ out += Color.reset;
165
+ if (cell.bg) out += cell.bg;
166
+ if (cell.fg) out += cell.fg;
167
+ lastFg = cell.fg;
168
+ lastBg = cell.bg;
169
+ }
170
+ out += cell.char;
171
+ }
172
+ out += Color.reset;
173
+ }
174
+ process.stdout.write(out);
175
+ }
176
+ destroy() {
177
+ process.stdout.write(`${Color.reset}${CSI}?25h${CSI}?1049l`);
178
+ }
179
+ };
180
+
181
+ // src/engine/Input.ts
182
+ var Input = class {
183
+ actionQueue = [];
184
+ ducking = false;
185
+ rl = null;
186
+ bind() {
187
+ if (process.stdin.isTTY) {
188
+ process.stdin.setRawMode(true);
189
+ }
190
+ process.stdin.resume();
191
+ process.stdin.setEncoding("utf8");
192
+ process.stdin.on("data", (key) => {
193
+ if (key === "") {
194
+ this.actionQueue.push(4 /* QUIT */);
195
+ return;
196
+ }
197
+ if (key === "\x1B[A" || key === " ") {
198
+ if (this.ducking) {
199
+ this.ducking = false;
200
+ this.actionQueue.push(2 /* DUCK_RELEASE */);
201
+ }
202
+ this.actionQueue.push(0 /* JUMP */);
203
+ return;
204
+ }
205
+ if (key === "\x1B[B") {
206
+ if (!this.ducking) {
207
+ this.ducking = true;
208
+ this.actionQueue.push(1 /* DUCK */);
209
+ }
210
+ return;
211
+ }
212
+ if (key === "\x1B" || key === "q" || key === "Q") {
213
+ this.actionQueue.push(4 /* QUIT */);
214
+ return;
215
+ }
216
+ if (key === "r" || key === "R") {
217
+ this.actionQueue.push(3 /* RESTART */);
218
+ return;
219
+ }
220
+ this.actionQueue.push(5 /* ANY_KEY */);
221
+ });
222
+ }
223
+ poll() {
224
+ const actions = [...this.actionQueue];
225
+ this.actionQueue = [];
226
+ return actions;
227
+ }
228
+ releaseDuck() {
229
+ this.ducking = false;
230
+ }
231
+ clear() {
232
+ this.actionQueue = [];
233
+ }
234
+ destroy() {
235
+ if (process.stdin.isTTY) {
236
+ process.stdin.setRawMode(false);
237
+ }
238
+ process.stdin.pause();
239
+ }
240
+ };
241
+
242
+ // src/scenes/SceneManager.ts
243
+ var SceneManager = class {
244
+ current = null;
245
+ set(scene) {
246
+ this.current = scene;
247
+ }
248
+ update(dt) {
249
+ this.current?.update(dt);
250
+ }
251
+ render(renderer) {
252
+ this.current?.render(renderer);
253
+ }
254
+ handleInput(actions) {
255
+ this.current?.handleInput(actions);
256
+ }
257
+ };
258
+
259
+ // src/assets/sprites.ts
260
+ var DINO_RUN_1 = [
261
+ " \u2584\u2588\u2588\u2588\u2588\u2588",
262
+ " \u2588\u2588\u2591\u2588 \u2588\u2588",
263
+ " \u2584 \u2588\u2588\u2588\u2588\u2588\u2588 ",
264
+ " \u2588\u2584\u2588\u2588\u2588\u2588\u2588\u2580 ",
265
+ " \u2580\u2588\u2588\u2588\u2588\u2580 ",
266
+ " \u2588\u2580\u2580\u2584 "
267
+ ];
268
+ var DINO_RUN_2 = [
269
+ " \u2584\u2588\u2588\u2588\u2588\u2588",
270
+ " \u2588\u2588\u2591\u2588 \u2588\u2588",
271
+ " \u2584 \u2588\u2588\u2588\u2588\u2588\u2588 ",
272
+ " \u2588\u2584\u2588\u2588\u2588\u2588\u2588\u2580 ",
273
+ " \u2580\u2588\u2588\u2588\u2588\u2580 ",
274
+ " \u2584\u2580 \u2588\u2580 "
275
+ ];
276
+ var DINO_JUMP = [
277
+ " \u2584\u2588\u2588\u2588\u2588\u2588",
278
+ " \u2588\u2588\u2591\u2588 \u2588\u2588",
279
+ " \u2584 \u2588\u2588\u2588\u2588\u2588\u2588 ",
280
+ " \u2588\u2584\u2588\u2588\u2588\u2588\u2588\u2580 ",
281
+ " \u2580\u2588\u2588\u2588\u2588\u2580 ",
282
+ " \u2588\u2580 \u2588\u2580 "
283
+ ];
284
+ var DINO_DUCK_1 = [
285
+ " ",
286
+ " ",
287
+ " ",
288
+ " \u2584\u2588\u2588\u2588\u2588\u2588\u2588\u2584\u2588\u2588",
289
+ " \u2588\u2588\u2588\u2588\u2588\u2591\u2588\u2588\u2588\u2588",
290
+ " \u2580\u2580 \u2580\u2580 "
291
+ ];
292
+ var DINO_DUCK_2 = [
293
+ " ",
294
+ " ",
295
+ " ",
296
+ " \u2584\u2588\u2588\u2588\u2588\u2588\u2588\u2584\u2588\u2588",
297
+ " \u2588\u2588\u2588\u2588\u2588\u2591\u2588\u2588\u2588\u2588",
298
+ " \u2580\u2580\u2580\u2580 "
299
+ ];
300
+ var DINO_DEAD = [
301
+ " \u2584\u2588\u2588\u2588\u2588\u2588",
302
+ " \u2588\u2588X\u2588X\u2588\u2588",
303
+ " \u2584 \u2588\u2588\u2588\u2588\u2588\u2588 ",
304
+ " \u2588\u2584\u2588\u2588\u2588\u2588\u2588\u2580 ",
305
+ " \u2580\u2588\u2588\u2588\u2588\u2580 ",
306
+ " \u2588\u2580 \u2588\u2580 "
307
+ ];
308
+ var CACTUS_SMALL = [
309
+ " \u2584\u2588\u2584 ",
310
+ "\u2580\u2588\u2588\u2588\u2580",
311
+ " \u2588 ",
312
+ " \u2588 "
313
+ ];
314
+ var CACTUS_LARGE = [
315
+ " \u2588 \u2584 ",
316
+ " \u2584\u2588\u2584\u2588\u2588 ",
317
+ "\u2580\u2588\u2588\u2588\u2588\u2580 ",
318
+ " \u2588\u2580 ",
319
+ " \u2588 "
320
+ ];
321
+ var CACTUS_GROUP = [
322
+ " \u2584\u2588\u2584 \u2584\u2588\u2584 ",
323
+ "\u2580\u2588\u2588\u2588\u2580\u2588\u2588\u2588\u2588\u2580",
324
+ " \u2588 \u2588 ",
325
+ " \u2588 \u2588 "
326
+ ];
327
+ var NODE_MODULES = [
328
+ "\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557",
329
+ "\u2551node_mods/\u2551",
330
+ "\u2551 \u2593\u2593\u2593\u2593\u2593\u2593\u2593\u2593 \u2551",
331
+ "\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"
332
+ ];
333
+ var NODE_MODULES_SMALL = [
334
+ "\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2557",
335
+ "\u2551n_mod/\u2551",
336
+ "\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D"
337
+ ];
338
+ var TODO_BIRD_1 = [
339
+ " \u2584\u2580\u2580\u2584//TODO",
340
+ "\u2580\u2584\u2584\u2580\u2580 "
341
+ ];
342
+ var RATE_LIMIT = [
343
+ "\u2554\u2550\u2550\u2550\u2550\u2550\u2557",
344
+ "\u2551 429 \u2551",
345
+ "\u2551LIMIT\u2551",
346
+ "\u255A\u2550\u2550\u2550\u2550\u2550\u255D"
347
+ ];
348
+ var MERGE_CONFLICT = [
349
+ "\u25C4\u25C4\u25C4HEAD",
350
+ "\u2550\u2550\u2550\u2550\u2550\u2550\u2550",
351
+ "\u25BA\u25BA\u25BAmain"
352
+ ];
353
+ var GROUND_CHARS = "\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581\u2581";
354
+ var CLOUD_1 = [
355
+ " \u2584\u2584\u2584\u2584 ",
356
+ "\u2580\u2580\u2580\u2580\u2580\u2580\u2580\u2580 "
357
+ ];
358
+ var CLOUD_2 = [
359
+ " \u2584\u2584\u2584 ",
360
+ "\u2580\u2580\u2580\u2580\u2580 "
361
+ ];
362
+ var STAR_CHARS = ["\xB7", "\u2219", "\u2022", "\xB0"];
363
+ var GAME_OVER_TEXT = [
364
+ "\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557",
365
+ "\u2551 G A M E O V E R \u2551",
366
+ "\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"
367
+ ];
368
+ var TITLE_DINO = [
369
+ " \u2584\u2584\u2588\u2588\u2588\u2588\u2584\u2584 ",
370
+ " \u2584\u2588\u2588\u2591\u2591\u2588\u2588\u2584\u2588\u2588\u2588\u2588 ",
371
+ " \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 ",
372
+ " \u2584\u2584 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2580\u2580 ",
373
+ " \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2580 ",
374
+ " \u2580\u2580\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2580\u2580 ",
375
+ " \u2588\u2588\u2580\u2580\u2588\u2588 ",
376
+ " \u2588 \u2588 "
377
+ ];
378
+
379
+ // src/ui/Messages.ts
380
+ var TITLE_MESSAGES = {
381
+ claude: [
382
+ "Claude ran out of tokens!",
383
+ "Token limit reached. Time to play!",
384
+ "Rate limited by Claude. Quick, run!"
385
+ ],
386
+ copilot: [
387
+ "Copilot hit its limit!",
388
+ "Copilot needs a break. You play!",
389
+ "Rate limited. Copilot is resting."
390
+ ],
391
+ default: [
392
+ "No tokens? No problem!",
393
+ "Token drought detected!",
394
+ "AI is resting. Dino is not."
395
+ ]
396
+ };
397
+ var GAME_OVER_QUIPS = [
398
+ "Tokens still regenerating...",
399
+ "Your AI will be back soon.",
400
+ "That was fun! Press R to retry.",
401
+ "node_modules got you!",
402
+ "Even dinos get merge conflicts.",
403
+ "HTTP 429: Too Many Jumps"
404
+ ];
405
+ function getTitleMessage(source) {
406
+ const pool = TITLE_MESSAGES[source ?? "default"] ?? TITLE_MESSAGES.default;
407
+ return pool[Math.floor(Math.random() * pool.length)];
408
+ }
409
+ function getGameOverQuip() {
410
+ return GAME_OVER_QUIPS[Math.floor(Math.random() * GAME_OVER_QUIPS.length)];
411
+ }
412
+
413
+ // src/assets/theme.ts
414
+ var SKY = {
415
+ top: Color.bgRgb(25, 25, 50),
416
+ // dark blue-black
417
+ mid: Color.bgRgb(35, 35, 65),
418
+ // slightly lighter
419
+ bottom: Color.bgRgb(50, 45, 75)
420
+ // dusk purple
421
+ };
422
+ var GROUND_BG = Color.bgRgb(60, 50, 30);
423
+ var GROUND_DEEP = Color.bgRgb(45, 38, 22);
424
+ var DINO_FG = Color.fgRgb(80, 200, 80);
425
+ var DINO_EYE = Color.fgRgb(255, 255, 255);
426
+ var CACTUS_FG = Color.fgRgb(50, 160, 50);
427
+ var OBSTACLE_FG = Color.fgRgb(200, 160, 60);
428
+ var BIRD_FG = Color.fgRgb(180, 100, 200);
429
+ var CLOUD_FG = Color.fgRgb(120, 120, 160);
430
+ var GROUND_FG = Color.fgRgb(140, 110, 60);
431
+ var SCORE_FG = Color.fgRgb(255, 255, 200);
432
+ var TITLE_FG = Color.fgRgb(100, 220, 100);
433
+ var GAMEOVER_FG = Color.fgRgb(255, 80, 80);
434
+ var PROMPT_FG = Color.fgRgb(255, 255, 100);
435
+ var HI_SCORE_FG = Color.fgRgb(255, 200, 50);
436
+ var QUIP_FG = Color.fgRgb(150, 150, 170);
437
+ function getObstacleColor(type) {
438
+ switch (type) {
439
+ case "cactus_small":
440
+ case "cactus_large":
441
+ case "cactus_group":
442
+ return CACTUS_FG;
443
+ case "todo_bird":
444
+ return BIRD_FG;
445
+ case "node_modules":
446
+ case "node_modules_small":
447
+ case "rate_limit":
448
+ case "merge_conflict":
449
+ return OBSTACLE_FG;
450
+ default:
451
+ return "";
452
+ }
453
+ }
454
+ function getSkyBg(y, height) {
455
+ const t = y / height;
456
+ const r = Math.round(25 + t * 35);
457
+ const g = Math.round(25 + t * 25);
458
+ const b = Math.round(50 + t * 30);
459
+ return Color.bgRgb(r, g, b);
460
+ }
461
+
462
+ // src/scenes/TitleScene.ts
463
+ var TitleScene = class {
464
+ message;
465
+ progressTick = 0;
466
+ progressTotal = 75;
467
+ ready = false;
468
+ onStart;
469
+ blinkTick = 0;
470
+ constructor(source, onStart) {
471
+ this.message = getTitleMessage(source);
472
+ this.onStart = onStart;
473
+ }
474
+ update(_dt) {
475
+ this.blinkTick++;
476
+ if (!this.ready) {
477
+ this.progressTick++;
478
+ if (this.progressTick >= this.progressTotal) {
479
+ this.ready = true;
480
+ }
481
+ }
482
+ }
483
+ render(renderer) {
484
+ renderer.clearBuffer();
485
+ const w = renderer.getWidth();
486
+ const h = renderer.getHeight();
487
+ for (let y = 0; y < h; y++) {
488
+ renderer.fillRow(y, getSkyBg(y, h));
489
+ }
490
+ const titleY = Math.floor(h * 0.12);
491
+ const titleStr = "V I B E D I N O";
492
+ renderer.drawText(Math.floor(w / 2) - Math.floor(titleStr.length / 2), titleY, titleStr, TITLE_FG);
493
+ const sub = "Token Exhaustion Entertainment";
494
+ renderer.drawText(Math.floor(w / 2) - Math.floor(sub.length / 2), titleY + 2, sub, QUIP_FG);
495
+ const dinoY = titleY + 5;
496
+ const dinoX = Math.floor(w / 2) - Math.floor(TITLE_DINO[0].length / 2);
497
+ renderer.drawSprite(dinoX, dinoY, TITLE_DINO, DINO_FG);
498
+ const groundY = dinoY + TITLE_DINO.length;
499
+ renderer.fillRow(groundY, GROUND_BG);
500
+ renderer.drawText(0, groundY, GROUND_CHARS.slice(0, w), GROUND_FG, GROUND_BG);
501
+ const msgY = groundY + 2;
502
+ renderer.drawText(Math.floor(w / 2) - Math.floor(this.message.length / 2), msgY, this.message, SCORE_FG);
503
+ const barY = msgY + 2;
504
+ if (!this.ready) {
505
+ const barWidth = 32;
506
+ const filled = Math.floor(this.progressTick / this.progressTotal * barWidth);
507
+ const barFull = "\u2588".repeat(filled);
508
+ const barEmpty = "\u2591".repeat(barWidth - filled);
509
+ const pct = Math.floor(this.progressTick / this.progressTotal * 100);
510
+ renderer.drawText(Math.floor(w / 2) - Math.floor(barWidth / 2) - 1, barY, "\u2551", QUIP_FG);
511
+ renderer.drawText(Math.floor(w / 2) - Math.floor(barWidth / 2), barY, barFull, Color.fgRgb(80, 220, 80));
512
+ renderer.drawText(Math.floor(w / 2) - Math.floor(barWidth / 2) + filled, barY, barEmpty, QUIP_FG);
513
+ renderer.drawText(Math.floor(w / 2) + Math.floor(barWidth / 2), barY, "\u2551", QUIP_FG);
514
+ const label = `Regenerating tokens... ${pct}%`;
515
+ renderer.drawText(Math.floor(w / 2) - Math.floor(label.length / 2), barY + 1, label, QUIP_FG);
516
+ } else {
517
+ if (Math.floor(this.blinkTick / 18) % 2 === 0) {
518
+ const prompt = ">>> Press SPACE to start <<<";
519
+ renderer.drawText(Math.floor(w / 2) - Math.floor(prompt.length / 2), barY, prompt, PROMPT_FG);
520
+ }
521
+ }
522
+ const footer = "SPACE/UP: Jump | DOWN: Duck | Q: Quit";
523
+ renderer.drawText(Math.floor(w / 2) - Math.floor(footer.length / 2), h - 2, footer, QUIP_FG);
524
+ renderer.present();
525
+ }
526
+ handleInput(actions) {
527
+ if (!this.ready) return;
528
+ for (const action of actions) {
529
+ if (action === 4 /* QUIT */) {
530
+ process.exit(0);
531
+ }
532
+ if (action === 0 /* JUMP */ || action === 5 /* ANY_KEY */) {
533
+ this.onStart();
534
+ return;
535
+ }
536
+ }
537
+ }
538
+ };
539
+
540
+ // src/entities/Entity.ts
541
+ var Entity = class {
542
+ x;
543
+ y;
544
+ vx = 0;
545
+ vy = 0;
546
+ grounded = false;
547
+ active = true;
548
+ sprite = [];
549
+ hitboxDef = { offsetX: 0, offsetY: 0, width: 0, height: 0 };
550
+ constructor(x, y) {
551
+ this.x = x;
552
+ this.y = y;
553
+ }
554
+ getHitbox() {
555
+ return {
556
+ x: this.x + this.hitboxDef.offsetX,
557
+ y: this.y + this.hitboxDef.offsetY,
558
+ width: this.hitboxDef.width,
559
+ height: this.hitboxDef.height
560
+ };
561
+ }
562
+ update(_dt) {
563
+ }
564
+ };
565
+
566
+ // src/entities/Dino.ts
567
+ var Dino = class extends Entity {
568
+ state = 0 /* IDLE */;
569
+ animFrame = 0;
570
+ animTick = 0;
571
+ animSpeed = 4;
572
+ // ticks per frame swap
573
+ constructor(x, groundY) {
574
+ super(x, groundY);
575
+ this.grounded = true;
576
+ this.updateSprite();
577
+ this.updateHitbox();
578
+ }
579
+ setState(state) {
580
+ if (this.state === state) return;
581
+ this.state = state;
582
+ this.animFrame = 0;
583
+ this.animTick = 0;
584
+ this.updateSprite();
585
+ this.updateHitbox();
586
+ }
587
+ update(_dt) {
588
+ this.animTick++;
589
+ if (this.animTick >= this.animSpeed) {
590
+ this.animTick = 0;
591
+ this.animFrame = (this.animFrame + 1) % 2;
592
+ this.updateSprite();
593
+ }
594
+ if (this.state === 2 /* JUMPING */ && this.grounded) {
595
+ this.setState(1 /* RUNNING */);
596
+ }
597
+ }
598
+ updateSprite() {
599
+ switch (this.state) {
600
+ case 0 /* IDLE */:
601
+ case 1 /* RUNNING */:
602
+ this.sprite = this.animFrame === 0 ? DINO_RUN_1 : DINO_RUN_2;
603
+ break;
604
+ case 2 /* JUMPING */:
605
+ this.sprite = DINO_JUMP;
606
+ break;
607
+ case 3 /* DUCKING */:
608
+ this.sprite = this.animFrame === 0 ? DINO_DUCK_1 : DINO_DUCK_2;
609
+ break;
610
+ case 4 /* DEAD */:
611
+ this.sprite = DINO_DEAD;
612
+ break;
613
+ }
614
+ }
615
+ updateHitbox() {
616
+ if (this.state === 3 /* DUCKING */) {
617
+ this.hitboxDef = { offsetX: 2, offsetY: 3, width: 9, height: 3 };
618
+ } else {
619
+ this.hitboxDef = { offsetX: 3, offsetY: 0, width: 8, height: 5 };
620
+ }
621
+ }
622
+ };
623
+
624
+ // src/entities/Ground.ts
625
+ var Ground = class {
626
+ offset = 0;
627
+ pattern;
628
+ constructor() {
629
+ this.pattern = GROUND_CHARS + GROUND_CHARS;
630
+ }
631
+ update(speed) {
632
+ this.offset = (this.offset + speed) % GROUND_CHARS.length;
633
+ }
634
+ getOffset() {
635
+ return this.offset;
636
+ }
637
+ getLine(width) {
638
+ const start = Math.floor(this.offset);
639
+ return this.pattern.slice(start, start + width);
640
+ }
641
+ };
642
+
643
+ // src/entities/Cloud.ts
644
+ var Cloud = class extends Entity {
645
+ constructor(x, y) {
646
+ super(x, y);
647
+ this.sprite = Math.random() > 0.5 ? CLOUD_1 : CLOUD_2;
648
+ this.vx = -(0.2 + Math.random() * 0.3);
649
+ }
650
+ update(_dt) {
651
+ this.x += this.vx;
652
+ }
653
+ };
654
+
655
+ // src/entities/obstacles/Obstacle.ts
656
+ var Obstacle = class extends Entity {
657
+ type;
658
+ isFlying;
659
+ constructor(x, y, type, sprite, isFlying = false) {
660
+ super(x, y);
661
+ this.type = type;
662
+ this.sprite = sprite;
663
+ this.isFlying = isFlying;
664
+ this.grounded = !isFlying;
665
+ const spriteWidth = Math.max(...sprite.map((l) => l.length));
666
+ const spriteHeight = sprite.length;
667
+ this.hitboxDef = {
668
+ offsetX: 1,
669
+ offsetY: 1,
670
+ width: Math.max(1, spriteWidth - 2),
671
+ height: Math.max(1, spriteHeight - 1)
672
+ };
673
+ }
674
+ update(_dt) {
675
+ this.x += this.vx;
676
+ }
677
+ };
678
+
679
+ // src/systems/SpawnSystem.ts
680
+ var OBSTACLE_DEFS = [
681
+ { type: "cactus_small", sprite: CACTUS_SMALL, isFlying: false, minDifficulty: 0 },
682
+ { type: "cactus_large", sprite: CACTUS_LARGE, isFlying: false, minDifficulty: 0.05 },
683
+ { type: "cactus_group", sprite: CACTUS_GROUP, isFlying: false, minDifficulty: 0.1 },
684
+ { type: "node_modules_small", sprite: NODE_MODULES_SMALL, isFlying: false, minDifficulty: 0.15 },
685
+ { type: "node_modules", sprite: NODE_MODULES, isFlying: false, minDifficulty: 0.25 },
686
+ { type: "merge_conflict", sprite: MERGE_CONFLICT, isFlying: false, minDifficulty: 0.3 },
687
+ { type: "rate_limit", sprite: RATE_LIMIT, isFlying: false, minDifficulty: 0.35 },
688
+ { type: "todo_bird", sprite: TODO_BIRD_1, isFlying: true, minDifficulty: 0.4 }
689
+ ];
690
+ var SpawnSystem = class {
691
+ ticksSinceSpawn = 0;
692
+ minGapTicks = 45;
693
+ maxGapTicks = 90;
694
+ nextSpawnAt;
695
+ constructor() {
696
+ this.nextSpawnAt = this.randomGap();
697
+ }
698
+ update(obstacles, screenWidth, groundY, speed, difficulty) {
699
+ this.ticksSinceSpawn++;
700
+ this.minGapTicks = Math.max(25, 45 - difficulty * 20);
701
+ this.maxGapTicks = Math.max(50, 90 - difficulty * 30);
702
+ if (this.ticksSinceSpawn >= this.nextSpawnAt) {
703
+ const available = OBSTACLE_DEFS.filter((d) => d.minDifficulty <= difficulty);
704
+ const def = available[Math.floor(Math.random() * available.length)];
705
+ const spriteH = def.sprite.length;
706
+ const yPos = def.isFlying ? groundY - spriteH - 3 - Math.floor(Math.random() * 3) : groundY - spriteH;
707
+ const obs = new Obstacle(screenWidth + 2, yPos, def.type, def.sprite, def.isFlying);
708
+ obs.vx = speed;
709
+ obstacles.push(obs);
710
+ this.ticksSinceSpawn = 0;
711
+ this.nextSpawnAt = this.randomGap();
712
+ }
713
+ }
714
+ randomGap() {
715
+ return this.minGapTicks + Math.floor(Math.random() * (this.maxGapTicks - this.minGapTicks));
716
+ }
717
+ reset() {
718
+ this.ticksSinceSpawn = 0;
719
+ this.nextSpawnAt = this.randomGap();
720
+ }
721
+ };
722
+
723
+ // src/systems/DifficultySystem.ts
724
+ var DifficultySystem = class {
725
+ ticks = 0;
726
+ maxSpeedMultiplier = 2.5;
727
+ rampDurationTicks = 30 * 120;
728
+ // ~120 seconds at 30fps to reach max
729
+ /** Current difficulty 0..1 */
730
+ difficulty = 0;
731
+ /** Current game scroll speed (negative = moving left) */
732
+ speed = -1.2;
733
+ baseSpeed = -1.2;
734
+ update() {
735
+ this.ticks++;
736
+ this.difficulty = Math.min(1, this.ticks / this.rampDurationTicks);
737
+ this.speed = this.baseSpeed * (1 + this.difficulty * (this.maxSpeedMultiplier - 1));
738
+ }
739
+ reset() {
740
+ this.ticks = 0;
741
+ this.difficulty = 0;
742
+ this.speed = this.baseSpeed;
743
+ }
744
+ };
745
+
746
+ // src/systems/ScoreSystem.ts
747
+ var ScoreSystem = class {
748
+ score = 0;
749
+ tickAccumulator = 0;
750
+ pointsPerSecond = 10;
751
+ ticksPerPoint;
752
+ milestone = false;
753
+ // true on the tick a milestone is hit
754
+ constructor(fps = 30) {
755
+ this.ticksPerPoint = Math.floor(fps / this.pointsPerSecond);
756
+ }
757
+ update() {
758
+ this.milestone = false;
759
+ this.tickAccumulator++;
760
+ if (this.tickAccumulator >= this.ticksPerPoint) {
761
+ this.tickAccumulator = 0;
762
+ this.score++;
763
+ if (this.score > 0 && this.score % 100 === 0) {
764
+ this.milestone = true;
765
+ }
766
+ }
767
+ }
768
+ getDisplay() {
769
+ return String(this.score).padStart(5, "0");
770
+ }
771
+ reset() {
772
+ this.score = 0;
773
+ this.tickAccumulator = 0;
774
+ this.milestone = false;
775
+ }
776
+ };
777
+
778
+ // src/ui/HUD.ts
779
+ var HUD = class {
780
+ render(renderer, score, highScore) {
781
+ const w = renderer.getWidth();
782
+ renderer.drawText(w - 7, 1, score, SCORE_FG);
783
+ renderer.drawText(w - 16, 1, "HI", HI_SCORE_FG);
784
+ renderer.drawText(w - 13, 1, highScore, HI_SCORE_FG);
785
+ }
786
+ };
787
+
788
+ // src/engine/Physics.ts
789
+ var GRAVITY = 0.35;
790
+ var JUMP_VELOCITY = -3.5;
791
+ var MAX_FALL_SPEED = 5;
792
+ function applyGravity(entity, groundY) {
793
+ if (entity.grounded) return;
794
+ entity.vy += GRAVITY;
795
+ if (entity.vy > MAX_FALL_SPEED) {
796
+ entity.vy = MAX_FALL_SPEED;
797
+ }
798
+ entity.y += entity.vy;
799
+ if (entity.y >= groundY) {
800
+ entity.y = groundY;
801
+ entity.vy = 0;
802
+ entity.grounded = true;
803
+ }
804
+ }
805
+ function jump(entity) {
806
+ if (!entity.grounded) return;
807
+ entity.vy = JUMP_VELOCITY;
808
+ entity.grounded = false;
809
+ }
810
+
811
+ // src/engine/Collision.ts
812
+ function checkCollision(a, b) {
813
+ return a.x < b.x + b.width && a.x + a.width > b.x && a.y < b.y + b.height && a.y + a.height > b.y;
814
+ }
815
+
816
+ // src/scenes/PlayScene.ts
817
+ var PlayScene = class {
818
+ dino;
819
+ ground;
820
+ obstacles = [];
821
+ clouds = [];
822
+ spawnSystem = new SpawnSystem();
823
+ difficulty = new DifficultySystem();
824
+ scoreSystem = new ScoreSystem();
825
+ hud = new HUD();
826
+ groundY;
827
+ screenWidth;
828
+ screenHeight;
829
+ dead = false;
830
+ highScoreDisplay;
831
+ onGameOver;
832
+ cloudSpawnTick = 0;
833
+ stars = [];
834
+ constructor(screenWidth, screenHeight, highScore, onGameOver) {
835
+ this.screenWidth = screenWidth;
836
+ this.screenHeight = screenHeight;
837
+ this.groundY = screenHeight - 3;
838
+ this.dino = new Dino(6, this.groundY - 6);
839
+ this.dino.setState(1 /* RUNNING */);
840
+ this.ground = new Ground();
841
+ this.highScoreDisplay = String(highScore).padStart(5, "0");
842
+ this.onGameOver = onGameOver;
843
+ for (let i = 0; i < 25; i++) {
844
+ this.stars.push({
845
+ x: Math.floor(Math.random() * screenWidth),
846
+ y: Math.floor(Math.random() * (this.groundY - 2)),
847
+ char: STAR_CHARS[Math.floor(Math.random() * STAR_CHARS.length)],
848
+ bright: 40 + Math.floor(Math.random() * 80)
849
+ });
850
+ }
851
+ }
852
+ update(dt) {
853
+ if (this.dead) return;
854
+ this.difficulty.update();
855
+ this.scoreSystem.update();
856
+ applyGravity(this.dino, this.groundY - 6);
857
+ this.dino.update(dt);
858
+ this.ground.update(Math.abs(this.difficulty.speed));
859
+ this.spawnSystem.update(
860
+ this.obstacles,
861
+ this.screenWidth,
862
+ this.groundY,
863
+ this.difficulty.speed,
864
+ this.difficulty.difficulty
865
+ );
866
+ for (const obs of this.obstacles) {
867
+ obs.vx = this.difficulty.speed;
868
+ obs.update(dt);
869
+ }
870
+ this.obstacles = this.obstacles.filter((o) => o.x + 20 > 0);
871
+ this.cloudSpawnTick++;
872
+ if (this.cloudSpawnTick > 60 + Math.random() * 120) {
873
+ this.cloudSpawnTick = 0;
874
+ if (this.clouds.length < 5) {
875
+ this.clouds.push(new Cloud(this.screenWidth + 5, 1 + Math.floor(Math.random() * 5)));
876
+ }
877
+ }
878
+ for (const cloud of this.clouds) cloud.update(dt);
879
+ this.clouds = this.clouds.filter((c) => c.x + 12 > 0);
880
+ const dinoBox = this.dino.getHitbox();
881
+ for (const obs of this.obstacles) {
882
+ if (checkCollision(dinoBox, obs.getHitbox())) {
883
+ this.dead = true;
884
+ this.dino.setState(4 /* DEAD */);
885
+ this.onGameOver(this.scoreSystem.score);
886
+ return;
887
+ }
888
+ }
889
+ }
890
+ render(renderer) {
891
+ renderer.clearBuffer();
892
+ for (let y = 0; y < this.groundY; y++) {
893
+ renderer.fillRow(y, getSkyBg(y, this.groundY));
894
+ }
895
+ for (const star of this.stars) {
896
+ renderer.drawChar(
897
+ star.x,
898
+ star.y,
899
+ star.char,
900
+ Color.fgRgb(star.bright, star.bright, star.bright + 30)
901
+ );
902
+ }
903
+ for (let y = this.groundY; y < this.screenHeight; y++) {
904
+ renderer.fillRow(y, y === this.groundY ? GROUND_BG : GROUND_DEEP);
905
+ }
906
+ for (let y = this.groundY + 1; y < this.screenHeight; y++) {
907
+ const offset = Math.floor(this.ground.getOffset() * 0.5) % this.screenWidth;
908
+ for (let x = 0; x < this.screenWidth; x++) {
909
+ if ((x + offset + y * 7) % 13 === 0) {
910
+ renderer.drawChar(x, y, "\xB7", Color.fgRgb(80, 65, 40), GROUND_DEEP);
911
+ }
912
+ if ((x + offset + y * 11) % 19 === 0) {
913
+ renderer.drawChar(x, y, "\u2219", Color.fgRgb(70, 55, 35), GROUND_DEEP);
914
+ }
915
+ }
916
+ }
917
+ for (const cloud of this.clouds) {
918
+ renderer.drawSprite(Math.round(cloud.x), Math.round(cloud.y), cloud.sprite, CLOUD_FG);
919
+ }
920
+ const groundLine = this.ground.getLine(this.screenWidth);
921
+ renderer.drawText(0, this.groundY, groundLine, GROUND_FG, GROUND_BG);
922
+ for (const obs of this.obstacles) {
923
+ const color = getObstacleColor(obs.type);
924
+ renderer.drawSprite(Math.round(obs.x), Math.round(obs.y), obs.sprite, color);
925
+ }
926
+ const dinoColor = this.dead ? GAMEOVER_FG : DINO_FG;
927
+ renderer.drawSprite(
928
+ Math.round(this.dino.x),
929
+ Math.round(this.dino.y),
930
+ this.dino.sprite,
931
+ dinoColor
932
+ );
933
+ this.hud.render(renderer, this.scoreSystem.getDisplay(), this.highScoreDisplay);
934
+ if (this.scoreSystem.milestone) {
935
+ const msg = `+${this.scoreSystem.score}!`;
936
+ renderer.drawText(
937
+ Math.floor(this.screenWidth / 2) - Math.floor(msg.length / 2),
938
+ 2,
939
+ msg,
940
+ GAMEOVER_FG
941
+ );
942
+ }
943
+ renderer.present();
944
+ }
945
+ handleInput(actions) {
946
+ if (this.dead) return;
947
+ for (const action of actions) {
948
+ switch (action) {
949
+ case 0 /* JUMP */:
950
+ if (this.dino.state === 3 /* DUCKING */) {
951
+ this.dino.setState(1 /* RUNNING */);
952
+ }
953
+ jump(this.dino);
954
+ if (!this.dino.grounded) {
955
+ this.dino.setState(2 /* JUMPING */);
956
+ }
957
+ break;
958
+ case 1 /* DUCK */:
959
+ if (this.dino.grounded) {
960
+ this.dino.setState(3 /* DUCKING */);
961
+ }
962
+ break;
963
+ case 2 /* DUCK_RELEASE */:
964
+ if (this.dino.state === 3 /* DUCKING */) {
965
+ this.dino.setState(1 /* RUNNING */);
966
+ }
967
+ break;
968
+ case 4 /* QUIT */:
969
+ this.dead = true;
970
+ this.onGameOver(this.scoreSystem.score);
971
+ break;
972
+ }
973
+ }
974
+ }
975
+ };
976
+
977
+ // src/scenes/GameOverScene.ts
978
+ var GameOverScene = class {
979
+ score;
980
+ highScore;
981
+ isNewHighScore;
982
+ quip;
983
+ onRestart;
984
+ onQuit;
985
+ blinkTick = 0;
986
+ constructor(score, highScore, isNewHighScore, onRestart, onQuit) {
987
+ this.score = score;
988
+ this.highScore = highScore;
989
+ this.isNewHighScore = isNewHighScore;
990
+ this.quip = getGameOverQuip();
991
+ this.onRestart = onRestart;
992
+ this.onQuit = onQuit;
993
+ }
994
+ update(_dt) {
995
+ this.blinkTick++;
996
+ }
997
+ render(renderer) {
998
+ renderer.clearBuffer();
999
+ const w = renderer.getWidth();
1000
+ const h = renderer.getHeight();
1001
+ for (let y = 0; y < h; y++) {
1002
+ renderer.fillRow(y, Color.bgRgb(20, 15, 30));
1003
+ }
1004
+ const centerY = Math.floor(h / 2) - 6;
1005
+ const bannerX = Math.floor(w / 2) - Math.floor(GAME_OVER_TEXT[0].length / 2);
1006
+ renderer.drawSprite(bannerX, centerY, GAME_OVER_TEXT, GAMEOVER_FG);
1007
+ const dinoX = Math.floor(w / 2) - Math.floor(DINO_DEAD[0].length / 2);
1008
+ renderer.drawSprite(dinoX, centerY + 4, DINO_DEAD, Color.fgRgb(100, 100, 100));
1009
+ const scoreY = centerY + 4 + DINO_DEAD.length + 1;
1010
+ const scoreText = `Score ${String(this.score).padStart(5, "0")}`;
1011
+ renderer.drawText(Math.floor(w / 2) - Math.floor(scoreText.length / 2), scoreY, scoreText, SCORE_FG);
1012
+ const hiY = scoreY + 2;
1013
+ if (this.isNewHighScore) {
1014
+ const hiText = "* * * NEW HIGH SCORE! * * *";
1015
+ if (Math.floor(this.blinkTick / 8) % 2 === 0) {
1016
+ renderer.drawText(Math.floor(w / 2) - Math.floor(hiText.length / 2), hiY, hiText, HI_SCORE_FG);
1017
+ }
1018
+ } else {
1019
+ const hiText = `Best ${String(this.highScore).padStart(5, "0")}`;
1020
+ renderer.drawText(Math.floor(w / 2) - Math.floor(hiText.length / 2), hiY, hiText, HI_SCORE_FG);
1021
+ }
1022
+ renderer.drawText(Math.floor(w / 2) - Math.floor(this.quip.length / 2), hiY + 2, this.quip, QUIP_FG);
1023
+ if (Math.floor(this.blinkTick / 18) % 2 === 0) {
1024
+ const prompt = "[R] Restart [Q] Quit";
1025
+ renderer.drawText(Math.floor(w / 2) - Math.floor(prompt.length / 2), hiY + 4, prompt, PROMPT_FG);
1026
+ }
1027
+ renderer.present();
1028
+ }
1029
+ handleInput(actions) {
1030
+ for (const action of actions) {
1031
+ if (action === 3 /* RESTART */) {
1032
+ this.onRestart();
1033
+ return;
1034
+ }
1035
+ if (action === 4 /* QUIT */) {
1036
+ this.onQuit();
1037
+ return;
1038
+ }
1039
+ }
1040
+ }
1041
+ };
1042
+
1043
+ // src/storage/ScoreStore.ts
1044
+ var import_fs = require("fs");
1045
+ var import_path = require("path");
1046
+ var import_os = require("os");
1047
+ var STORE_DIR = (0, import_path.join)((0, import_os.homedir)(), ".vibedino");
1048
+ var SCORES_FILE = (0, import_path.join)(STORE_DIR, "scores.json");
1049
+ function ensureDir() {
1050
+ if (!(0, import_fs.existsSync)(STORE_DIR)) {
1051
+ (0, import_fs.mkdirSync)(STORE_DIR, { recursive: true });
1052
+ }
1053
+ }
1054
+ function load() {
1055
+ try {
1056
+ const raw = (0, import_fs.readFileSync)(SCORES_FILE, "utf-8");
1057
+ return JSON.parse(raw);
1058
+ } catch {
1059
+ return { highScore: 0, scores: [] };
1060
+ }
1061
+ }
1062
+ function save(data) {
1063
+ ensureDir();
1064
+ (0, import_fs.writeFileSync)(SCORES_FILE, JSON.stringify(data, null, 2), "utf-8");
1065
+ }
1066
+ function getHighScore() {
1067
+ return load().highScore;
1068
+ }
1069
+ function saveScore(score) {
1070
+ const data = load();
1071
+ const isNew = score > data.highScore;
1072
+ if (isNew) {
1073
+ data.highScore = score;
1074
+ }
1075
+ data.scores.push({ score, date: (/* @__PURE__ */ new Date()).toISOString() });
1076
+ data.scores.sort((a, b) => b.score - a.score);
1077
+ data.scores = data.scores.slice(0, 10);
1078
+ save(data);
1079
+ return { highScore: data.highScore, isNew };
1080
+ }
1081
+ function getTopScores() {
1082
+ return load().scores;
1083
+ }
1084
+ function resetScores() {
1085
+ save({ highScore: 0, scores: [] });
1086
+ }
1087
+
1088
+ // src/index.ts
1089
+ function startGame(options = {}) {
1090
+ return new Promise((resolve) => {
1091
+ const startTime = Date.now();
1092
+ const trigger = {
1093
+ source: options.source ?? "cli",
1094
+ reason: options.reason,
1095
+ metadata: options.metadata
1096
+ };
1097
+ const renderer = new Renderer();
1098
+ const input = new Input();
1099
+ const sceneManager = new SceneManager();
1100
+ input.bind();
1101
+ let finalResult = null;
1102
+ const cleanup = () => {
1103
+ loop.stop();
1104
+ input.destroy();
1105
+ renderer.destroy();
1106
+ if (finalResult) resolve(finalResult);
1107
+ };
1108
+ const startPlay = () => {
1109
+ const highScore = getHighScore();
1110
+ const scene = new PlayScene(
1111
+ renderer.getWidth(),
1112
+ renderer.getHeight(),
1113
+ highScore,
1114
+ (score) => {
1115
+ const { highScore: newHigh, isNew } = saveScore(score);
1116
+ finalResult = {
1117
+ score,
1118
+ highScore: newHigh,
1119
+ isNewHighScore: isNew,
1120
+ duration: Date.now() - startTime,
1121
+ trigger
1122
+ };
1123
+ sceneManager.set(new GameOverScene(
1124
+ score,
1125
+ newHigh,
1126
+ isNew,
1127
+ () => {
1128
+ input.clear();
1129
+ startPlay();
1130
+ },
1131
+ () => {
1132
+ cleanup();
1133
+ }
1134
+ ));
1135
+ }
1136
+ );
1137
+ sceneManager.set(scene);
1138
+ };
1139
+ const titleScene = new TitleScene(trigger.source, () => {
1140
+ input.clear();
1141
+ startPlay();
1142
+ });
1143
+ sceneManager.set(titleScene);
1144
+ const loop = new GameLoop(
1145
+ (dt) => {
1146
+ const actions = input.poll();
1147
+ sceneManager.handleInput(actions);
1148
+ sceneManager.update(dt);
1149
+ },
1150
+ () => {
1151
+ sceneManager.render(renderer);
1152
+ }
1153
+ );
1154
+ loop.start();
1155
+ process.on("SIGINT", cleanup);
1156
+ process.on("SIGTERM", cleanup);
1157
+ });
1158
+ }
1159
+
1160
+ // src/integrations/claudeCode.ts
1161
+ var import_fs2 = require("fs");
1162
+ var import_path2 = require("path");
1163
+ var HOOK_ENTRY = {
1164
+ matcher: "rate_limit|token_limit|quota_exceeded|overloaded",
1165
+ command: 'npx vibedino --trigger claude --reason "Token limit reached"',
1166
+ timeout: 3e5
1167
+ };
1168
+ function installClaudeHook(projectDir = process.cwd()) {
1169
+ const claudeDir = (0, import_path2.join)(projectDir, ".claude");
1170
+ const settingsPath = (0, import_path2.join)(claudeDir, "settings.json");
1171
+ if (!(0, import_fs2.existsSync)(claudeDir)) {
1172
+ (0, import_fs2.mkdirSync)(claudeDir, { recursive: true });
1173
+ }
1174
+ let settings = {};
1175
+ if ((0, import_fs2.existsSync)(settingsPath)) {
1176
+ try {
1177
+ settings = JSON.parse((0, import_fs2.readFileSync)(settingsPath, "utf-8"));
1178
+ } catch {
1179
+ }
1180
+ }
1181
+ if (!settings.hooks) {
1182
+ settings.hooks = {};
1183
+ }
1184
+ const hooks = settings.hooks;
1185
+ if (!hooks.PostToolUseFailure) {
1186
+ hooks.PostToolUseFailure = [];
1187
+ }
1188
+ const existing = hooks.PostToolUseFailure;
1189
+ if (!existing.some((h) => h.command?.includes("vibedino"))) {
1190
+ existing.push(HOOK_ENTRY);
1191
+ }
1192
+ (0, import_fs2.writeFileSync)(settingsPath, JSON.stringify(settings, null, 2), "utf-8");
1193
+ console.log(` Installed VibeDino hook in ${settingsPath}`);
1194
+ }
1195
+ function uninstallClaudeHook(projectDir = process.cwd()) {
1196
+ const settingsPath = (0, import_path2.join)(projectDir, ".claude", "settings.json");
1197
+ if (!(0, import_fs2.existsSync)(settingsPath)) {
1198
+ console.log(" No .claude/settings.json found.");
1199
+ return;
1200
+ }
1201
+ const settings = JSON.parse((0, import_fs2.readFileSync)(settingsPath, "utf-8"));
1202
+ const hooks = settings.hooks?.PostToolUseFailure;
1203
+ if (hooks) {
1204
+ settings.hooks.PostToolUseFailure = hooks.filter(
1205
+ (h) => !h.command?.includes("vibedino")
1206
+ );
1207
+ }
1208
+ (0, import_fs2.writeFileSync)(settingsPath, JSON.stringify(settings, null, 2), "utf-8");
1209
+ console.log(` Removed VibeDino hook from ${settingsPath}`);
1210
+ }
1211
+
1212
+ // src/integrations/copilotCli.ts
1213
+ var import_fs3 = require("fs");
1214
+ var import_path3 = require("path");
1215
+ var import_os2 = require("os");
1216
+ var PLUGIN_DIR = (0, import_path3.join)((0, import_os2.homedir)(), ".copilot", "plugins", "vibedino");
1217
+ var MANIFEST_PATH = (0, import_path3.join)(PLUGIN_DIR, "plugin.json");
1218
+ function generateCopilotPluginManifest() {
1219
+ return {
1220
+ name: "vibedino",
1221
+ version: "0.1.0",
1222
+ description: "Play dino runner when rate limited",
1223
+ hooks: {
1224
+ onError: {
1225
+ match: ["rate_limit", "token_exhausted", "429"],
1226
+ action: "exec",
1227
+ command: 'npx vibedino --trigger copilot --reason "Rate limited"'
1228
+ }
1229
+ }
1230
+ };
1231
+ }
1232
+ function installCopilotPlugin() {
1233
+ if (!(0, import_fs3.existsSync)(PLUGIN_DIR)) {
1234
+ (0, import_fs3.mkdirSync)(PLUGIN_DIR, { recursive: true });
1235
+ }
1236
+ const manifest = generateCopilotPluginManifest();
1237
+ (0, import_fs3.writeFileSync)(MANIFEST_PATH, JSON.stringify(manifest, null, 2), "utf-8");
1238
+ console.log(` Installed VibeDino Copilot plugin at ${MANIFEST_PATH}`);
1239
+ }
1240
+ function uninstallCopilotPlugin() {
1241
+ if (!(0, import_fs3.existsSync)(MANIFEST_PATH)) {
1242
+ console.log(" No VibeDino Copilot plugin found.");
1243
+ return;
1244
+ }
1245
+ (0, import_fs3.unlinkSync)(MANIFEST_PATH);
1246
+ try {
1247
+ (0, import_fs3.rmdirSync)(PLUGIN_DIR);
1248
+ } catch {
1249
+ }
1250
+ console.log(" Removed VibeDino Copilot plugin.");
1251
+ }
1252
+
1253
+ // src/cli.ts
1254
+ function printHelp() {
1255
+ console.log(`
1256
+ vibedino - Terminal T-Rex runner for when AI tools run out of tokens
1257
+
1258
+ USAGE
1259
+ vibedino [options]
1260
+
1261
+ OPTIONS
1262
+ --trigger <source> Trigger source: claude, copilot, cli (default: cli)
1263
+ --reason <text> Why the game was triggered
1264
+ --install <target> Install integration: claude, copilot
1265
+ --uninstall <target> Remove integration: claude, copilot
1266
+ --scores Show high scores and exit
1267
+ --reset Reset high scores
1268
+ --help, -h Show this help
1269
+ --version, -v Show version
1270
+
1271
+ CONTROLS
1272
+ SPACE / UP Jump
1273
+ DOWN Duck
1274
+ R Restart (on game over)
1275
+ Q / ESC Quit
1276
+ `);
1277
+ }
1278
+ function parseArgs(argv) {
1279
+ const args = {};
1280
+ for (let i = 0; i < argv.length; i++) {
1281
+ const arg = argv[i];
1282
+ if (arg === "--help" || arg === "-h") {
1283
+ args.help = true;
1284
+ } else if (arg === "--version" || arg === "-v") {
1285
+ args.version = true;
1286
+ } else if (arg === "--scores") {
1287
+ args.scores = true;
1288
+ } else if (arg === "--reset") {
1289
+ args.reset = true;
1290
+ } else if (arg === "--trigger" && argv[i + 1]) {
1291
+ args.trigger = argv[++i];
1292
+ } else if (arg === "--reason" && argv[i + 1]) {
1293
+ args.reason = argv[++i];
1294
+ } else if (arg === "--install" && argv[i + 1]) {
1295
+ args.install = argv[++i];
1296
+ } else if (arg === "--uninstall" && argv[i + 1]) {
1297
+ args.uninstall = argv[++i];
1298
+ }
1299
+ }
1300
+ return args;
1301
+ }
1302
+ async function cli(argv) {
1303
+ const args = parseArgs(argv);
1304
+ if (args.help) {
1305
+ printHelp();
1306
+ process.exit(0);
1307
+ }
1308
+ if (args.version) {
1309
+ console.log("vibedino v0.1.0");
1310
+ process.exit(0);
1311
+ }
1312
+ if (args.scores) {
1313
+ const hi = getHighScore();
1314
+ const top = getTopScores();
1315
+ console.log(`
1316
+ High Score: ${hi}
1317
+ `);
1318
+ if (top.length > 0) {
1319
+ console.log(" Top Scores:");
1320
+ top.forEach((s, i) => {
1321
+ console.log(` ${i + 1}. ${String(s.score).padStart(5, "0")} (${s.date.split("T")[0]})`);
1322
+ });
1323
+ console.log();
1324
+ }
1325
+ process.exit(0);
1326
+ }
1327
+ if (args.install) {
1328
+ if (args.install === "claude") installClaudeHook();
1329
+ else if (args.install === "copilot") installCopilotPlugin();
1330
+ else console.log(" Unknown target. Use: claude, copilot");
1331
+ process.exit(0);
1332
+ }
1333
+ if (args.uninstall) {
1334
+ if (args.uninstall === "claude") uninstallClaudeHook();
1335
+ else if (args.uninstall === "copilot") uninstallCopilotPlugin();
1336
+ else console.log(" Unknown target. Use: claude, copilot");
1337
+ process.exit(0);
1338
+ }
1339
+ if (args.reset) {
1340
+ resetScores();
1341
+ console.log(" Scores reset.");
1342
+ process.exit(0);
1343
+ }
1344
+ const options = {
1345
+ source: args.trigger ?? "cli",
1346
+ reason: args.reason
1347
+ };
1348
+ try {
1349
+ const result = await startGame(options);
1350
+ console.log(`
1351
+ Score: ${result.score} | High Score: ${result.highScore}`);
1352
+ if (result.isNewHighScore) {
1353
+ console.log(" New high score!");
1354
+ }
1355
+ console.log();
1356
+ } catch (err) {
1357
+ console.error(` Error: ${err.message}`);
1358
+ process.exit(1);
1359
+ }
1360
+ process.exit(0);
1361
+ }
1362
+ // Annotate the CommonJS export names for ESM import in node:
1363
+ 0 && (module.exports = {
1364
+ cli
1365
+ });