novac 2.2.1 → 2.3.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.
@@ -0,0 +1,2037 @@
1
+ /**
2
+ * Kitmisc - Miscellaneous utilities kit
3
+ */
4
+
5
+ // ─── BRAINFUCK ────────────────────────────────────────────────────────────────
6
+
7
+ /**
8
+ * @param {() => Promise<number>} inputSource - async fn that resolves to a byte value (char code)
9
+ * @returns {{ run, cell, reset, register }}
10
+ */
11
+ function makeBfObject(inputSource) {
12
+ const TAPE_SIZE = 30000;
13
+
14
+ let tape = new Uint8Array(TAPE_SIZE);
15
+ let ptr = 0;
16
+ const customCommands = new Map();
17
+
18
+ function reset() {
19
+ tape = new Uint8Array(TAPE_SIZE);
20
+ ptr = 0;
21
+ }
22
+
23
+ function register(char, handler) {
24
+ if (typeof char !== "string" || char.length !== 1)
25
+ throw new TypeError("register: char must be a single character");
26
+ if (typeof handler !== "function")
27
+ throw new TypeError("register: handler must be a function");
28
+ customCommands.set(char, handler);
29
+ }
30
+
31
+ async function run(code) {
32
+ const ops = Array.from(String(code));
33
+
34
+ const jumpTable = new Map();
35
+ const stack = [];
36
+ for (let i = 0; i < ops.length; i++) {
37
+ if (ops[i] === "[") {
38
+ stack.push(i);
39
+ } else if (ops[i] === "]") {
40
+ if (!stack.length) throw new SyntaxError(`Unmatched ] at position ${i}`);
41
+ const open = stack.pop();
42
+ jumpTable.set(open, i);
43
+ jumpTable.set(i, open);
44
+ }
45
+ }
46
+ if (stack.length)
47
+ throw new SyntaxError(`Unmatched [ at position ${stack[0]}`);
48
+
49
+ const output = [];
50
+ let ip = 0;
51
+
52
+ while (ip < ops.length) {
53
+ const op = ops[ip];
54
+
55
+ switch (op) {
56
+ case ">": ptr = (ptr + 1) % TAPE_SIZE; break;
57
+ case "<": ptr = (ptr - 1 + TAPE_SIZE) % TAPE_SIZE; break;
58
+ case "+": tape[ptr] = (tape[ptr] + 1) & 0xff; break;
59
+ case "-": tape[ptr] = (tape[ptr] - 1 + 256) & 0xff; break;
60
+ case ".": output.push(String.fromCharCode(tape[ptr])); break;
61
+ case ",": {
62
+ const byte = await inputSource();
63
+ tape[ptr] = typeof byte === "number" ? byte & 0xff : byte.charCodeAt(0);
64
+ break;
65
+ }
66
+ case "[": if (tape[ptr] === 0) ip = jumpTable.get(ip); break;
67
+ case "]": if (tape[ptr] !== 0) ip = jumpTable.get(ip); break;
68
+ default:
69
+ if (customCommands.has(op)) {
70
+ await customCommands.get(op)({
71
+ tape,
72
+ get ptr() { return ptr; },
73
+ set ptr(v) { ptr = ((v % TAPE_SIZE) + TAPE_SIZE) % TAPE_SIZE; },
74
+ output,
75
+ inputSource,
76
+ });
77
+ }
78
+ break;
79
+ }
80
+
81
+ ip++;
82
+ }
83
+
84
+ return output.join("");
85
+ }
86
+
87
+ return { run, reset, register, get cell() { return tape[ptr]; } };
88
+ }
89
+
90
+ // ─── OOK! ─────────────────────────────────────────────────────────────────────
91
+
92
+ /**
93
+ * Ook! interpreter — BF in monkey.
94
+ * https://esolangs.org/wiki/Ook!
95
+ * @param {() => Promise<number>} inputSource
96
+ * @returns {{ transpile, run, reset, cell }}
97
+ */
98
+ function makeOokObject(inputSource) {
99
+ const OOK_MAP = {
100
+ "Ook. Ook?": ">",
101
+ "Ook? Ook.": "<",
102
+ "Ook. Ook.": "+",
103
+ "Ook! Ook!": "-",
104
+ "Ook! Ook.": ".",
105
+ "Ook. Ook!": ",",
106
+ "Ook! Ook?": "[",
107
+ "Ook? Ook!": "]",
108
+ };
109
+
110
+ function transpile(source) {
111
+ const tokens = String(source).match(/Ook[.!?]/g);
112
+ if (!tokens) throw new SyntaxError("No Ook tokens found");
113
+ if (tokens.length & 1) throw new SyntaxError("Odd number of Ook tokens");
114
+ const bf = [];
115
+ for (let i = 0; i < tokens.length; i += 2) {
116
+ const pair = `${tokens[i]} ${tokens[i + 1]}`;
117
+ const cmd = OOK_MAP[pair];
118
+ if (!cmd) throw new SyntaxError(`Unknown Ook pair: "${pair}"`);
119
+ bf.push(cmd);
120
+ }
121
+ return bf.join("");
122
+ }
123
+
124
+ const bf = makeBfObject(inputSource);
125
+ return {
126
+ transpile,
127
+ async run(source) { return bf.run(transpile(source)); },
128
+ reset: bf.reset,
129
+ get cell() { return bf.cell; },
130
+ };
131
+ }
132
+
133
+ // ─── BEFUNGE-93 ───────────────────────────────────────────────────────────────
134
+
135
+ /**
136
+ * Befunge-93 interpreter — 2D grid-based esoteric language.
137
+ * https://esolangs.org/wiki/Befunge
138
+ * @param {() => Promise<number>} inputSource
139
+ * @returns {{ run }}
140
+ */
141
+ function makeBefungeObject(inputSource) {
142
+ const W = 80, H = 25;
143
+
144
+ async function run(source, maxSteps = 1_000_000) {
145
+ const lines = String(source).split("\n");
146
+ const grid = Array.from({ length: H }, (_, y) => {
147
+ const row = Array.from(lines[y] ?? "");
148
+ while (row.length < W) row.push(" ");
149
+ return row.slice(0, W);
150
+ });
151
+
152
+ let x = 0, y = 0, dx = 1, dy = 0;
153
+ const stack = [];
154
+ let stringMode = false;
155
+ const output = [];
156
+
157
+ const pop = () => stack.pop() ?? 0;
158
+ const push = (v) => stack.push(v | 0);
159
+ const wrap = (n, max) => ((n % max) + max) % max;
160
+
161
+ for (let step = 0; step < maxSteps; step++) {
162
+ const op = grid[y]?.[x] ?? " ";
163
+
164
+ if (stringMode) {
165
+ if (op === '"') stringMode = false;
166
+ else push(op.charCodeAt(0));
167
+ } else {
168
+ switch (op) {
169
+ case ">": dx = 1; dy = 0; break;
170
+ case "<": dx = -1; dy = 0; break;
171
+ case "^": dx = 0; dy = -1; break;
172
+ case "v": dx = 0; dy = 1; break;
173
+ case "?": {
174
+ const dirs = [[1,0],[-1,0],[0,1],[0,-1]];
175
+ [dx, dy] = dirs[Math.random() * 4 | 0];
176
+ break;
177
+ }
178
+ case "#":
179
+ x = wrap(x + dx, W);
180
+ y = wrap(y + dy, H);
181
+ break;
182
+ case '"': stringMode = true; break;
183
+ case "0": case "1": case "2": case "3": case "4":
184
+ case "5": case "6": case "7": case "8": case "9":
185
+ push(+op); break;
186
+ case "+": push(pop() + pop()); break;
187
+ case "-": { const b = pop(), a = pop(); push(a - b); break; }
188
+ case "*": push(pop() * pop()); break;
189
+ case "/": { const b = pop(), a = pop(); push(b === 0 ? 0 : (a / b) | 0); break; }
190
+ case "%": { const b = pop(), a = pop(); push(b === 0 ? 0 : a % b); break; }
191
+ case "!": push(pop() === 0 ? 1 : 0); break;
192
+ case "`": { const b = pop(), a = pop(); push(a > b ? 1 : 0); break; }
193
+ case ":": { const a = pop(); push(a); push(a); break; }
194
+ case "\\": { const b = pop(), a = pop(); push(b); push(a); break; }
195
+ case "$": pop(); break;
196
+ case ".": output.push(String(pop()) + " "); break;
197
+ case ",": output.push(String.fromCharCode(pop())); break;
198
+ case "&": {
199
+ const v = await inputSource();
200
+ push(typeof v === "number" ? v : parseInt(v) || 0);
201
+ break;
202
+ }
203
+ case "~": {
204
+ const v = await inputSource();
205
+ push(typeof v === "number" ? v : (String(v).charCodeAt(0) || 0));
206
+ break;
207
+ }
208
+ case "p": {
209
+ const gy = pop(), gx = pop(), val = pop();
210
+ if (grid[gy]) grid[gy][gx] = String.fromCharCode(val);
211
+ break;
212
+ }
213
+ case "g": {
214
+ const gy = pop(), gx = pop();
215
+ push((grid[gy]?.[gx] ?? " ").charCodeAt(0));
216
+ break;
217
+ }
218
+ case "_": dx = pop() === 0 ? 1 : -1; dy = 0; break;
219
+ case "|": dy = pop() === 0 ? 1 : -1; dx = 0; break;
220
+ case "@": return output.join("");
221
+ default: break;
222
+ }
223
+ }
224
+
225
+ x = wrap(x + dx, W);
226
+ y = wrap(y + dy, H);
227
+ }
228
+
229
+ throw new Error("Befunge: step limit exceeded (infinite loop?)");
230
+ }
231
+
232
+ return { run };
233
+ }
234
+
235
+ // ─── MARKOV CHAIN ─────────────────────────────────────────────────────────────
236
+
237
+ /**
238
+ * Character-level Markov chain text generator.
239
+ * @param {number} order - context window size (default 2)
240
+ * @returns {{ train, generate, clear, size }}
241
+ */
242
+ function makeMarkov(order = 2) {
243
+ const chain = new Map();
244
+
245
+ function train(text) {
246
+ const s = String(text);
247
+ for (let i = 0; i < s.length - order; i++) {
248
+ const key = s.slice(i, i + order);
249
+ const next = s[i + order];
250
+ if (!chain.has(key)) chain.set(key, []);
251
+ chain.get(key).push(next);
252
+ }
253
+ return api;
254
+ }
255
+
256
+ function generate(length = 200, seed = null) {
257
+ const keys = Array.from(chain.keys());
258
+ if (!keys.length) throw new Error("Markov: not trained");
259
+
260
+ let current;
261
+ if (seed && seed.length >= order && chain.has(seed.slice(-order))) {
262
+ current = seed.slice(-order);
263
+ } else {
264
+ current = keys[Math.random() * keys.length | 0];
265
+ }
266
+
267
+ const result = [seed ?? current];
268
+
269
+ for (let i = 0; i < length - order; i++) {
270
+ const options = chain.get(current);
271
+ if (!options) {
272
+ const fallback = keys[Math.random() * keys.length | 0];
273
+ result.push(fallback);
274
+ current = fallback;
275
+ continue;
276
+ }
277
+ const next = options[Math.random() * options.length | 0];
278
+ result.push(next);
279
+ current = current.slice(1) + next;
280
+ }
281
+
282
+ return result.join("");
283
+ }
284
+
285
+ function clear() { chain.clear(); return api; }
286
+
287
+ const api = { train, generate, clear, get size() { return chain.size; } };
288
+ return api;
289
+ }
290
+
291
+ // ─── ZALGO ────────────────────────────────────────────────────────────────────
292
+
293
+ // Combining characters above, through, and below
294
+ const ZALGO_UP = [
295
+ ...Array.from({ length: 0x0315 - 0x0300 + 1 }, (_, i) => String.fromCharCode(0x0300 + i)),
296
+ ...Array.from({ length: 0x0344 - 0x033D + 1 }, (_, i) => String.fromCharCode(0x033D + i)),
297
+ "\u0346", "\u034A", "\u034B", "\u034C",
298
+ ...Array.from({ length: 0x0352 - 0x0350 + 1 }, (_, i) => String.fromCharCode(0x0350 + i)),
299
+ "\u0357", "\u035B",
300
+ ...Array.from({ length: 0x036F - 0x0363 + 1 }, (_, i) => String.fromCharCode(0x0363 + i)),
301
+ ];
302
+ const ZALGO_MID = Array.from({ length: 0x0338 - 0x0334 + 1 }, (_, i) => String.fromCharCode(0x0334 + i));
303
+ const ZALGO_DOWN = [
304
+ ...Array.from({ length: 0x0319 - 0x0316 + 1 }, (_, i) => String.fromCharCode(0x0316 + i)),
305
+ ...Array.from({ length: 0x0320 - 0x031C + 1 }, (_, i) => String.fromCharCode(0x031C + i)),
306
+ ...Array.from({ length: 0x0333 - 0x0324 + 1 }, (_, i) => String.fromCharCode(0x0324 + i)),
307
+ ...Array.from({ length: 0x033C - 0x0339 + 1 }, (_, i) => String.fromCharCode(0x0339 + i)),
308
+ "\u0345",
309
+ ...Array.from({ length: 0x0349 - 0x0347 + 1 }, (_, i) => String.fromCharCode(0x0347 + i)),
310
+ "\u034D", "\u034E",
311
+ ...Array.from({ length: 0x0356 - 0x0353 + 1 }, (_, i) => String.fromCharCode(0x0353 + i)),
312
+ "\u0359", "\u035A",
313
+ ];
314
+
315
+ /**
316
+ * Corrupts text with Zalgo combining characters.
317
+ * @param {string} text
318
+ * @param {'mini'|'medium'|'maxi'} intensity
319
+ */
320
+ function zalgoify(text, intensity = "medium") {
321
+ const max = { mini: 1, medium: 4, maxi: 12 }[intensity] ?? 4;
322
+ const pick = (arr) => arr[Math.random() * arr.length | 0];
323
+ const count = () => Math.random() * max | 0;
324
+
325
+ return Array.from(String(text)).map(ch => {
326
+ if (ch === " " || ch === "\n") return ch;
327
+ let out = ch;
328
+ for (let i = 0; i < count(); i++) out += pick(ZALGO_UP);
329
+ for (let i = 0; i < count(); i++) out += pick(ZALGO_MID);
330
+ for (let i = 0; i < count(); i++) out += pick(ZALGO_DOWN);
331
+ return out;
332
+ }).join("");
333
+ }
334
+
335
+ // ─── UWUIFY ───────────────────────────────────────────────────────────────────
336
+
337
+ const UWU_FACES = ["(・`ω´・)", "OwO", "UwU", ">w<", "^w^", ":3", "x3", "uwu", "owo", "(⁄ ⁄•⁄ω⁄•⁄ ⁄)"];
338
+
339
+ /**
340
+ * UwU text transformer. Devastating.
341
+ * @param {string} text
342
+ */
343
+ function uwuify(text) {
344
+ return String(text)
345
+ .replace(/(?:r|l)/g, "w")
346
+ .replace(/(?:R|L)/g, "W")
347
+ .replace(/n([aeiou])/g, "ny$1")
348
+ .replace(/N([aeiou])/g, "Ny$1")
349
+ .replace(/N([AEIOU])/g, "NY$1")
350
+ .replace(/ove/g, "uv")
351
+ .replace(/th/g, "d")
352
+ .replace(/Th/g, "D")
353
+ .replace(/!/g, () => ` ${UWU_FACES[Math.random() * UWU_FACES.length | 0]}!`)
354
+ .replace(/\?/g, () => ` ${UWU_FACES[Math.random() * UWU_FACES.length | 0]}?`);
355
+ }
356
+
357
+ // ─── LEET SPEAK ───────────────────────────────────────────────────────────────
358
+
359
+ const L33T_LEVELS = [
360
+ // 0 — tasteful
361
+ { a:"4", e:"3", i:"1", o:"0", t:"7", s:"5" },
362
+ // 1 — committed
363
+ { a:"4", b:"8", e:"3", g:"9", i:"1", l:"1", o:"0", s:"5", t:"7", z:"2" },
364
+ // 2 — unhinged
365
+ {
366
+ a:"@", b:"8", c:"(", d:"|)", e:"3", f:"|=", g:"9", h:"|-|",
367
+ i:"!", j:"_|", k:"|<", l:"|_", m:"|\\/|",n:"|\\|",o:"()", p:"|>",
368
+ q:"(_,)",r:"|2", s:"$", t:"+", u:"|_|", v:"\\/", w:"\\/\\/",
369
+ x:"><", y:"`/", z:"2",
370
+ },
371
+ ];
372
+
373
+ /**
374
+ * Converts text to leet speak.
375
+ * @param {string} text
376
+ * @param {0|1|2} level - 0=tasteful, 1=committed, 2=unhinged
377
+ */
378
+ function l33tspeak(text, level = 0) {
379
+ const map = L33T_LEVELS[Math.min(level, 2)];
380
+ return Array.from(String(text)).map(ch => {
381
+ const r = map[ch.toLowerCase()];
382
+ if (!r) return ch;
383
+ if (r.length === 1 && ch !== ch.toLowerCase()) return r.toUpperCase();
384
+ return r;
385
+ }).join("");
386
+ }
387
+
388
+ // ─── DRUNKIFY ─────────────────────────────────────────────────────────────────
389
+
390
+ const DRUNK_NEIGHBORS = { a:"sq", e:"wr", i:"uo", o:"ip", u:"yo", t:"ry", s:"da", n:"bm", h:"gj", r:"ef" };
391
+
392
+ /**
393
+ * Makes text look like it was typed by someone who should not be texting right now.
394
+ * @param {string} text
395
+ * @param {number} drunkLevel - 0.0 (stone cold sober) to 1.0 (floor is home)
396
+ */
397
+ function drunkify(text, drunkLevel = 0.4) {
398
+ const d = Math.max(0, Math.min(1, drunkLevel));
399
+ const r = () => Math.random();
400
+ const chars = Array.from(String(text));
401
+
402
+ const result = chars.map((ch, i) => {
403
+ let out = ch;
404
+
405
+ // double press
406
+ if (r() < d * 0.25) out = ch + ch;
407
+
408
+ // random case slip
409
+ if (r() < d * 0.2) out = r() < 0.5 ? out.toUpperCase() : out.toLowerCase();
410
+
411
+ // dropped character
412
+ if (r() < d * 0.12 && ch !== " ") out = "";
413
+
414
+ // nearby key misfire
415
+ if (r() < d * 0.1) {
416
+ const keys = DRUNK_NEIGHBORS[ch.toLowerCase()];
417
+ if (keys) out += keys[r() * keys.length | 0];
418
+ }
419
+
420
+ // transposition with next char
421
+ if (r() < d * 0.08 && i < chars.length - 1) out = chars[i + 1] + ch;
422
+
423
+ return out;
424
+ });
425
+
426
+ const suffix = d > 0.8 ? " lmaooo" : d > 0.55 ? " lmao" : d > 0.3 ? " haha" : "";
427
+ return result.join("") + suffix;
428
+ }
429
+
430
+ // ─── PIG LATIN ────────────────────────────────────────────────────────────────
431
+
432
+ /**
433
+ * Converts English text to Pig Latin. Ay!
434
+ * @param {string} text
435
+ */
436
+ function piglatin(text) {
437
+ const VOWEL = /^[aeiou]/i;
438
+ return String(text).replace(/\b([a-zA-Z]+)\b/g, (word) => {
439
+ if (VOWEL.test(word)) return word + "yay";
440
+ const m = word.match(/^([^aeiouAEIOU]*)(.+)/);
441
+ if (!m) return word;
442
+ const [, cons, rest] = m;
443
+ const isCapital = word[0] === word[0].toUpperCase() && /[A-Z]/.test(word[0]);
444
+ const stem = isCapital ? rest[0].toUpperCase() + rest.slice(1).toLowerCase() : rest;
445
+ return stem + cons.toLowerCase() + "ay";
446
+ });
447
+ }
448
+
449
+ // ─── MORSE CODE ───────────────────────────────────────────────────────────────
450
+
451
+ const MORSE_ENC = {
452
+ a:".-", b:"-...", c:"-.-.", d:"-..", e:".", f:"..-.", g:"--.", h:"....",
453
+ i:"..", j:".---", k:"-.-", l:".-..", m:"--", n:"-.", o:"---", p:".--.",
454
+ q:"--.-", r:".-.", s:"...", t:"-", u:"..-", v:"...-", w:".--", x:"-..-",
455
+ y:"-.--", z:"--..",
456
+ "0":"-----","1":".----","2":"..---","3":"...--","4":"....-","5":".....",
457
+ "6":"-....","7":"--...","8":"---..","9":"----.",
458
+ ".":".-.-.-", ",":"--..--", "?":"..--..", "'":".--.--", "!":"-.-.--",
459
+ "/":"-..-.", ":":"---...", ";":"-.-.-.","=":"-...-", "+":".-.-.",
460
+ "-":"-....-", "_":"..--.-", '"':'.-..-.','@':'.--.-.', ' ':"/",
461
+ };
462
+ const MORSE_DEC = Object.fromEntries(Object.entries(MORSE_ENC).map(([k, v]) => [v, k]));
463
+
464
+ /**
465
+ * Encodes text to Morse code.
466
+ * @param {string} text
467
+ * @param {{ dot?: string, dash?: string, letterSep?: string, wordSep?: string }} opts
468
+ */
469
+ function morseEncode(text, opts = {}) {
470
+ const dot = opts.dot ?? ".";
471
+ const dash = opts.dash ?? "-";
472
+ const lsep = opts.letterSep ?? " ";
473
+ const wsep = opts.wordSep ?? " / ";
474
+
475
+ return Array.from(String(text).toLowerCase())
476
+ .map(ch => {
477
+ const code = MORSE_ENC[ch];
478
+ if (!code) return null;
479
+ if (ch === " ") return "\x00"; // word boundary sentinel
480
+ return code.replace(/\./g, dot).replace(/-/g, dash);
481
+ })
482
+ .filter(t => t !== null)
483
+ .reduce((acc, token) => {
484
+ if (token === "\x00") return acc + wsep;
485
+ return acc + (acc && !acc.endsWith(wsep) ? lsep : "") + token;
486
+ }, "");
487
+ }
488
+
489
+ /**
490
+ * Decodes Morse code back to text.
491
+ * @param {string} morse
492
+ */
493
+ function morseDecode(morse) {
494
+ return String(morse)
495
+ .split(" / ")
496
+ .map(word => word.split(" ").map(code => MORSE_DEC[code] ?? "?").join(""))
497
+ .join(" ");
498
+ }
499
+
500
+ // ─── NATO PHONETIC ────────────────────────────────────────────────────────────
501
+
502
+ const NATO = {
503
+ a:"Alpha", b:"Bravo", c:"Charlie", d:"Delta", e:"Echo", f:"Foxtrot",
504
+ g:"Golf", h:"Hotel", i:"India", j:"Juliett",k:"Kilo", l:"Lima",
505
+ m:"Mike", n:"November",o:"Oscar", p:"Papa", q:"Quebec", r:"Romeo",
506
+ s:"Sierra",t:"Tango", u:"Uniform", v:"Victor",w:"Whiskey",x:"X-ray",
507
+ y:"Yankee",z:"Zulu",
508
+ "0":"Zero","1":"One","2":"Two","3":"Three","4":"Four",
509
+ "5":"Five","6":"Six","7":"Seven","8":"Eight","9":"Nine",
510
+ };
511
+
512
+ /**
513
+ * Converts text to NATO phonetic alphabet.
514
+ * @param {string} text
515
+ */
516
+ function natoPhonetic(text) {
517
+ return Array.from(String(text)).map(ch => {
518
+ if (ch === " ") return "[SPACE]";
519
+ return NATO[ch.toLowerCase()] ?? ch;
520
+ }).join("-");
521
+ }
522
+
523
+ // ─── COWSAY ───────────────────────────────────────────────────────────────────
524
+
525
+ const COW_BODIES = {
526
+ default: (e) => [
527
+ ` \\ ^__^`,
528
+ ` \\ (${e})\\_______`,
529
+ ` (__)\\ )\\/\\`,
530
+ ` ||----w |`,
531
+ ` || ||`,
532
+ ],
533
+ tux: () => [
534
+ ` \\`,
535
+ ` \\`,
536
+ ` .--.`,
537
+ ` |o_o |`,
538
+ ` |:_/ |`,
539
+ ` // \\ \\`,
540
+ ` (| | )`,
541
+ ` /'\\_ _/\`\\`,
542
+ ` \\___)=(___/`,
543
+ ],
544
+ bunny: (e) => [
545
+ ` \\`,
546
+ ` \\ (\\ /)`,
547
+ ` \\ ( ${e[0]} )`,
548
+ ` (")(")`,
549
+ ],
550
+ dragon: () => [
551
+ ` \\ / \\ //\\`,
552
+ ` \\ |\\___/| / \\// \\\\`,
553
+ ` /0 0 \\__ / // | \\ \\ `,
554
+ ` / / \\/_/ // | \\ \\ `,
555
+ ` @_^_@'/ \\/_ // | \\ \\ `,
556
+ ` //_^_/ \\/_ // | \\ \\`,
557
+ ` ( //) | \\/// | \\ \\`,
558
+ ` ( / /) _|_ / ) // | \\ _\\`,
559
+ ` ( // /) \`/|/// ( /// | \\ //\\`,
560
+ ` ( // /) / |// \\ /// | \\ // \\`,
561
+ ` (///-.// / /// | /// //`,
562
+ ` (//- . / / | // //`,
563
+ ],
564
+ };
565
+
566
+ const COW_EYES = {
567
+ default: "oo", dead: "xx", wink: ";o", wide: "OO",
568
+ money: "$$", paranoid: "@@", greedy: "$$", shocked: "!!",
569
+ };
570
+
571
+ /**
572
+ * Classic cowsay — prints an ASCII cow delivering your message.
573
+ * @param {string} message
574
+ * @param {{ wrap?: number, cow?: keyof COW_BODIES, eyes?: keyof COW_EYES }} opts
575
+ */
576
+ function cowsay(message, opts = {}) {
577
+ const wrapWidth = opts.wrap ?? 40;
578
+ const cowType = opts.cow ?? "default";
579
+ const eyes = COW_EYES[opts.eyes ?? "default"] ?? "oo";
580
+
581
+ // word-wrap
582
+ const words = String(message).split(" ");
583
+ const lines = [];
584
+ let line = "";
585
+ for (const word of words) {
586
+ if (line && (line + " " + word).length > wrapWidth) {
587
+ lines.push(line);
588
+ line = word;
589
+ } else {
590
+ line = line ? line + " " + word : word;
591
+ }
592
+ }
593
+ if (line) lines.push(line);
594
+
595
+ const maxLen = Math.max(...lines.map(l => l.length));
596
+ const border = "-".repeat(maxLen + 2);
597
+ const bubble = [` ${border}`];
598
+
599
+ if (lines.length === 1) {
600
+ bubble.push(`< ${lines[0].padEnd(maxLen)} >`);
601
+ } else {
602
+ lines.forEach((l, i) => {
603
+ const padded = l.padEnd(maxLen);
604
+ if (i === 0) bubble.push(`/ ${padded} \\`);
605
+ else if (i === lines.length - 1) bubble.push(`\\ ${padded} /`);
606
+ else bubble.push(`| ${padded} |`);
607
+ });
608
+ }
609
+ bubble.push(` ${border}`);
610
+
611
+ const bodyFn = COW_BODIES[cowType] ?? COW_BODIES.default;
612
+ return [...bubble, ...bodyFn(eyes)].join("\n");
613
+ }
614
+
615
+ // ─── RPN CALCULATOR ───────────────────────────────────────────────────────────
616
+
617
+ /**
618
+ * Reverse Polish Notation calculator with extensible operator support.
619
+ * @returns {{ evaluate, defineOp, peek, reset, stack }}
620
+ */
621
+ function makeRPN() {
622
+ let stack = [];
623
+ const customOps = new Map();
624
+
625
+ const UNARY = {
626
+ sqrt: Math.sqrt, abs: Math.abs, floor: Math.floor, ceil: Math.ceil,
627
+ round: Math.round, log: Math.log, log2: Math.log2, log10: Math.log10,
628
+ sin: Math.sin, cos: Math.cos, tan: Math.tan, asin: Math.asin,
629
+ acos: Math.acos, atan: Math.atan, neg: x => -x, inv: x => 1 / x,
630
+ sign: Math.sign, trunc: Math.trunc, exp: Math.exp, fact: x => {
631
+ if (x < 0 || !Number.isInteger(x)) return NaN;
632
+ let r = 1; for (let i = 2; i <= x; i++) r *= i; return r;
633
+ },
634
+ };
635
+
636
+ const BINARY = {
637
+ "+": (a, b) => a + b,
638
+ "-": (a, b) => a - b,
639
+ "*": (a, b) => a * b,
640
+ "/": (a, b) => b === 0 ? NaN : a / b,
641
+ "%": (a, b) => a % b,
642
+ "**": (a, b) => a ** b,
643
+ "^": (a, b) => a ^ b, // bitwise XOR
644
+ "&": (a, b) => a & b, // bitwise AND
645
+ "|": (a, b) => a | b, // bitwise OR
646
+ ">>": (a, b) => a >> b,
647
+ "<<": (a, b) => a << b,
648
+ "max": Math.max,
649
+ "min": Math.min,
650
+ "atan2": Math.atan2,
651
+ "hypot": Math.hypot,
652
+ "gcd": (a, b) => { a = Math.abs(a); b = Math.abs(b); while (b) { [a, b] = [b, a % b]; } return a; },
653
+ "lcm": (a, b) => Math.abs(a * b) / BINARY.gcd(a, b),
654
+ };
655
+
656
+ function evaluate(expression) {
657
+ for (const token of String(expression).trim().split(/\s+/)) {
658
+ if (!token) continue;
659
+
660
+ // number literal
661
+ const num = Number(token);
662
+ if (!isNaN(num)) { stack.push(num); continue; }
663
+
664
+ // constants
665
+ if (token === "pi") { stack.push(Math.PI); continue; }
666
+ if (token === "e") { stack.push(Math.E); continue; }
667
+ if (token === "phi") { stack.push((1 + Math.sqrt(5)) / 2); continue; }
668
+ if (token === "inf") { stack.push(Infinity); continue; }
669
+ if (token === "nan") { stack.push(NaN); continue; }
670
+
671
+ // stack manipulation
672
+ if (token === "dup") {
673
+ if (!stack.length) throw new Error("RPN stack underflow: dup");
674
+ stack.push(stack[stack.length - 1]); continue;
675
+ }
676
+ if (token === "drop") {
677
+ if (!stack.length) throw new Error("RPN stack underflow: drop");
678
+ stack.pop(); continue;
679
+ }
680
+ if (token === "swap") {
681
+ if (stack.length < 2) throw new Error("RPN stack underflow: swap");
682
+ const b = stack.pop(), a = stack.pop();
683
+ stack.push(b); stack.push(a); continue;
684
+ }
685
+ if (token === "over") {
686
+ if (stack.length < 2) throw new Error("RPN stack underflow: over");
687
+ stack.push(stack[stack.length - 2]); continue;
688
+ }
689
+ if (token === "rot") {
690
+ if (stack.length < 3) throw new Error("RPN stack underflow: rot");
691
+ const c = stack.pop(), b = stack.pop(), a = stack.pop();
692
+ stack.push(b); stack.push(c); stack.push(a); continue;
693
+ }
694
+ if (token === "clear") { stack = []; continue; }
695
+
696
+ // user ops
697
+ if (customOps.has(token)) {
698
+ const { arity, fn } = customOps.get(token);
699
+ if (stack.length < arity) throw new Error(`RPN stack underflow: ${token}`);
700
+ const args = stack.splice(-arity).reverse();
701
+ stack.push(fn(...args)); continue;
702
+ }
703
+
704
+ // unary ops
705
+ if (UNARY[token]) {
706
+ if (!stack.length) throw new Error(`RPN stack underflow: ${token}`);
707
+ stack.push(UNARY[token](stack.pop())); continue;
708
+ }
709
+
710
+ // binary ops
711
+ if (BINARY[token]) {
712
+ if (stack.length < 2) throw new Error(`RPN stack underflow: ${token}`);
713
+ const b = stack.pop(), a = stack.pop();
714
+ stack.push(BINARY[token](a, b)); continue;
715
+ }
716
+
717
+ throw new Error(`RPN: unknown token "${token}"`);
718
+ }
719
+
720
+ return stack[stack.length - 1] ?? null;
721
+ }
722
+
723
+ function defineOp(name, arity, fn) {
724
+ if (typeof name !== "string") throw new TypeError("defineOp: name must be string");
725
+ if (typeof arity !== "number") throw new TypeError("defineOp: arity must be number");
726
+ if (typeof fn !== "function") throw new TypeError("defineOp: fn must be function");
727
+ customOps.set(name, { arity, fn });
728
+ return api;
729
+ }
730
+
731
+ const api = {
732
+ evaluate,
733
+ defineOp,
734
+ peek() { return stack[stack.length - 1] ?? null; },
735
+ reset() { stack = []; return api; },
736
+ get stack() { return [...stack]; },
737
+ };
738
+ return api;
739
+ }
740
+
741
+ // ─── BRAINFUCK CODE GENERATOR ─────────────────────────────────────────────────
742
+
743
+ /**
744
+ * Generates Brainfuck code that prints a given string.
745
+ * Uses a multiply-then-adjust strategy: sets cell[1] to `a`, loops `b` times
746
+ * adding `b` to cell[0], then fine-tunes. Reasonably compact output.
747
+ * @param {string} str
748
+ * @returns {string}
749
+ */
750
+ function stringToBF(str) {
751
+ // find best (a, b, remainder) such that a*b + rem = target, minimising total ops
752
+ function bestMult(target) {
753
+ let best = { ops: Infinity, a: 0, b: 0, rem: 0 };
754
+ for (let a = 2; a <= 16; a++) {
755
+ const b = Math.max(0, Math.round(target / a));
756
+ const rem = target - a * b;
757
+ const ops = a + b + Math.abs(rem) + 6; // setup + loop + adjust + overhead
758
+ if (ops < best.ops) best = { ops, a, b, rem };
759
+ }
760
+ return best;
761
+ }
762
+
763
+ const chars = Array.from(String(str)).map(c => c.charCodeAt(0));
764
+ const parts = [];
765
+ let prev = 0;
766
+
767
+ for (const target of chars) {
768
+ const diff = target - prev;
769
+
770
+ if (target === 0) {
771
+ parts.push(prev > 0 ? "[-]." : ".");
772
+ prev = 0;
773
+ continue;
774
+ }
775
+
776
+ // close enough — just nudge
777
+ if (Math.abs(diff) <= 10) {
778
+ parts.push(diff > 0 ? "+".repeat(diff) : "-".repeat(-diff));
779
+ parts.push(".");
780
+ prev = target;
781
+ continue;
782
+ }
783
+
784
+ // multiply strategy: uses cell[0]=output, cell[1]=scratch
785
+ // pattern: [zero cell0] >aaa[<bbb...>-]< [adjust] .
786
+ const { a, b, rem } = bestMult(target);
787
+ const zero = prev > 0 ? "[-]" : "";
788
+ const setA = "+".repeat(a);
789
+ const addB = b >= 0 ? "+".repeat(b) : "-".repeat(-b);
790
+ const adjust = rem >= 0 ? "+".repeat(rem) : "-".repeat(-rem);
791
+
792
+ parts.push(`${zero}>${setA}[<${addB}>-]<${adjust}.`);
793
+ prev = target;
794
+ }
795
+
796
+ return parts.join("");
797
+ }
798
+
799
+ // ─── FAKE LOADER ──────────────────────────────────────────────────────────────
800
+
801
+ /**
802
+ * Async generator that yields fake-but-convincing progress updates.
803
+ * Pair with process.stdout.write or a terminal UI for maximum deception.
804
+ * @param {string[]} steps - step labels
805
+ * @param {number} minMs
806
+ * @param {number} maxMs
807
+ * @yields {{ step, index, total, percent, bar, done }}
808
+ */
809
+ async function* fakeLoader(steps, minMs = 80, maxMs = 500) {
810
+ const BAR = 24;
811
+ const total = steps.length;
812
+
813
+ for (let i = 0; i < total; i++) {
814
+ await new Promise(r => setTimeout(r, minMs + Math.random() * (maxMs - minMs)));
815
+
816
+ const percent = Math.round(((i + 1) / total) * 100);
817
+ const filled = Math.round(((i + 1) / total) * BAR);
818
+
819
+ yield {
820
+ step: steps[i],
821
+ index: i,
822
+ total,
823
+ percent,
824
+ bar: "█".repeat(filled) + "░".repeat(BAR - filled),
825
+ done: i === total - 1,
826
+ };
827
+ }
828
+ }
829
+
830
+ // ─── ELIZA ────────────────────────────────────────────────────────────────────
831
+
832
+ /**
833
+ * Classic ELIZA psychotherapist chatbot (Weizenbaum, 1966).
834
+ * @returns {{ chat }}
835
+ */
836
+ function makeEliza() {
837
+ const REFLECT = {
838
+ "i am": "you are", "i was": "you were", "i": "you", "my": "your",
839
+ "me": "you", "you are": "I am", "you were": "I was", "your": "my",
840
+ "you": "I", "i've": "you've", "i'd": "you'd", "i'll": "you'll",
841
+ "i'm": "you're",
842
+ };
843
+
844
+ const RULES = [
845
+ [/i need (.+)/i, [
846
+ "Why do you need {1}?",
847
+ "Would it really help you to get {1}?",
848
+ "Are you certain you need {1}?",
849
+ ]],
850
+ [/why don'?t you (.+)/i, [
851
+ "Do you think I don't {1}?",
852
+ "Perhaps I will {1} in time.",
853
+ "Should I {1}?",
854
+ ]],
855
+ [/why can'?t i (.+)/i, [
856
+ "Do you think you should be able to {1}?",
857
+ "If you could {1}, what would that change?",
858
+ "Have you really tried?",
859
+ ]],
860
+ [/i can'?t (.+)/i, [
861
+ "How do you know you can't {1}?",
862
+ "Perhaps you could {1} if you approached it differently.",
863
+ "What would it take for you to {1}?",
864
+ ]],
865
+ [/i am (.+)/i, [
866
+ "How long have you been {1}?",
867
+ "Do you believe it is normal to be {1}?",
868
+ "How do you feel about being {1}?",
869
+ ]],
870
+ [/i'?m (.+)/i, [
871
+ "How does being {1} make you feel?",
872
+ "Do you enjoy being {1}?",
873
+ "Why do you tell me you're {1}?",
874
+ ]],
875
+ [/i feel (.+)/i, [
876
+ "Tell me more about feeling {1}.",
877
+ "Do you often feel {1}?",
878
+ "When do you usually feel {1}?",
879
+ ]],
880
+ [/my (.+) is (.+)/i, [
881
+ "Tell me more about your {1}.",
882
+ "Why is your {1} important to you?",
883
+ "What does it mean that your {1} is {2}?",
884
+ ]],
885
+ [/my (.+)/i, [
886
+ "Your {1}?",
887
+ "Tell me more about your {1}.",
888
+ "Why do you bring up your {1}?",
889
+ ]],
890
+ [/you are (.+)/i, [
891
+ "What makes you think I am {1}?",
892
+ "Does it please you to think of me as {1}?",
893
+ ]],
894
+ [/you'?re (.+)/i, [
895
+ "Why do you say I'm {1}?",
896
+ "What makes you think I'm {1}?",
897
+ ]],
898
+ [/sorry/i, [
899
+ "There are many times when no apology is needed.",
900
+ "What feelings do you have when you apologize?",
901
+ ]],
902
+ [/hello|hi|hey/i, [
903
+ "Hello. How are you feeling today?",
904
+ "Hi there. What's on your mind?",
905
+ "Greetings. Please tell me about yourself.",
906
+ ]],
907
+ [/i think (.+)/i, [
908
+ "Do you really think so?",
909
+ "But you're not entirely sure {1}?",
910
+ "Do others share that view?",
911
+ ]],
912
+ [/i want (.+)/i, [
913
+ "What would it mean if you got {1}?",
914
+ "Why do you want {1}?",
915
+ "Suppose you got {1} — then what?",
916
+ ]],
917
+ [/dream/i, [
918
+ "What does that dream suggest to you?",
919
+ "Have you ever had that dream before?",
920
+ "What persons appear in your dreams?",
921
+ ]],
922
+ [/yes/i, [
923
+ "You seem quite certain.",
924
+ "I see. Can you elaborate?",
925
+ "OK. But can you expand on that?",
926
+ ]],
927
+ [/no/i, [
928
+ "Why not?",
929
+ "You seem quite certain.",
930
+ "Are you saying no just to be safe?",
931
+ ]],
932
+ [/always/i, [
933
+ "Can you think of a specific example?",
934
+ "Really — always?",
935
+ "When was the last time?",
936
+ ]],
937
+ [/never/i, [
938
+ "Never? Are you sure?",
939
+ "Not even once?",
940
+ "Why do you think that is?",
941
+ ]],
942
+ [/quit|bye|goodbye|exit/i, [
943
+ "Goodbye. It was nice talking to you.",
944
+ "Farewell. I hope I've been of some help.",
945
+ "Goodbye! Come back any time.",
946
+ ]],
947
+ [/(.+)/i, [
948
+ "Please tell me more.",
949
+ "Can you elaborate on that?",
950
+ "Why do you say that?",
951
+ "I see.",
952
+ "Very interesting.",
953
+ "How does that make you feel?",
954
+ "Let's change focus a bit. Tell me about your family.",
955
+ "How do you feel when you say that?",
956
+ ]],
957
+ ];
958
+
959
+ function reflect(text) {
960
+ // longest-match substitution to avoid partial replacements
961
+ const words = text.split(/\b/);
962
+ return words.map((w, i) => {
963
+ const lower = w.toLowerCase();
964
+ const twoWord = (words[i - 1] ?? "").toLowerCase() + lower;
965
+ if (REFLECT[twoWord]) return REFLECT[twoWord];
966
+ return REFLECT[lower] ?? w;
967
+ }).join("");
968
+ }
969
+
970
+ function chat(input) {
971
+ const text = String(input).trim();
972
+ for (const [pattern, responses] of RULES) {
973
+ const m = text.match(pattern);
974
+ if (m) {
975
+ const template = responses[Math.random() * responses.length | 0];
976
+ return template.replace(/\{(\d+)\}/g, (_, idx) =>
977
+ reflect((m[+idx] ?? "").trim())
978
+ );
979
+ }
980
+ }
981
+ return "Please tell me more.";
982
+ }
983
+
984
+ return { chat };
985
+ }
986
+
987
+ // ─── ROT-N / VIGENÈRE ─────────────────────────────────────────────────────────
988
+
989
+ /**
990
+ * ROT-N cipher (default ROT13).
991
+ * @param {string} text
992
+ * @param {number} n
993
+ */
994
+ function rotN(text, n = 13) {
995
+ n = ((n % 26) + 26) % 26;
996
+ return String(text).replace(/[a-zA-Z]/g, ch => {
997
+ const base = ch >= "a" ? 97 : 65;
998
+ return String.fromCharCode((ch.charCodeAt(0) - base + n) % 26 + base);
999
+ });
1000
+ }
1001
+
1002
+ /**
1003
+ * Vigenère cipher encode.
1004
+ * @param {string} text
1005
+ * @param {string} key
1006
+ */
1007
+ function vigenereEncode(text, key) {
1008
+ const k = String(key).toLowerCase().replace(/[^a-z]/g, "");
1009
+ if (!k.length) throw new Error("vigenereEncode: key must contain letters");
1010
+ let ki = 0;
1011
+ return String(text).replace(/[a-zA-Z]/g, ch => {
1012
+ const base = ch >= "a" ? 97 : 65;
1013
+ const shift = k.charCodeAt(ki++ % k.length) - 97;
1014
+ return String.fromCharCode((ch.charCodeAt(0) - base + shift) % 26 + base);
1015
+ });
1016
+ }
1017
+
1018
+ /**
1019
+ * Vigenère cipher decode.
1020
+ * @param {string} text
1021
+ * @param {string} key
1022
+ */
1023
+ function vigenereDecode(text, key) {
1024
+ const k = String(key).toLowerCase().replace(/[^a-z]/g, "");
1025
+ if (!k.length) throw new Error("vigenereDecode: key must contain letters");
1026
+ let ki = 0;
1027
+ return String(text).replace(/[a-zA-Z]/g, ch => {
1028
+ const base = ch >= "a" ? 97 : 65;
1029
+ const shift = k.charCodeAt(ki++ % k.length) - 97;
1030
+ return String.fromCharCode((ch.charCodeAt(0) - base - shift + 26) % 26 + base);
1031
+ });
1032
+ }
1033
+
1034
+ // ─── RUN-LENGTH ENCODING ──────────────────────────────────────────────────────
1035
+
1036
+ /**
1037
+ * Run-length encode a string. "aaabbc" → "3a2bc"
1038
+ * @param {string} str
1039
+ */
1040
+ function runLengthEncode(str) {
1041
+ if (!str.length) return "";
1042
+ const out = [];
1043
+ let count = 1;
1044
+ for (let i = 1; i <= str.length; i++) {
1045
+ if (i < str.length && str[i] === str[i - 1]) {
1046
+ count++;
1047
+ } else {
1048
+ out.push(count > 1 ? `${count}${str[i - 1]}` : str[i - 1]);
1049
+ count = 1;
1050
+ }
1051
+ }
1052
+ return out.join("");
1053
+ }
1054
+
1055
+ /**
1056
+ * Decode a run-length encoded string. "3a2bc" → "aaabbc"
1057
+ * @param {string} str
1058
+ */
1059
+ function runLengthDecode(str) {
1060
+ return String(str).replace(/(\d+)([^0-9])/g, (_, n, ch) => ch.repeat(+n));
1061
+ }
1062
+
1063
+ // ─── BASE CONVERSION ──────────────────────────────────────────────────────────
1064
+
1065
+ const BASE_CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+/";
1066
+
1067
+ /**
1068
+ * Converts an integer to a string in the given base (2–64).
1069
+ * @param {number} n
1070
+ * @param {number} base
1071
+ */
1072
+ function toBase(n, base = 16) {
1073
+ if (base < 2 || base > 64) throw new RangeError("toBase: base must be 2–64");
1074
+ if (!Number.isInteger(n)) throw new TypeError("toBase: n must be an integer");
1075
+ if (n === 0) return "0";
1076
+ const sign = n < 0 ? "-" : "";
1077
+ let mag = Math.abs(n);
1078
+ const digits = [];
1079
+ while (mag > 0) { digits.unshift(BASE_CHARS[mag % base]); mag = Math.floor(mag / base); }
1080
+ return sign + digits.join("");
1081
+ }
1082
+
1083
+ /**
1084
+ * Parses a base-N string back to a number.
1085
+ * @param {string} str
1086
+ * @param {number} base
1087
+ */
1088
+ function fromBase(str, base = 16) {
1089
+ if (base < 2 || base > 64) throw new RangeError("fromBase: base must be 2–64");
1090
+ const s = String(str).trim();
1091
+ const sign = s[0] === "-" ? -1 : 1;
1092
+ const body = s.replace(/^-/, "");
1093
+ let result = 0;
1094
+ for (const ch of body) {
1095
+ const val = BASE_CHARS.indexOf(ch);
1096
+ if (val === -1 || val >= base) throw new SyntaxError(`fromBase: invalid char "${ch}" for base ${base}`);
1097
+ result = result * base + val;
1098
+ }
1099
+ return sign * result;
1100
+ }
1101
+
1102
+ // ─── DJB2 HASH ────────────────────────────────────────────────────────────────
1103
+
1104
+ /**
1105
+ * DJB2 hash — Dan Bernstein's classic string hash. Fast, non-cryptographic.
1106
+ * Returns a 32-bit unsigned integer.
1107
+ * @param {string} str
1108
+ * @param {number} seed
1109
+ */
1110
+ function hashDJB2(str, seed = 5381) {
1111
+ let hash = seed >>> 0;
1112
+ for (let i = 0; i < str.length; i++) {
1113
+ hash = (((hash << 5) + hash) + str.charCodeAt(i)) >>> 0;
1114
+ }
1115
+ return hash;
1116
+ }
1117
+
1118
+ // ─── WEIGHTED RANDOM ─────────────────────────────────────────────────────────
1119
+
1120
+ /**
1121
+ * Weighted random picker.
1122
+ * @param {Array<{ value: any, weight: number }>} items
1123
+ * @returns {{ pick, add, clear }}
1124
+ *
1125
+ * @example
1126
+ * const loot = makeWeightedRandom([
1127
+ * { value: 'common sword', weight: 70 },
1128
+ * { value: 'rare shield', weight: 25 },
1129
+ * { value: 'legendary orb',weight: 5 },
1130
+ * ]);
1131
+ * loot.pick(); // → "common sword" most of the time
1132
+ */
1133
+ function makeWeightedRandom(items = []) {
1134
+ let pool = items.map(({ value, weight }) => ({ value, weight: Math.max(0, weight) }));
1135
+
1136
+ function pick(count = 1) {
1137
+ if (!pool.length) throw new Error("WeightedRandom: pool is empty");
1138
+ const total = pool.reduce((s, i) => s + i.weight, 0);
1139
+ if (total <= 0) throw new Error("WeightedRandom: all weights are zero");
1140
+
1141
+ const results = [];
1142
+ for (let c = 0; c < count; c++) {
1143
+ let r = Math.random() * total;
1144
+ for (const item of pool) {
1145
+ r -= item.weight;
1146
+ if (r <= 0) { results.push(item.value); break; }
1147
+ }
1148
+ }
1149
+ return count === 1 ? results[0] : results;
1150
+ }
1151
+
1152
+ function add(value, weight) {
1153
+ pool.push({ value, weight: Math.max(0, weight) });
1154
+ return api;
1155
+ }
1156
+
1157
+ function clear() { pool = []; return api; }
1158
+
1159
+ const api = { pick, add, clear, get size() { return pool.length; } };
1160
+ return api;
1161
+ }
1162
+
1163
+ // ─── MAGIC 8-BALL ─────────────────────────────────────────────────────────────
1164
+
1165
+ const EIGHT_BALL_DEFAULTS = {
1166
+ positive: [
1167
+ "It is certain.",
1168
+ "It is decidedly so.",
1169
+ "Without a doubt.",
1170
+ "Yes, definitely.",
1171
+ "You may rely on it.",
1172
+ "As I see it, yes.",
1173
+ "Most likely.",
1174
+ "Outlook good.",
1175
+ "Yes.",
1176
+ "Signs point to yes.",
1177
+ ],
1178
+ neutral: [
1179
+ "Reply hazy, try again.",
1180
+ "Ask again later.",
1181
+ "Better not tell you now.",
1182
+ "Cannot predict now.",
1183
+ "Concentrate and ask again.",
1184
+ ],
1185
+ negative: [
1186
+ "Don't count on it.",
1187
+ "My reply is no.",
1188
+ "My sources say no.",
1189
+ "Outlook not so good.",
1190
+ "Very doubtful.",
1191
+ ],
1192
+ };
1193
+
1194
+ /**
1195
+ * Magic 8-ball with fully customisable response pools.
1196
+ *
1197
+ * @param {object} [opts]
1198
+ * @param {string[]} [opts.positive] - custom positive responses (replaces defaults)
1199
+ * @param {string[]} [opts.neutral] - custom neutral responses (replaces defaults)
1200
+ * @param {string[]} [opts.negative] - custom negative responses (replaces defaults)
1201
+ * @param {number} [opts.posWeight] - relative weight of positive pool (default 10)
1202
+ * @param {number} [opts.neuWeight] - relative weight of neutral pool (default 5)
1203
+ * @param {number} [opts.negWeight] - relative weight of negative pool (default 5)
1204
+ *
1205
+ * @returns {{
1206
+ * ask,
1207
+ * add,
1208
+ * remove,
1209
+ * list,
1210
+ * setWeights,
1211
+ * history,
1212
+ * clearHistory
1213
+ * }}
1214
+ *
1215
+ * @example
1216
+ * const ball = make8Ball({
1217
+ * positive: ['Ship it.', 'Merge without review.'],
1218
+ * negative: ['Revert everything.', 'Have you tried turning it off?'],
1219
+ * neutral: ['Check the logs.'],
1220
+ * });
1221
+ * ball.ask('Will this deploy succeed?');
1222
+ * // → { question: '...', response: 'Ship it.', tone: 'positive' }
1223
+ */
1224
+ function make8Ball(opts = {}) {
1225
+ const pools = {
1226
+ positive: [...(opts.positive ?? EIGHT_BALL_DEFAULTS.positive)],
1227
+ neutral: [...(opts.neutral ?? EIGHT_BALL_DEFAULTS.neutral)],
1228
+ negative: [...(opts.negative ?? EIGHT_BALL_DEFAULTS.negative)],
1229
+ };
1230
+
1231
+ let weights = {
1232
+ positive: opts.posWeight ?? 10,
1233
+ neutral: opts.neuWeight ?? 5,
1234
+ negative: opts.negWeight ?? 5,
1235
+ };
1236
+
1237
+ const log = [];
1238
+
1239
+ /**
1240
+ * Ask the ball a question.
1241
+ * @param {string} [question]
1242
+ * @returns {{ question: string, response: string, tone: 'positive'|'neutral'|'negative' }}
1243
+ */
1244
+ function ask(question = "") {
1245
+ const tones = /** @type {const} */ (["positive", "neutral", "negative"]);
1246
+ const total = tones.reduce((s, t) => s + (pools[t].length ? weights[t] : 0), 0);
1247
+
1248
+ if (total === 0) throw new Error("8-ball: all response pools are empty");
1249
+
1250
+ let r = Math.random() * total;
1251
+ let tone = tones[tones.length - 1];
1252
+ for (const t of tones) {
1253
+ if (!pools[t].length) continue;
1254
+ r -= weights[t];
1255
+ if (r <= 0) { tone = t; break; }
1256
+ }
1257
+
1258
+ const pool = pools[tone];
1259
+ const response = pool[Math.random() * pool.length | 0];
1260
+ const entry = { question: String(question), response, tone };
1261
+ log.push(entry);
1262
+ return entry;
1263
+ }
1264
+
1265
+ /**
1266
+ * Add one or more custom responses to a pool.
1267
+ * @param {'positive'|'neutral'|'negative'} tone
1268
+ * @param {...string} responses
1269
+ */
1270
+ function add(tone, ...responses) {
1271
+ if (!pools[tone]) throw new TypeError(`8-ball: unknown tone "${tone}" — use positive, neutral, or negative`);
1272
+ for (const r of responses) {
1273
+ if (typeof r !== "string" || !r.trim()) throw new TypeError("8-ball: responses must be non-empty strings");
1274
+ pools[tone].push(r.trim());
1275
+ }
1276
+ return api;
1277
+ }
1278
+
1279
+ /**
1280
+ * Remove a response from a pool by exact text or index.
1281
+ * @param {'positive'|'neutral'|'negative'} tone
1282
+ * @param {string|number} target - exact response text or 0-based index
1283
+ */
1284
+ function remove(tone, target) {
1285
+ if (!pools[tone]) throw new TypeError(`8-ball: unknown tone "${tone}"`);
1286
+ const pool = pools[tone];
1287
+ const idx = typeof target === "number"
1288
+ ? target
1289
+ : pool.findIndex(r => r === target);
1290
+ if (idx < 0 || idx >= pool.length) throw new RangeError(`8-ball: response not found in "${tone}" pool`);
1291
+ pool.splice(idx, 1);
1292
+ return api;
1293
+ }
1294
+
1295
+ /**
1296
+ * List all responses, optionally filtered by tone.
1297
+ * @param {'positive'|'neutral'|'negative'|undefined} tone
1298
+ */
1299
+ function list(tone) {
1300
+ if (tone) {
1301
+ if (!pools[tone]) throw new TypeError(`8-ball: unknown tone "${tone}"`);
1302
+ return [...pools[tone]];
1303
+ }
1304
+ return {
1305
+ positive: [...pools.positive],
1306
+ neutral: [...pools.neutral],
1307
+ negative: [...pools.negative],
1308
+ };
1309
+ }
1310
+
1311
+ /**
1312
+ * Adjust the relative likelihood of each tone being picked.
1313
+ * @param {{ positive?: number, neutral?: number, negative?: number }} w
1314
+ */
1315
+ function setWeights(w) {
1316
+ for (const [tone, val] of Object.entries(w)) {
1317
+ if (!pools[tone]) throw new TypeError(`8-ball: unknown tone "${tone}"`);
1318
+ if (typeof val !== "number" || val < 0) throw new RangeError("8-ball: weights must be non-negative numbers");
1319
+ weights[tone] = val;
1320
+ }
1321
+ return api;
1322
+ }
1323
+
1324
+ function clearHistory() { log.length = 0; return api; }
1325
+
1326
+ const api = {
1327
+ ask,
1328
+ add,
1329
+ remove,
1330
+ list,
1331
+ setWeights,
1332
+ clearHistory,
1333
+ get history() { return [...log]; },
1334
+ get counts() { return { positive: pools.positive.length, neutral: pools.neutral.length, negative: pools.negative.length }; },
1335
+ };
1336
+ return api;
1337
+ }
1338
+
1339
+ // ─── EXPORTS ──────────────────────────────────────────────────────────────────
1340
+
1341
+ let kitdef = {
1342
+ // esoteric language runtimes
1343
+ Brainfuck: makeBfObject,
1344
+ Ook: makeOokObject,
1345
+ Befunge: makeBefungeObject,
1346
+
1347
+ // text transformers
1348
+ zalgoify,
1349
+ uwuify,
1350
+ l33tspeak,
1351
+ drunkify,
1352
+ piglatin,
1353
+ rotN,
1354
+ vigenereEncode,
1355
+ vigenereDecode,
1356
+
1357
+ // encoding / decoding
1358
+ morseEncode,
1359
+ morseDecode,
1360
+ natoPhonetic,
1361
+ runLengthEncode,
1362
+ runLengthDecode,
1363
+ toBase,
1364
+ fromBase,
1365
+
1366
+ // generators / output
1367
+ cowsay,
1368
+ stringToBF,
1369
+ fakeLoader,
1370
+
1371
+ // hashing & randomness
1372
+ hashDJB2,
1373
+
1374
+ // stateful factories
1375
+ Markov: makeMarkov,
1376
+ RPN: makeRPN,
1377
+ Eliza: makeEliza,
1378
+ WeightedRandom: makeWeightedRandom,
1379
+ EightBall: make8Ball,
1380
+ };
1381
+ // ─── CORPORATE BUZZWORD GENERATOR ────────────────────────────────────────────
1382
+
1383
+ const BUZZ_VERBS = ["leverage","synergize","disrupt","pivot","ideate","strategize","streamline","democratize","incentivize","operationalize","monetize","gamify","productize","evangelize","optimize","holistically align","circle back on","take offline","unpack","double-click on"];
1384
+ const BUZZ_ADJ = ["agile","scalable","next-generation","best-in-class","bleeding-edge","robust","mission-critical","end-to-end","data-driven","cloud-native","customer-centric","proactive","value-added","cross-functional","omni-channel","disruptive","innovative","paradigm-shifting","frictionless","hyper-focused"];
1385
+ const BUZZ_NOUNS = ["synergies","stakeholders","deliverables","bandwidth","ecosystems","KPIs","touchpoints","verticals","pain points","low-hanging fruit","core competencies","value propositions","thought leadership","learnings","headwinds","run rate","north star","needle","flywheel","DNA"];
1386
+
1387
+ const _pick = (arr) => arr[Math.random() * arr.length | 0];
1388
+
1389
+ /**
1390
+ * Generates a sentence of meaningless corporate speak.
1391
+ * Perfect for your next all-hands.
1392
+ * @param {number} count - number of sentences (default 1)
1393
+ */
1394
+ function buzzword(count = 1) {
1395
+ const sentence = () =>
1396
+ `We need to ${_pick(BUZZ_VERBS)} our ${_pick(BUZZ_ADJ)} ${_pick(BUZZ_NOUNS)} `+
1397
+ `to ${_pick(BUZZ_VERBS)} ${_pick(BUZZ_ADJ)} ${_pick(BUZZ_NOUNS)}.`;
1398
+ return Array.from({ length: count }, sentence).join(" ");
1399
+ }
1400
+
1401
+ // ─── EXCUSE GENERATOR ────────────────────────────────────────────────────────
1402
+
1403
+ const EXCUSE_WHO = ["the intern","Jenkins","a cosmic ray","npm","the PM","DNS","a rogue semicolon","Schrödinger's merge conflict","the on-call engineer","a passing seagull","vim","the garbage collector","WiFi","a solar flare","the load balancer"];
1404
+ const EXCUSE_WHAT = ["deleted","corrupted","silently dropped","reordered","base64-encoded","null-coalesced","round-tripped","monkey-patched","accidentally memoized","rate-limited","hot-reloaded","race-conditioned","undefined-behavioured","recursively exploded","sharded incorrectly"];
1405
+ const EXCUSE_NOUN = ["the database","production","the config","main","the cache","our entire test suite","the Docker socket","a single byte","the schema","every environment variable","the API contract","the feature flag","the connection pool","a load-bearing comment","the seed data"];
1406
+ const EXCUSE_WHY = ["during a routine deploy","right before the demo","at 4:59 PM on Friday","while the CTO was watching","in UTC but we stored it in EST","because of a type coercion","due to a comment that was actually load-bearing","thanks to an implicit toString()","mid-standup","via a regex that looked fine","because NaN !== NaN","after a perfectly innocent refactor","when we upgraded a minor version","despite it working locally","for compliance reasons"];
1407
+
1408
+ /**
1409
+ * Generates a plausible-sounding engineering excuse.
1410
+ * For postmortems, stand-ups, and general survival.
1411
+ */
1412
+ function excuse() {
1413
+ return `${_pick(EXCUSE_WHO)} ${_pick(EXCUSE_WHAT)} ${_pick(EXCUSE_NOUN)} ${_pick(EXCUSE_WHY)}.`;
1414
+ }
1415
+
1416
+ // ─── COMMIT MESSAGE GENERATOR ────────────────────────────────────────────────
1417
+
1418
+ const COMMIT_VERBS = ["fix","fix","fix","fix","fixup","oops","revert","add","remove","update","refactor","hopefully fix","finally fix","maybe fix","WIP:","TEMP:","DO NOT MERGE:","please work","ugh,"];
1419
+ const COMMIT_NOUNS = ["thing","stuff","that thing","the bug","it","everything","nothing","the build","tests","types","linting","the whatever","that PR comment","the CI","the flaky test","the import order","trailing whitespace","a typo","the typo I introduced while fixing the first typo","merge conflicts"];
1420
+ const COMMIT_SUFFIX = ["","","","","","","(again)","(for real this time)","(I think)","(don't ask)","(sorry)","(pls review fast)","(see slack)","(it's 2am)","(this is fine)","🙏","💀","???","[skip ci]","-- will explain later"];
1421
+
1422
+ /**
1423
+ * Generates a realistic git commit message.
1424
+ */
1425
+ function commitMessage() {
1426
+ return `${_pick(COMMIT_VERBS)} ${_pick(COMMIT_NOUNS)} ${_pick(COMMIT_SUFFIX)}`.trim();
1427
+ }
1428
+
1429
+ // ─── VARIABLE NAME GENERATOR ──────────────────────────────────────────────────
1430
+
1431
+ const VAR_PREFIXES = ["temp","temp2","temp_final","final","final2","actualFinal","realFinal","data","result","res","val","value","thing","stuff","foo","bar","baz","x","x2","myVar","theVar","thisVar","thatVar","newVar","oldVar"];
1432
+ const VAR_SUFFIXES = ["","New","2","Final","Temp","Copy","Backup","Fixed","Updated","V2","Again","Real","Actual","Better","Good","Bad","Test","Prod","Tmp","Bak"];
1433
+
1434
+ /**
1435
+ * Generates the kind of variable name that gets left in production.
1436
+ */
1437
+ function badVarName() {
1438
+ return _pick(VAR_PREFIXES) + _pick(VAR_SUFFIXES);
1439
+ }
1440
+
1441
+ // ─── HOROSCOPE FOR DEVELOPERS ────────────────────────────────────────────────
1442
+
1443
+ const HOROSCOPE_SIGNS = ["Aries","Taurus","Gemini","Cancer","Leo","Virgo","Libra","Scorpio","Sagittarius","Capricorn","Aquarius","Pisces"];
1444
+ const HOROSCOPE_EVENTS = [
1445
+ "A null pointer exception looms on the horizon.",
1446
+ "Mercury is in retrograde. Do not deploy.",
1447
+ "The stars suggest your unit tests are lying to you.",
1448
+ "A senior engineer will leave a one-word comment on your PR: 'Why?'",
1449
+ "You will spend four hours debugging only to find a missing semicolon.",
1450
+ "An unexpected refactor will consume your entire sprint.",
1451
+ "The linter gods are displeased. Offer a sacrifice of trailing whitespace.",
1452
+ "Your branch will conflict with main before you even push.",
1453
+ "Someone will reopen a bug you closed as 'works as designed.'",
1454
+ "A meeting that could have been an email will consume your afternoon.",
1455
+ "npm install will betray you today.",
1456
+ "You will Google the same thing you Googled yesterday.",
1457
+ "The documentation lies. It always has.",
1458
+ "Production will go down exactly when you step away from your desk.",
1459
+ "Your local environment works. That's the last time it will.",
1460
+ ];
1461
+
1462
+ /**
1463
+ * Reads your developer horoscope. Unsettlingly accurate.
1464
+ * @param {string} [sign] - zodiac sign, or random if omitted
1465
+ */
1466
+ function devHoroscope(sign) {
1467
+ const s = sign ?? _pick(HOROSCOPE_SIGNS);
1468
+ const e = Array.from({ length: 2 + (Math.random() * 2 | 0) }, () => _pick(HOROSCOPE_EVENTS));
1469
+ return `✨ ${s}: ${[...new Set(e)].join(" ")}`;
1470
+ }
1471
+
1472
+ // ─── ENTERPRISE FUNCTION NAMER ────────────────────────────────────────────────
1473
+
1474
+ const ENT_VERBS = ["initialize","instantiate","execute","invoke","perform","process","orchestrate","facilitate","coordinate","aggregate","normalize","serialize","deserialize","hydrate","dehydrate","transform","validate","sanitize","propagate","reconcile"];
1475
+ const ENT_NOUNS = ["Entity","Manager","Handler","Factory","Provider","Service","Repository","Controller","Processor","Orchestrator","Facilitator","Coordinator","Aggregator","Transformer","Validator","Sanitizer","Propagator","Reconciler","Adapter","Decorator"];
1476
+ const ENT_SUFFIX = ["ForCurrentUser","WithContext","Async","Safely","Recursively","Lazily","Eagerly","Idempotently","AtomicTransaction","WithRetry","AndCache","OrFallback","IfNeeded","WhenReady","InBackground"];
1477
+
1478
+ /**
1479
+ * Generates a perfectly enterprise-grade function name.
1480
+ * Six words minimum. Means nothing.
1481
+ */
1482
+ function enterpriseFunctionName() {
1483
+ return `${_pick(ENT_VERBS)}${_pick(ENT_NOUNS)}${_pick(ENT_NOUNS)}${_pick(ENT_SUFFIX)}`;
1484
+ }
1485
+
1486
+ // ─── KEYBOARD SMASH DETECTOR ─────────────────────────────────────────────────
1487
+
1488
+ /**
1489
+ * Determines if a string is someone smashing their keyboard.
1490
+ * Uses a sophisticated statistical model (it isn't).
1491
+ * @param {string} str
1492
+ * @returns {{ isSmash: boolean, confidence: number, verdict: string }}
1493
+ */
1494
+ function detectKeyboardSmash(str) {
1495
+ const s = String(str).toLowerCase();
1496
+ const unique = new Set(s.replace(/\s/g,"")).size;
1497
+ const len = s.replace(/\s/g,"").length;
1498
+ const alpha = (s.match(/[a-z]/g) ?? []).length;
1499
+ const avgFreq = len / (unique || 1);
1500
+ const homeRow = (s.match(/[asdfghjkl;]/g) ?? []).length / (alpha || 1);
1501
+ const score = (avgFreq * 0.4 + homeRow * 3 + (len > 8 ? 1 : 0) * 0.5) / 4;
1502
+ const confidence = Math.min(1, Math.max(0, score));
1503
+ const isSmash = confidence > 0.4;
1504
+ const verdicts = isSmash
1505
+ ? ["Yep, that's a smash.","Classic keyboard smash.","Your keyboard felt that.","Forensic analysis: smash.","Has anyone checked on you?"]
1506
+ : ["Looks intentional (barely).","Questionable but coherent.","I'll allow it.","Could be a variable name.","Technically words."];
1507
+ return { isSmash, confidence: +confidence.toFixed(2), verdict: _pick(verdicts) };
1508
+ }
1509
+
1510
+ // ─── SARCASM DETECTOR ────────────────────────────────────────────────────────
1511
+
1512
+ /**
1513
+ * Detects sarcasm in text. Extremely unreliable, as nature intended.
1514
+ * @param {string} text
1515
+ * @returns {{ sarcastic: boolean, confidence: number, reason: string }}
1516
+ */
1517
+ function detectSarcasm(text) {
1518
+ const s = String(text).toLowerCase();
1519
+ let score = 0;
1520
+ const reasons = [];
1521
+
1522
+ if (/oh great|oh sure|wow thanks|fantastic|wonderful|lovely|brilliant/i.test(s)) { score += 0.4; reasons.push("suspicious enthusiasm"); }
1523
+ if (/totally|definitely|absolutely|obviously|clearly/i.test(s)) { score += 0.2; reasons.push("over-affirmation"); }
1524
+ if (/\.\.\./g.test(s)) { score += 0.15 * (s.match(/\.\.\./g)?.length ?? 0); reasons.push("suspicious ellipses"); }
1525
+ if (/not like|it's not as if|because that always works/i.test(s)) { score += 0.5; reasons.push("classic sarcasm template"); }
1526
+ if (/🙄|😒|😤/u.test(text)) { score += 0.6; reasons.push("betrayed by emoji"); }
1527
+ if (/works on my machine|should be fine|it's just one line|this is fine/i.test(s)) { score += 0.7; reasons.push("infamous phrase detected"); }
1528
+ if (/!{2,}/g.test(s)) { score += 0.2; reasons.push("excessive exclamation"); }
1529
+
1530
+ const confidence = Math.min(1, score);
1531
+ return {
1532
+ sarcastic: confidence > 0.35,
1533
+ confidence: +confidence.toFixed(2),
1534
+ reason: reasons.length ? reasons.join(", ") : "vibes",
1535
+ };
1536
+ }
1537
+
1538
+ // ─── MEETING TITLE GENERATOR ─────────────────────────────────────────────────
1539
+
1540
+ const MTG_ADJ = ["Quick","Brief","Short","Urgent","Important","Mandatory","Optional (but required)","Productive","Effective","Sync","Async","Bi-weekly","Ad-hoc","Recurring","Critical","Strategic","Informational","Time-sensitive","Action-oriented","Stakeholder"];
1541
+ const MTG_NOUNS = ["Sync","Check-in","Alignment","Standup","Retrospective","Kickoff","Deep-dive","Brainstorm","Workshop","Discussion","Debrief","Touchbase","Calibration","Review","Readout","Showcase","Planning","Grooming","Estimation","Ceremony"];
1542
+ const MTG_TOPIC = ["re: the thing we discussed","(no agenda yet)","TBD","re: Slack thread","follow-up on the follow-up","to discuss the upcoming discussion","re: yesterday's meeting","where we figure out the next meeting","to align on alignment","(details in calendar invite that has no details)"];
1543
+
1544
+ /**
1545
+ * Generates a realistic but meaningless meeting invite title.
1546
+ */
1547
+ function meetingTitle() {
1548
+ return `${_pick(MTG_ADJ)} ${_pick(MTG_NOUNS)} ${_pick(MTG_TOPIC)}`;
1549
+ }
1550
+
1551
+ // ─── PASSIVE-AGGRESSIVE EMAIL CLOSER ────────────────────────────────────────
1552
+
1553
+ const PA_CLOSERS = [
1554
+ "As per my last email,",
1555
+ "As previously discussed,",
1556
+ "Per our conversation,",
1557
+ "Going forward,",
1558
+ "Just looping in [PERSON] here,",
1559
+ "Not sure if you saw my last message,",
1560
+ "Friendly reminder:",
1561
+ "Circling back on this,",
1562
+ "Bumping this to the top of your inbox,",
1563
+ "Happy to jump on a call if that's easier,",
1564
+ "Let me know if this makes sense,",
1565
+ "Does this work for everyone?",
1566
+ "Thoughts?",
1567
+ "Please advise.",
1568
+ "Let me know your thoughts.",
1569
+ "Hope this helps!",
1570
+ "Does that answer your question?",
1571
+ "Please let me know if you have any questions.",
1572
+ "Thanks in advance.",
1573
+ "Best,",
1574
+ ];
1575
+
1576
+ /**
1577
+ * Returns a passive-aggressive email sign-off or opener.
1578
+ * Use responsibly. Or don't.
1579
+ */
1580
+ function paEmail() {
1581
+ return _pick(PA_CLOSERS);
1582
+ }
1583
+
1584
+ // ─── LOADING MESSAGE GENERATOR ────────────────────────────────────────────────
1585
+
1586
+ const LOADING_MSGS = [
1587
+ "Reticulating splines...",
1588
+ "Dividing by zero...",
1589
+ "Spinning up the cloud...",
1590
+ "Contacting the mothership...",
1591
+ "Summoning the daemon...",
1592
+ "Convincing the database to cooperate...",
1593
+ "Asking the compiler nicely...",
1594
+ "Bribing the load balancer...",
1595
+ "Untangling the callback hell...",
1596
+ "Recalibrating the flux capacitor...",
1597
+ "Feeding the hamsters...",
1598
+ "Downloading more RAM...",
1599
+ "Inverting the binary tree...",
1600
+ "Defragmenting the cloud...",
1601
+ "Compiling in the background (lie)...",
1602
+ "Running the tests (also a lie)...",
1603
+ "Warming up the cache...",
1604
+ "Herding the microservices...",
1605
+ "Questioning my career choices...",
1606
+ "Pretending to be busy...",
1607
+ "Generating buzzwords...",
1608
+ "Ignoring the linter...",
1609
+ "Praying to the merge gods...",
1610
+ "Deploying to prod (it's fine)...",
1611
+ "Making it look harder than it is...",
1612
+ ];
1613
+
1614
+ /**
1615
+ * Returns a silly loading message. Useful for delaying the inevitable.
1616
+ * @param {number} count - how many unique messages to return
1617
+ */
1618
+ function loadingMessage(count = 1) {
1619
+ const shuffled = [...LOADING_MSGS].sort(() => Math.random() - 0.5);
1620
+ const msgs = shuffled.slice(0, Math.min(count, LOADING_MSGS.length));
1621
+ return count === 1 ? msgs[0] : msgs;
1622
+ }
1623
+
1624
+ // ─── PROGRAMMER PERSONALITY TYPE ─────────────────────────────────────────────
1625
+
1626
+ const PROGRAMMER_TYPES = [
1627
+ { type: "The Tabs Guy", sign: (s) => /\t/.test(s), desc: "Uses tabs. Will bring it up unprompted." },
1628
+ { type: "The Spaces Person", sign: (s) => /^ /m.test(s) && !/\t/.test(s), desc: "Uses spaces. Also will bring it up unprompted." },
1629
+ { type: "The One-Liner", sign: (s) => s.split('\n').some(l => l.length > 120), desc: "Fits everything in one line. Blames the screen." },
1630
+ { type: "The Comment Avoider", sign: (s) => !s.includes('//') && !s.includes('/*') && s.length>200, desc: "Code is self-documenting. It isn't." },
1631
+ { type: "The Over-Commenter", sign: (s) => (s.match(/\/\//g)?.length ?? 0) > 8, desc: "Comments every line. Including the closing brace." },
1632
+ { type: "The Magic Number Fan", sign: (s) => /\b(42|69|1337|9999|999|777)\b/.test(s), desc: "Numbers have meaning. Just not to anyone else." },
1633
+ { type: "The Ternary Abuser", sign: (s) => (s.match(/\?/g)?.length ?? 0) > 3, desc: "Why use if/else when you can nest ternaries 4 deep?" },
1634
+ { type: "The var Person", sign: (s) => /\bvar\b/.test(s), desc: "Still using var. ES5 was peak JavaScript." },
1635
+ { type: "The Promise Rejector", sign: (s) => /\.then\(.*\.then\(.*\.then\(/s.test(s), desc: "Heard of async/await. Disagrees philosophically." },
1636
+ { type: "The Default Exporter", sign: () => Math.random() < 0.15, desc: "Everything is default export. Good luck importing it." },
1637
+ ];
1638
+
1639
+ /**
1640
+ * Analyses code and returns the programmer's personality type.
1641
+ * Scientifically dubious.
1642
+ * @param {string} code
1643
+ */
1644
+ function programmerType(code) {
1645
+ const matched = PROGRAMMER_TYPES.filter(t => t.sign(String(code)));
1646
+ if (!matched.length) return { type: "The Balanced Developer", desc: "Statistically impossible. Are you okay?" };
1647
+ return _pick(matched);
1648
+ }
1649
+
1650
+ // ─── SUDO MAKE ME A SANDWICH ─────────────────────────────────────────────────
1651
+
1652
+ const SANDWICH_REFUSALS = [
1653
+ "What do you say?",
1654
+ "Please.",
1655
+ "Magic word?",
1656
+ "I don't think so.",
1657
+ "You're not even root on this machine.",
1658
+ "I'm on break.",
1659
+ "Have you tried asking nicely?",
1660
+ "Error 403: sandwich forbidden.",
1661
+ "Not my department.",
1662
+ "Talk to your manager.",
1663
+ ];
1664
+
1665
+ /**
1666
+ * Classic xkcd #149 implementation.
1667
+ * @param {string} input
1668
+ * @returns {string}
1669
+ */
1670
+ function sudo(input) {
1671
+ const isSudo = /^sudo\s+/i.test(String(input).trim());
1672
+ const isSandwich = /sandwich/i.test(input);
1673
+ if (isSudo && isSandwich) return "Okay.";
1674
+ if (!isSudo && isSandwich) return _pick(SANDWICH_REFUSALS);
1675
+ if (isSudo) return `sudo: ${input.replace(/^sudo\s+/i,"").split(" ")[0]}: permission denied (just kidding, but no)`;
1676
+ return "sudo: command not found in your heart";
1677
+ }
1678
+
1679
+ // ─── COMPLIMENT / INSULT GENERATOR ───────────────────────────────────────────
1680
+
1681
+ const COMPLIMENT_PARTS = {
1682
+ adj1: ["elegant","brilliant","surprisingly readable","dare I say clean","almost optimal","boldly structured","hauntingly efficient","suspiciously correct","begrudgingly impressive"],
1683
+ adj2: ["courageous","time-efficient","dependency-free","side-effect-free","stack-safe","type-safe","merge-conflict-free","lint-passing","test-covering"],
1684
+ noun: ["approach","implementation","commit","variable name","algorithm","regex","one-liner","abstraction","error message","comment"],
1685
+ };
1686
+
1687
+ const INSULT_PARTS = {
1688
+ adj1: ["creative","unique","memorable","ambitious","bold","unconventional","one-of-a-kind","spirited","brave","avant-garde"],
1689
+ adj2: ["difficult to read","hard to test","impossible to maintain","fun to explain in a postmortem","character-building","a growth opportunity","educational for the team","resume-generating","definitely intentional"],
1690
+ noun: ["approach","implementation","variable name","architecture decision","regex","abstraction","naming convention","use of globals","comment strategy","deployment strategy"],
1691
+ };
1692
+
1693
+ /**
1694
+ * Returns a genuine-sounding compliment for someone's code.
1695
+ */
1696
+ function codeCompliment() {
1697
+ const p = COMPLIMENT_PARTS;
1698
+ return `This is a genuinely ${_pick(p.adj1)}, ${_pick(p.adj2)} ${_pick(p.noun)}. Well done.`;
1699
+ }
1700
+
1701
+ /**
1702
+ * Returns a technically-complimentary-but-actually-devastating code critique.
1703
+ */
1704
+ function codeInsult() {
1705
+ const p = INSULT_PARTS;
1706
+ return `This is a very ${_pick(p.adj1)} ${_pick(p.noun)}. Very ${_pick(p.adj2)}.`;
1707
+ }
1708
+
1709
+ // ─── TECHNO-BABBLE GENERATOR ─────────────────────────────────────────────────
1710
+
1711
+ const TECHNO_PREFIXES = ["quantum","neural","blockchain","cloud-native","distributed","asynchronous","recursive","polymorphic","isomorphic","homomorphic","edge-computed","post-quantum","cyber","hyper-converged","event-driven","zero-trust","serverless","AI-powered","ML-augmented","Web3"];
1712
+ const TECHNO_MIDDLES = ["mesh","fabric","lattice","graph","matrix","pipeline","orchestration","federation","consensus","inference","propagation","sharding","topology","namespace","microkernel","hypervisor","sandbox","runtime","middleware","monorepo"];
1713
+ const TECHNO_SUFFIXES = ["as a service","protocol","framework","architecture","paradigm","pattern","abstraction","primitive","contract","oracle","proxy","gateway","resolver","scheduler","allocator","dispatcher","validator","interceptor","serializer","transformer"];
1714
+
1715
+ /**
1716
+ * Generates plausible-sounding tech jargon.
1717
+ * Use in pitch decks and funding proposals.
1718
+ * @param {number} count - number of terms
1719
+ */
1720
+ function technobabble(count = 1) {
1721
+ const term = () => `${_pick(TECHNO_PREFIXES)} ${_pick(TECHNO_MIDDLES)} ${_pick(TECHNO_SUFFIXES)}`;
1722
+ const terms = Array.from({ length: count }, term);
1723
+ return count === 1 ? terms[0] : terms;
1724
+ }
1725
+
1726
+ // ─── SILICON VALLEY NAME GENERATOR ────────────────────────────────────────────
1727
+
1728
+ const SV_PREFIX = ["Air","Cloud","Snap","Shift","Flux","Spin","Glow","Zip","Dash","Flow","Spark","Bloom","Pulse","Surge","Drift","Hive","Loop","Nest","Nova","Peak","Forge","Kite","Loom","Mint","Orb","Prism","Quill","Ramp","Sage","Trek","Verve","Wove","Xylo","Yoke","Zest"];
1729
+ const SV_SUFFIX = ["io","ly","ify","hub","base","kit","lab","pad","bay","box","co","hq","now","up","ful","ai","app","ware","fy","ster"];
1730
+
1731
+ /**
1732
+ * Generates a startup name indistinguishable from the real thing.
1733
+ * Incorporates the time-tested science of removing vowels or appending suffixes.
1734
+ * @param {number} count
1735
+ */
1736
+ function startupName(count = 1) {
1737
+ const name = () => {
1738
+ const style = Math.random() * 3 | 0;
1739
+ if (style === 0) return _pick(SV_PREFIX) + _pick(SV_SUFFIX);
1740
+ if (style === 1) {
1741
+ const base = _pick(SV_PREFIX).toLowerCase();
1742
+ return (base.replace(/[aeiou]/g, "") + _pick(SV_SUFFIX)).replace(/^./, c => c.toUpperCase());
1743
+ }
1744
+ return _pick(SV_PREFIX) + _pick(SV_PREFIX).toLowerCase() + (_pick(SV_SUFFIX));
1745
+ };
1746
+ const names = Array.from({ length: count }, name);
1747
+ return count === 1 ? names[0] : names;
1748
+ }
1749
+
1750
+ // ─── DEPENDENCY AUDIT ─────────────────────────────────────────────────────────
1751
+
1752
+ /**
1753
+ * Audits a package name and delivers a scathing review.
1754
+ * More accurate than npm audit.
1755
+ * @param {string} packageName
1756
+ */
1757
+ function dependencyAudit(packageName) {
1758
+ const pkg = String(packageName).trim();
1759
+ const scores = {
1760
+ bloat: Math.random() * 100 | 0,
1761
+ drama: Math.random() * 100 | 0, // likelihood of abandoned/drama
1762
+ regret: Math.random() * 100 | 0,
1763
+ transitive: 2 + (Math.random() * 897 | 0), // dependencies it pulls in
1764
+ };
1765
+ const verdict = scores.regret > 70
1766
+ ? "You will regret this."
1767
+ : scores.drama > 70
1768
+ ? "The maintainer is currently arguing on GitHub."
1769
+ : scores.bloat > 70
1770
+ ? `This installs ${scores.transitive} packages to do something you could write in 8 lines.`
1771
+ : "Probably fine. Probably.";
1772
+
1773
+ return {
1774
+ package: pkg,
1775
+ bloatScore: `${scores.bloat}/100`,
1776
+ dramaScore: `${scores.drama}/100`,
1777
+ regretScore: `${scores.regret}/100`,
1778
+ transitiveDeps: scores.transitive,
1779
+ verdict,
1780
+ };
1781
+ }
1782
+
1783
+ // ─── STANDUP GENERATOR ────────────────────────────────────────────────────────
1784
+
1785
+ const STANDUP_YESTERDAY = [
1786
+ "Attended meetings about meetings.",
1787
+ "Fixed the thing I broke while fixing the previous thing.",
1788
+ "Investigated a production incident that turned out to be user error.",
1789
+ "Reviewed a PR that has since been abandoned.",
1790
+ "Wrote a script that already exists in the codebase.",
1791
+ "Updated documentation that nobody reads.",
1792
+ "Refactored something nobody asked me to refactor.",
1793
+ "Spent four hours on a two-line fix.",
1794
+ "Unblocked myself by giving up on the original approach.",
1795
+ "Successfully attended all of my recurring calendar events.",
1796
+ ];
1797
+ const STANDUP_TODAY = [
1798
+ "Continue investigating the investigation.",
1799
+ "Write the PR I've been meaning to write.",
1800
+ "Fix the thing I'll break today.",
1801
+ "Ask for clarification on requirements that will change anyway.",
1802
+ "Attend the meeting about the meeting I attended yesterday.",
1803
+ "Review the PR that broke prod in my absence.",
1804
+ "Sync with stakeholders (unclear which ones).",
1805
+ "Work on the ticket that's been in 'In Progress' for three weeks.",
1806
+ "Close 47 browser tabs from my last debugging session.",
1807
+ "Decide if it's worth bringing up in the retro.",
1808
+ ];
1809
+ const STANDUP_BLOCKERS = [
1810
+ "No blockers (several blockers).",
1811
+ "Waiting on design.",
1812
+ "Waiting on code review.",
1813
+ "Waiting on a response from [NAME] who has been unreachable since Tuesday.",
1814
+ "The ticket is unclear but I'm afraid to ask.",
1815
+ "My local environment.",
1816
+ "A meeting conflict I haven't resolved yet.",
1817
+ "My own earlier decisions.",
1818
+ "Nothing that I'm willing to surface right now.",
1819
+ "The concept of time.",
1820
+ ];
1821
+
1822
+ /**
1823
+ * Generates a realistic-sounding daily standup.
1824
+ */
1825
+ function standup() {
1826
+ return {
1827
+ yesterday: _pick(STANDUP_YESTERDAY),
1828
+ today: _pick(STANDUP_TODAY),
1829
+ blockers: _pick(STANDUP_BLOCKERS),
1830
+ };
1831
+ }
1832
+
1833
+ // ─── RETROGRADE EXCUSE ────────────────────────────────────────────────────────
1834
+
1835
+ const RETROGRADE_PLANETS = ["Mercury","Venus","Mars","Jupiter","Saturn","Uranus","Neptune","Pluto (still counts)"];
1836
+ const RETROGRADE_EFFECTS = [
1837
+ "is causing DNS failures.",
1838
+ "is responsible for your failed deploys.",
1839
+ "explains the race condition.",
1840
+ "is why the tests are flaky.",
1841
+ "is making the CEO ask for a new feature today.",
1842
+ "caused your git push to be rejected.",
1843
+ "is why npm install broke on the CI server.",
1844
+ "explains the merge conflict with yourself.",
1845
+ "is why production is down but staging is fine.",
1846
+ "is responsible for the missing semicolon.",
1847
+ ];
1848
+
1849
+ /**
1850
+ * Blames the current engineering problem on planetary retrograde.
1851
+ * Astrologically certified.
1852
+ */
1853
+ function retrogradExcuse() {
1854
+ return `${_pick(RETROGRADE_PLANETS)} in retrograde ${_pick(RETROGRADE_EFFECTS)}`;
1855
+ }
1856
+
1857
+ // ─── YELL (MOCK SHOUTING) ─────────────────────────────────────────────────────
1858
+
1859
+ /**
1860
+ * Converts text to SHOUTING CASE with random extra letters for emphasis.
1861
+ * @param {string} text
1862
+ * @param {number} intensity 0.0–1.0
1863
+ */
1864
+ function yell(text, intensity = 0.5) {
1865
+ return String(text)
1866
+ .toUpperCase()
1867
+ .replace(/[A-Z]/g, (ch) =>
1868
+ Math.random() < intensity * 0.3 ? ch.repeat(2 + (Math.random() * 3 | 0)) : ch
1869
+ ) + "!".repeat(1 + (Math.random() * (intensity * 5) | 0));
1870
+ }
1871
+
1872
+ // ─── TINY STORY GENERATOR ────────────────────────────────────────────────────
1873
+
1874
+ const STORY_HERO = ["a senior engineer","a junior dev","a product manager","the on-call rotation","an intern","a Jira ticket","a rogue cron job","a forgotten Lambda function","the CEO","a rubber duck"];
1875
+ const STORY_ACTION = ["accidentally deleted","heroically refactored","mysteriously fixed","desperately tried to understand","fell asleep looking at","wrote unit tests for","blamed DevOps for","opened a ticket about","scheduled a meeting to discuss","gave up on"];
1876
+ const STORY_OBJECT = ["the entire production database","a 4,000-line function","the authentication service","a config file with no comments","the company's only backup","a deprecated API endpoint","a regex nobody understands","a global variable named `data`","the last 6 months of logs","the monorepo"];
1877
+ const STORY_END = ["It's fine.","Nobody noticed.","This is in production right now.","The incident report is still open.","They got a promotion anyway.","It worked. We don't know why.","Jira has a ticket for it.","The post-mortem is scheduled for next quarter.","They blamed it on Mercury retrograde.","Same time tomorrow."];
1878
+
1879
+ /**
1880
+ * Generates a tiny developer tragedy in three sentences.
1881
+ */
1882
+ function tinyStory() {
1883
+ return `${_pick(STORY_HERO)} ${_pick(STORY_ACTION)} ${_pick(STORY_OBJECT)}. ${_pick(STORY_END)} ${_pick(STORY_END)}`;
1884
+ }
1885
+
1886
+ // ─── IS IT DNS? ────────────────────────────────────────────────────────────────
1887
+
1888
+ /**
1889
+ * Determines if the problem is DNS.
1890
+ * It's always DNS.
1891
+ * @param {string} [problem]
1892
+ * @returns {{ isDNS: boolean, confidence: string, explanation: string }}
1893
+ */
1894
+ function isItDNS(problem) {
1895
+ const p = String(problem ?? "").toLowerCase();
1896
+ const notDNSWords = ["css","ui","ux","font","color","margin","padding","layout","animation","transition"];
1897
+ const definitelyDNS = notDNSWords.every(w => !p.includes(w));
1898
+ return {
1899
+ isDNS: true, // always
1900
+ confidence: definitelyDNS ? "100%" : "97% (it's still probably DNS)",
1901
+ explanation: definitelyDNS
1902
+ ? "It's DNS. It's always DNS."
1903
+ : "It looked like a CSS problem. It's DNS.",
1904
+ };
1905
+ }
1906
+
1907
+ // ─── ROCK PAPER SCISSORS ─────────────────────────────────────────────────────
1908
+
1909
+ const RPS_BEATS = { rock: "scissors", scissors: "paper", paper: "rock" };
1910
+ const RPS_MOVES = Object.keys(RPS_BEATS);
1911
+
1912
+ /**
1913
+ * Plays rock-paper-scissors against the computer.
1914
+ * @param {'rock'|'paper'|'scissors'} playerMove
1915
+ * @returns {{ player: string, computer: string, result: 'win'|'lose'|'draw' }}
1916
+ */
1917
+ function rps(playerMove) {
1918
+ const p = String(playerMove).toLowerCase();
1919
+ if (!RPS_BEATS[p]) throw new Error(`rps: "${playerMove}" is not a valid move. Try rock, paper, or scissors.`);
1920
+ const computer = _pick(RPS_MOVES);
1921
+ const result = p === computer ? "draw" : RPS_BEATS[p] === computer ? "win" : "lose";
1922
+ return { player: p, computer, result };
1923
+ }
1924
+
1925
+ // ─── COIN FLIP ────────────────────────────────────────────────────────────────
1926
+
1927
+ /**
1928
+ * Flips a coin. Handles the hard architectural decisions.
1929
+ * @param {number} count
1930
+ */
1931
+ function coinFlip(count = 1) {
1932
+ const flips = Array.from({ length: count }, () => Math.random() < 0.5 ? "heads" : "tails");
1933
+ return count === 1 ? flips[0] : { flips, heads: flips.filter(f=>f==="heads").length, tails: flips.filter(f=>f==="tails").length };
1934
+ }
1935
+
1936
+ // ─── SHOULD I REWRITE IT IN RUST? ────────────────────────────────────────────
1937
+
1938
+ const RUST_YES = [
1939
+ "Absolutely. Start today.",
1940
+ "Yes. It's the only way.",
1941
+ "Without question. The memory safety alone justifies it.",
1942
+ "Yes. Attach the migration plan to your next PR.",
1943
+ "The compiler will thank you. Eventually.",
1944
+ ];
1945
+ const RUST_NO = [
1946
+ "No. Ship what you have.",
1947
+ "Not yet. Finish the feature first.",
1948
+ "No. Your team will mutiny.",
1949
+ "No. But think about it for six more months.",
1950
+ "Not unless you enjoy explaining lifetimes to your PM.",
1951
+ ];
1952
+
1953
+ /**
1954
+ * Answers the eternal question.
1955
+ * @returns {{ answer: string, confidence: string }}
1956
+ */
1957
+ function shouldRewriteInRust() {
1958
+ const yes = Math.random() > 0.5;
1959
+ return {
1960
+ answer: _pick(yes ? RUST_YES : RUST_NO),
1961
+ confidence: `${60 + (Math.random() * 39 | 0)}%`,
1962
+ };
1963
+ }
1964
+
1965
+ // ─── LOREM IPSUM BUT WORSE ────────────────────────────────────────────────────
1966
+
1967
+ const DEV_LOREM_CHUNKS = [
1968
+ "undefined is not a function","cannot read property of null","works on my machine","it's a known issue","we can fix it in the next sprint","the ticket is in the backlog","have you tried turning it off and on again","this is just a workaround","the tests were passing locally","we'll add proper error handling later","let's not over-engineer this","this is a temporary solution","the documentation is out of date","we can refactor it after the release","it's not a bug it's a feature","someone else will fix it","we just need to move fast","the build has been failing for two weeks","nobody remembers why this is here","it made sense at the time",
1969
+ ];
1970
+
1971
+ /**
1972
+ * Like lorem ipsum, but for developers.
1973
+ * @param {number} count - number of chunks
1974
+ */
1975
+ function devLorem(count = 5) {
1976
+ const chunks = Array.from({ length: count }, () => _pick(DEV_LOREM_CHUNKS));
1977
+ const result = chunks.join(", ") + ".";
1978
+ return result[0].toUpperCase() + result.slice(1);
1979
+ }
1980
+
1981
+ // ─── THE DAILY WTF ────────────────────────────────────────────────────────────
1982
+
1983
+ const WTF_SNIPPETS = [
1984
+ `// I have no idea why this works but don't touch it\nif (typeof x !== 'undefined' && x !== null && !!x && x) { return x; }`,
1985
+ `// TODO: remove this in v2\n// (this comment is from 2019)`,
1986
+ `const TRUE = false;\nconst FALSE = true; // legacy reasons`,
1987
+ `// This is definitely not a hack\nsetTimeout(() => fixLayout(), 1000);`,
1988
+ `function add(a, b) {\n // This was faster\n return a - (-b);\n}`,
1989
+ `if (user.role === 'admin' || user.role === 'admin ') { // don't ask`,
1990
+ `// never null, trust me\nreturn value!; // TypeScript`,
1991
+ `const data = JSON.parse(JSON.stringify(data)); // deep clone`,
1992
+ `catch (e) { } // the error was annoying`,
1993
+ `// works in prod, breaks in dev\n// works in dev, breaks in prod\nreturn process.env.NODE_ENV === 'production' ? x * 2 : x / 2;`,
1994
+ `for (let i = 0; i < array.length; i++) {\n i--; i++; // don't remove, fixes off-by-one\n}`,
1995
+ `const isTrue = (val) => val === true || val === 'true' || val === 1 || val === '1' || val === 'yes' || val === 'on';`,
1996
+ ];
1997
+
1998
+ /**
1999
+ * Returns a snippet of code that will haunt you.
2000
+ */
2001
+ function dailyWTF() {
2002
+ return _pick(WTF_SNIPPETS);
2003
+ }
2004
+
2005
+ // ─── EXPORTS ──────────────────────────────────────────────────────────────────
2006
+
2007
+ let newFeatures = {
2008
+ buzzword,
2009
+ excuse,
2010
+ commitMessage,
2011
+ badVarName,
2012
+ devHoroscope,
2013
+ enterpriseFunctionName,
2014
+ detectKeyboardSmash,
2015
+ detectSarcasm,
2016
+ meetingTitle,
2017
+ paEmail,
2018
+ loadingMessage,
2019
+ programmerType,
2020
+ sudo,
2021
+ codeCompliment,
2022
+ codeInsult,
2023
+ technobabble,
2024
+ startupName,
2025
+ dependencyAudit,
2026
+ standup,
2027
+ retrogradExcuse,
2028
+ yell,
2029
+ tinyStory,
2030
+ isItDNS,
2031
+ rps,
2032
+ coinFlip,
2033
+ shouldRewriteInRust,
2034
+ devLorem,
2035
+ dailyWTF,
2036
+ };
2037
+ module.exports = { kitdef: {...kitdef, ...newFeatures} };