windsor-bot 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.
Files changed (226) hide show
  1. package/README.md +128 -0
  2. package/SPEC.md +179 -0
  3. package/assets/pokemon-sprites/1.png +0 -0
  4. package/assets/pokemon-sprites/10.png +0 -0
  5. package/assets/pokemon-sprites/100.png +0 -0
  6. package/assets/pokemon-sprites/101.png +0 -0
  7. package/assets/pokemon-sprites/102.png +0 -0
  8. package/assets/pokemon-sprites/103.png +0 -0
  9. package/assets/pokemon-sprites/104.png +0 -0
  10. package/assets/pokemon-sprites/105.png +0 -0
  11. package/assets/pokemon-sprites/106.png +0 -0
  12. package/assets/pokemon-sprites/107.png +0 -0
  13. package/assets/pokemon-sprites/108.png +0 -0
  14. package/assets/pokemon-sprites/109.png +0 -0
  15. package/assets/pokemon-sprites/11.png +0 -0
  16. package/assets/pokemon-sprites/110.png +0 -0
  17. package/assets/pokemon-sprites/111.png +0 -0
  18. package/assets/pokemon-sprites/112.png +0 -0
  19. package/assets/pokemon-sprites/113.png +0 -0
  20. package/assets/pokemon-sprites/114.png +0 -0
  21. package/assets/pokemon-sprites/115.png +0 -0
  22. package/assets/pokemon-sprites/116.png +0 -0
  23. package/assets/pokemon-sprites/117.png +0 -0
  24. package/assets/pokemon-sprites/118.png +0 -0
  25. package/assets/pokemon-sprites/119.png +0 -0
  26. package/assets/pokemon-sprites/12.png +0 -0
  27. package/assets/pokemon-sprites/120.png +0 -0
  28. package/assets/pokemon-sprites/121.png +0 -0
  29. package/assets/pokemon-sprites/122.png +0 -0
  30. package/assets/pokemon-sprites/123.png +0 -0
  31. package/assets/pokemon-sprites/124.png +0 -0
  32. package/assets/pokemon-sprites/125.png +0 -0
  33. package/assets/pokemon-sprites/126.png +0 -0
  34. package/assets/pokemon-sprites/127.png +0 -0
  35. package/assets/pokemon-sprites/128.png +0 -0
  36. package/assets/pokemon-sprites/129.png +0 -0
  37. package/assets/pokemon-sprites/13.png +0 -0
  38. package/assets/pokemon-sprites/130.png +0 -0
  39. package/assets/pokemon-sprites/131.png +0 -0
  40. package/assets/pokemon-sprites/132.png +0 -0
  41. package/assets/pokemon-sprites/133.png +0 -0
  42. package/assets/pokemon-sprites/134.png +0 -0
  43. package/assets/pokemon-sprites/135.png +0 -0
  44. package/assets/pokemon-sprites/136.png +0 -0
  45. package/assets/pokemon-sprites/137.png +0 -0
  46. package/assets/pokemon-sprites/138.png +0 -0
  47. package/assets/pokemon-sprites/139.png +0 -0
  48. package/assets/pokemon-sprites/14.png +0 -0
  49. package/assets/pokemon-sprites/140.png +0 -0
  50. package/assets/pokemon-sprites/141.png +0 -0
  51. package/assets/pokemon-sprites/142.png +0 -0
  52. package/assets/pokemon-sprites/143.png +0 -0
  53. package/assets/pokemon-sprites/144.png +0 -0
  54. package/assets/pokemon-sprites/145.png +0 -0
  55. package/assets/pokemon-sprites/146.png +0 -0
  56. package/assets/pokemon-sprites/147.png +0 -0
  57. package/assets/pokemon-sprites/148.png +0 -0
  58. package/assets/pokemon-sprites/149.png +0 -0
  59. package/assets/pokemon-sprites/15.png +0 -0
  60. package/assets/pokemon-sprites/150.png +0 -0
  61. package/assets/pokemon-sprites/151.png +0 -0
  62. package/assets/pokemon-sprites/16.png +0 -0
  63. package/assets/pokemon-sprites/17.png +0 -0
  64. package/assets/pokemon-sprites/18.png +0 -0
  65. package/assets/pokemon-sprites/19.png +0 -0
  66. package/assets/pokemon-sprites/2.png +0 -0
  67. package/assets/pokemon-sprites/20.png +0 -0
  68. package/assets/pokemon-sprites/21.png +0 -0
  69. package/assets/pokemon-sprites/22.png +0 -0
  70. package/assets/pokemon-sprites/23.png +0 -0
  71. package/assets/pokemon-sprites/24.png +0 -0
  72. package/assets/pokemon-sprites/25.png +0 -0
  73. package/assets/pokemon-sprites/26.png +0 -0
  74. package/assets/pokemon-sprites/27.png +0 -0
  75. package/assets/pokemon-sprites/28.png +0 -0
  76. package/assets/pokemon-sprites/29.png +0 -0
  77. package/assets/pokemon-sprites/3.png +0 -0
  78. package/assets/pokemon-sprites/30.png +0 -0
  79. package/assets/pokemon-sprites/31.png +0 -0
  80. package/assets/pokemon-sprites/32.png +0 -0
  81. package/assets/pokemon-sprites/33.png +0 -0
  82. package/assets/pokemon-sprites/34.png +0 -0
  83. package/assets/pokemon-sprites/35.png +0 -0
  84. package/assets/pokemon-sprites/36.png +0 -0
  85. package/assets/pokemon-sprites/37.png +0 -0
  86. package/assets/pokemon-sprites/38.png +0 -0
  87. package/assets/pokemon-sprites/39.png +0 -0
  88. package/assets/pokemon-sprites/4.png +0 -0
  89. package/assets/pokemon-sprites/40.png +0 -0
  90. package/assets/pokemon-sprites/41.png +0 -0
  91. package/assets/pokemon-sprites/42.png +0 -0
  92. package/assets/pokemon-sprites/43.png +0 -0
  93. package/assets/pokemon-sprites/44.png +0 -0
  94. package/assets/pokemon-sprites/45.png +0 -0
  95. package/assets/pokemon-sprites/46.png +0 -0
  96. package/assets/pokemon-sprites/47.png +0 -0
  97. package/assets/pokemon-sprites/48.png +0 -0
  98. package/assets/pokemon-sprites/49.png +0 -0
  99. package/assets/pokemon-sprites/5.png +0 -0
  100. package/assets/pokemon-sprites/50.png +0 -0
  101. package/assets/pokemon-sprites/51.png +0 -0
  102. package/assets/pokemon-sprites/52.png +0 -0
  103. package/assets/pokemon-sprites/53.png +0 -0
  104. package/assets/pokemon-sprites/54.png +0 -0
  105. package/assets/pokemon-sprites/55.png +0 -0
  106. package/assets/pokemon-sprites/56.png +0 -0
  107. package/assets/pokemon-sprites/57.png +0 -0
  108. package/assets/pokemon-sprites/58.png +0 -0
  109. package/assets/pokemon-sprites/59.png +0 -0
  110. package/assets/pokemon-sprites/6.png +0 -0
  111. package/assets/pokemon-sprites/60.png +0 -0
  112. package/assets/pokemon-sprites/61.png +0 -0
  113. package/assets/pokemon-sprites/62.png +0 -0
  114. package/assets/pokemon-sprites/63.png +0 -0
  115. package/assets/pokemon-sprites/64.png +0 -0
  116. package/assets/pokemon-sprites/65.png +0 -0
  117. package/assets/pokemon-sprites/66.png +0 -0
  118. package/assets/pokemon-sprites/67.png +0 -0
  119. package/assets/pokemon-sprites/68.png +0 -0
  120. package/assets/pokemon-sprites/69.png +0 -0
  121. package/assets/pokemon-sprites/7.png +0 -0
  122. package/assets/pokemon-sprites/70.png +0 -0
  123. package/assets/pokemon-sprites/71.png +0 -0
  124. package/assets/pokemon-sprites/72.png +0 -0
  125. package/assets/pokemon-sprites/73.png +0 -0
  126. package/assets/pokemon-sprites/74.png +0 -0
  127. package/assets/pokemon-sprites/75.png +0 -0
  128. package/assets/pokemon-sprites/76.png +0 -0
  129. package/assets/pokemon-sprites/77.png +0 -0
  130. package/assets/pokemon-sprites/78.png +0 -0
  131. package/assets/pokemon-sprites/79.png +0 -0
  132. package/assets/pokemon-sprites/8.png +0 -0
  133. package/assets/pokemon-sprites/80.png +0 -0
  134. package/assets/pokemon-sprites/81.png +0 -0
  135. package/assets/pokemon-sprites/82.png +0 -0
  136. package/assets/pokemon-sprites/83.png +0 -0
  137. package/assets/pokemon-sprites/84.png +0 -0
  138. package/assets/pokemon-sprites/85.png +0 -0
  139. package/assets/pokemon-sprites/86.png +0 -0
  140. package/assets/pokemon-sprites/87.png +0 -0
  141. package/assets/pokemon-sprites/88.png +0 -0
  142. package/assets/pokemon-sprites/89.png +0 -0
  143. package/assets/pokemon-sprites/9.png +0 -0
  144. package/assets/pokemon-sprites/90.png +0 -0
  145. package/assets/pokemon-sprites/91.png +0 -0
  146. package/assets/pokemon-sprites/92.png +0 -0
  147. package/assets/pokemon-sprites/93.png +0 -0
  148. package/assets/pokemon-sprites/94.png +0 -0
  149. package/assets/pokemon-sprites/95.png +0 -0
  150. package/assets/pokemon-sprites/96.png +0 -0
  151. package/assets/pokemon-sprites/97.png +0 -0
  152. package/assets/pokemon-sprites/98.png +0 -0
  153. package/assets/pokemon-sprites/99.png +0 -0
  154. package/dist/ai.js +193 -0
  155. package/dist/bot.d.ts +22 -0
  156. package/dist/bot.d.ts.map +1 -0
  157. package/dist/bot.js +547 -0
  158. package/dist/bot.js.map +1 -0
  159. package/dist/commands/debug-fail.js +8 -0
  160. package/dist/commands/hello-world.js +12 -0
  161. package/dist/commands/hello.d.ts +3 -0
  162. package/dist/commands/hello.d.ts.map +1 -0
  163. package/dist/commands/hello.js +8 -0
  164. package/dist/commands/hello.js.map +1 -0
  165. package/dist/commands/index.d.ts +3 -0
  166. package/dist/commands/index.d.ts.map +1 -0
  167. package/dist/commands/index.js +12 -0
  168. package/dist/commands/index.js.map +1 -0
  169. package/dist/commands/pokemon.js +132 -0
  170. package/dist/commands/print.d.ts +9 -0
  171. package/dist/commands/print.d.ts.map +1 -0
  172. package/dist/commands/print.js +46 -0
  173. package/dist/commands/print.js.map +1 -0
  174. package/dist/commands/sudoku.js +79 -0
  175. package/dist/commands/util.js +13 -0
  176. package/dist/commands/wordsearch.js +109 -0
  177. package/dist/config.d.ts +8 -0
  178. package/dist/config.d.ts.map +1 -0
  179. package/dist/config.js +149 -0
  180. package/dist/config.js.map +1 -0
  181. package/dist/index.d.ts +2 -0
  182. package/dist/index.d.ts.map +1 -0
  183. package/dist/index.js +85 -0
  184. package/dist/index.js.map +1 -0
  185. package/dist/printer.js +245 -0
  186. package/dist/printing/cups.js +89 -0
  187. package/dist/printing/escp.js +147 -0
  188. package/dist/printing/imagefeed.js +212 -0
  189. package/dist/printing/index.js +54 -0
  190. package/dist/reactions.js +5 -0
  191. package/dist/server.d.ts +5 -0
  192. package/dist/server.d.ts.map +1 -0
  193. package/dist/server.js +312 -0
  194. package/dist/server.js.map +1 -0
  195. package/dist/tests/config.test.js +78 -0
  196. package/dist/tests/urls.test.js +54 -0
  197. package/dist/types.d.ts +55 -0
  198. package/dist/types.d.ts.map +1 -0
  199. package/dist/types.js +1 -0
  200. package/dist/types.js.map +1 -0
  201. package/dist/web/app.js +325 -0
  202. package/package.json +45 -0
  203. package/scripts/download-pokemon-sprites.mjs +38 -0
  204. package/src/ai.ts +214 -0
  205. package/src/bot.ts +657 -0
  206. package/src/commands/debug-fail.ts +11 -0
  207. package/src/commands/hello-world.ts +15 -0
  208. package/src/commands/index.ts +35 -0
  209. package/src/commands/pokemon.ts +146 -0
  210. package/src/commands/sudoku.ts +94 -0
  211. package/src/commands/util.ts +14 -0
  212. package/src/commands/wordsearch.ts +118 -0
  213. package/src/config.ts +166 -0
  214. package/src/index.ts +98 -0
  215. package/src/printer.ts +268 -0
  216. package/src/printing/cups.ts +104 -0
  217. package/src/printing/escp.ts +158 -0
  218. package/src/printing/imagefeed.ts +246 -0
  219. package/src/printing/index.ts +63 -0
  220. package/src/reactions.ts +6 -0
  221. package/src/server.ts +355 -0
  222. package/src/tests/config.test.ts +88 -0
  223. package/src/tests/urls.test.ts +64 -0
  224. package/src/types.ts +120 -0
  225. package/src/web/app.tsx +690 -0
  226. package/tsconfig.json +25 -0
@@ -0,0 +1,132 @@
1
+ import { join, dirname } from 'path';
2
+ import { fileURLToPath } from 'url';
3
+ import { tryExecCommandFunction } from './util.js';
4
+ const __dirname = dirname(fileURLToPath(import.meta.url));
5
+ const SPRITE_DIR = join(__dirname, '../assets/pokemon-sprites');
6
+ // Kanto Pokédex — index 0 = Bulbasaur (#1)
7
+ const KANTO = [
8
+ 'Bulbasaur', 'Ivysaur', 'Venusaur',
9
+ 'Charmander', 'Charmeleon', 'Charizard',
10
+ 'Squirtle', 'Wartortle', 'Blastoise',
11
+ 'Caterpie', 'Metapod', 'Butterfree',
12
+ 'Weedle', 'Kakuna', 'Beedrill',
13
+ 'Pidgey', 'Pidgeotto', 'Pidgeot',
14
+ 'Rattata', 'Raticate',
15
+ 'Spearow', 'Fearow',
16
+ 'Ekans', 'Arbok',
17
+ 'Pikachu', 'Raichu',
18
+ 'Sandshrew', 'Sandslash',
19
+ 'Nidoran♀', 'Nidorina', 'Nidoqueen',
20
+ 'Nidoran♂', 'Nidorino', 'Nidoking',
21
+ 'Clefairy', 'Clefable',
22
+ 'Vulpix', 'Ninetales',
23
+ 'Jigglypuff', 'Wigglytuff',
24
+ 'Zubat', 'Golbat',
25
+ 'Oddish', 'Gloom', 'Vileplume',
26
+ 'Paras', 'Parasect',
27
+ 'Venonat', 'Venomoth',
28
+ 'Diglett', 'Dugtrio',
29
+ 'Meowth', 'Persian',
30
+ 'Psyduck', 'Golduck',
31
+ 'Mankey', 'Primeape',
32
+ 'Growlithe', 'Arcanine',
33
+ 'Poliwag', 'Poliwhirl', 'Poliwrath',
34
+ 'Abra', 'Kadabra', 'Alakazam',
35
+ 'Machop', 'Machoke', 'Machamp',
36
+ 'Bellsprout', 'Weepinbell', 'Victreebel',
37
+ 'Tentacool', 'Tentacruel',
38
+ 'Geodude', 'Graveler', 'Golem',
39
+ 'Ponyta', 'Rapidash',
40
+ 'Slowpoke', 'Slowbro',
41
+ 'Magnemite', 'Magneton',
42
+ "Farfetch'd",
43
+ 'Doduo', 'Dodrio',
44
+ 'Seel', 'Dewgong',
45
+ 'Grimer', 'Muk',
46
+ 'Shellder', 'Cloyster',
47
+ 'Gastly', 'Haunter', 'Gengar',
48
+ 'Onix',
49
+ 'Drowzee', 'Hypno',
50
+ 'Krabby', 'Kingler',
51
+ 'Voltorb', 'Electrode',
52
+ 'Exeggcute', 'Exeggutor',
53
+ 'Cubone', 'Marowak',
54
+ 'Hitmonlee', 'Hitmonchan',
55
+ 'Lickitung',
56
+ 'Koffing', 'Weezing',
57
+ 'Rhyhorn', 'Rhydon',
58
+ 'Chansey',
59
+ 'Tangela',
60
+ 'Kangaskhan',
61
+ 'Horsea', 'Seadra',
62
+ 'Goldeen', 'Seaking',
63
+ 'Staryu', 'Starmie',
64
+ 'Mr. Mime',
65
+ 'Scyther',
66
+ 'Jynx',
67
+ 'Electabuzz',
68
+ 'Magmar',
69
+ 'Pinsir',
70
+ 'Tauros',
71
+ 'Magikarp', 'Gyarados',
72
+ 'Lapras',
73
+ 'Ditto',
74
+ 'Eevee', 'Vaporeon', 'Jolteon', 'Flareon',
75
+ 'Porygon',
76
+ 'Omanyte', 'Omastar',
77
+ 'Kabuto', 'Kabutops',
78
+ 'Aerodactyl',
79
+ 'Snorlax',
80
+ 'Articuno', 'Zapdos', 'Moltres',
81
+ 'Dratini', 'Dragonair', 'Dragonite',
82
+ 'Mewtwo',
83
+ 'Mew',
84
+ ];
85
+ function findPokemon(arg) {
86
+ const trimmed = arg.trim();
87
+ // Try by number
88
+ const num = parseInt(trimmed, 10);
89
+ if (!isNaN(num) && num >= 1 && num <= 151) {
90
+ return { id: num, name: KANTO[num - 1] };
91
+ }
92
+ // Try by name (case-insensitive)
93
+ const lower = trimmed.toLowerCase();
94
+ const idx = KANTO.findIndex(n => n.toLowerCase() === lower);
95
+ if (idx !== -1) {
96
+ return { id: idx + 1, name: KANTO[idx] };
97
+ }
98
+ return null;
99
+ }
100
+ async function pokemonWorker(args, ctx) {
101
+ let id;
102
+ let name;
103
+ if (!args.trim()) {
104
+ // Random
105
+ id = Math.floor(Math.random() * KANTO.length) + 1;
106
+ name = KANTO[id - 1];
107
+ }
108
+ else {
109
+ const found = findPokemon(args);
110
+ if (!found) {
111
+ return {
112
+ kind: 'fail',
113
+ reason: `Unknown Pokémon "${args.trim()}". Use a name or number 1-151.`,
114
+ };
115
+ }
116
+ ({ id, name } = found);
117
+ }
118
+ const spritePath = join(SPRITE_DIR, `${id}.png`);
119
+ const numStr = String(id).padStart(3, '0');
120
+ const job = {
121
+ header: `#${numStr} ${name}`,
122
+ lines: [],
123
+ urls: [],
124
+ iconPath: spritePath,
125
+ };
126
+ await ctx.printJob(job);
127
+ return { kind: 'pass', reply: `Printing #${numStr} ${name}!` };
128
+ }
129
+ export const printPokemon = {
130
+ aliases: ['pokemon', 'kanto'],
131
+ invoke: tryExecCommandFunction(pokemonWorker),
132
+ };
@@ -0,0 +1,9 @@
1
+ import type { BotCommand } from '../types.js';
2
+ /**
3
+ * Prints text to the configured CUPS printer.
4
+ *
5
+ * Environment variables:
6
+ * PRINTER_NAME - CUPS printer name (optional; omit to use the system default)
7
+ */
8
+ export declare const printCommand: BotCommand;
9
+ //# sourceMappingURL=print.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"print.d.ts","sourceRoot":"","sources":["../../src/commands/print.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C;;;;;GAKG;AACH,eAAO,MAAM,YAAY,EAAE,UAkB1B,CAAC"}
@@ -0,0 +1,46 @@
1
+ import { exec } from 'child_process';
2
+ import { writeFile, unlink } from 'fs/promises';
3
+ import { tmpdir } from 'os';
4
+ import { join } from 'path';
5
+ /**
6
+ * Prints text to the configured CUPS printer.
7
+ *
8
+ * Environment variables:
9
+ * PRINTER_NAME - CUPS printer name (optional; omit to use the system default)
10
+ */
11
+ export const printCommand = {
12
+ name: 'print',
13
+ channels: ['todo'],
14
+ description: 'Prints the given text on the local printer. Usage: !print <text>',
15
+ async execute(ctx) {
16
+ const text = ctx.args.trim();
17
+ if (!text)
18
+ throw new Error('Nothing to print — provide text after !print');
19
+ const tmpFile = join(tmpdir(), `d2p-${Date.now()}.txt`);
20
+ await writeFile(tmpFile, text + '\n', 'utf8');
21
+ try {
22
+ await printFile(tmpFile);
23
+ }
24
+ finally {
25
+ await unlink(tmpFile).catch(() => undefined);
26
+ }
27
+ },
28
+ };
29
+ function printFile(filePath) {
30
+ const printerName = process.env['PRINTER_NAME'];
31
+ const destination = printerName ? `-d ${shellQuote(printerName)}` : '';
32
+ const cmd = `lp ${destination} ${shellQuote(filePath)}`;
33
+ return new Promise((resolve, reject) => {
34
+ exec(cmd, (err, _stdout, stderr) => {
35
+ if (err) {
36
+ reject(new Error(`lp failed: ${stderr.trim() || err.message}`));
37
+ }
38
+ else {
39
+ resolve();
40
+ }
41
+ });
42
+ });
43
+ }
44
+ function shellQuote(s) {
45
+ return `'${s.replace(/'/g, "'\\''")}'`;
46
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"print.js","sourceRoot":"","sources":["../../src/commands/print.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AACrC,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,MAAM,EAAE,MAAM,IAAI,CAAC;AAC5B,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAG5B;;;;;GAKG;AACH,MAAM,CAAC,MAAM,YAAY,GAAe;IACpC,IAAI,EAAE,OAAO;IACb,QAAQ,EAAE,CAAC,MAAM,CAAC;IAClB,WAAW,EAAE,kEAAkE;IAE/E,KAAK,CAAC,OAAO,CAAC,GAAG;QACb,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;QAC7B,IAAI,CAAC,IAAI;YAAE,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;QAE3E,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,EAAE,EAAE,OAAO,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;QACxD,MAAM,SAAS,CAAC,OAAO,EAAE,IAAI,GAAG,IAAI,EAAE,MAAM,CAAC,CAAC;QAE9C,IAAI,CAAC;YACD,MAAM,SAAS,CAAC,OAAO,CAAC,CAAC;QAC7B,CAAC;gBAAS,CAAC;YACP,MAAM,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QACjD,CAAC;IACL,CAAC;CACJ,CAAC;AAEF,SAAS,SAAS,CAAC,QAAgB;IAC/B,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAChD,MAAM,WAAW,GAAG,WAAW,CAAC,CAAC,CAAC,MAAM,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACvE,MAAM,GAAG,GAAG,MAAM,WAAW,IAAI,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;IAExD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACnC,IAAI,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE;YAC/B,IAAI,GAAG,EAAE,CAAC;gBACN,MAAM,CAAC,IAAI,KAAK,CAAC,cAAc,MAAM,CAAC,IAAI,EAAE,IAAI,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;YACpE,CAAC;iBAAM,CAAC;gBACJ,OAAO,EAAE,CAAC;YACd,CAAC;QACL,CAAC,CAAC,CAAC;IACP,CAAC,CAAC,CAAC;AACP,CAAC;AAED,SAAS,UAAU,CAAC,CAAS;IACzB,OAAO,IAAI,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC;AAC3C,CAAC"}
@@ -0,0 +1,79 @@
1
+ import { tryExecCommandFunction } from './util.js';
2
+ function generateSudoku(difficulty) {
3
+ // Start with a solved grid
4
+ const base = [
5
+ [5, 3, 4, 6, 7, 8, 9, 1, 2],
6
+ [6, 7, 2, 1, 9, 5, 3, 4, 8],
7
+ [1, 9, 8, 3, 4, 2, 5, 6, 7],
8
+ [8, 5, 9, 7, 6, 1, 4, 2, 3],
9
+ [4, 2, 6, 8, 5, 3, 7, 9, 1],
10
+ [7, 1, 3, 9, 2, 4, 8, 5, 6],
11
+ [9, 6, 1, 5, 3, 7, 2, 8, 4],
12
+ [2, 8, 7, 4, 1, 9, 6, 3, 5],
13
+ [3, 4, 5, 2, 8, 6, 1, 7, 9],
14
+ ];
15
+ // Number of cells to remove based on difficulty
16
+ const removes = {
17
+ '': 35,
18
+ kid: 20,
19
+ easy: 35,
20
+ medium: 45,
21
+ hard: 55,
22
+ };
23
+ const toRemove = removes[difficulty] ?? 35;
24
+ // Shuffle positions and remove
25
+ const positions = Array.from({ length: 81 }, (_, i) => i);
26
+ for (let i = positions.length - 1; i > 0; i--) {
27
+ const j = Math.floor(Math.random() * (i + 1));
28
+ const tmp = positions[i];
29
+ positions[i] = positions[j];
30
+ positions[j] = tmp;
31
+ }
32
+ const grid = base.map(row => [...row]);
33
+ for (let i = 0; i < toRemove; i++) {
34
+ const pos = positions[i];
35
+ if (pos === undefined)
36
+ break;
37
+ const row = Math.floor(pos / 9);
38
+ const col = pos % 9;
39
+ if (grid[row])
40
+ grid[row][col] = 0;
41
+ }
42
+ return grid;
43
+ }
44
+ function renderSudokuGrid(grid) {
45
+ const lines = [];
46
+ lines.push('┌───────┬───────┬───────┐');
47
+ for (let row = 0; row < 9; row++) {
48
+ const cells = grid[row] ?? [];
49
+ const r1 = cells.slice(0, 3).map(c => c === 0 ? '·' : String(c)).join(' ');
50
+ const r2 = cells.slice(3, 6).map(c => c === 0 ? '·' : String(c)).join(' ');
51
+ const r3 = cells.slice(6, 9).map(c => c === 0 ? '·' : String(c)).join(' ');
52
+ lines.push(`│ ${r1} │ ${r2} │ ${r3} │`);
53
+ if (row === 2 || row === 5) {
54
+ lines.push('├───────┼───────┼───────┤');
55
+ }
56
+ }
57
+ lines.push('└───────┴───────┴───────┘');
58
+ return lines;
59
+ }
60
+ async function printSudokuWorker(args, ctx) {
61
+ // !TODO: Implement good command parsing
62
+ const difficulty = args;
63
+ const diff = difficulty || 'easy';
64
+ const grid = generateSudoku(diff);
65
+ const gridLines = renderSudokuGrid(grid);
66
+ const job = {
67
+ urls: [],
68
+ header: `Sudoku (${diff})`,
69
+ lines: gridLines,
70
+ };
71
+ await ctx.printJob(job);
72
+ return {
73
+ kind: "pass"
74
+ };
75
+ }
76
+ export const printSudoku = {
77
+ aliases: ["sudoku"],
78
+ invoke: tryExecCommandFunction(printSudokuWorker)
79
+ };
@@ -0,0 +1,13 @@
1
+ export function tryExecCommandFunction(func) {
2
+ return async function (cmd, ctx) {
3
+ try {
4
+ return await func(cmd, ctx);
5
+ }
6
+ catch (err) {
7
+ return {
8
+ kind: "fail",
9
+ reason: `${err}`
10
+ };
11
+ }
12
+ };
13
+ }
@@ -0,0 +1,109 @@
1
+ import { tryExecCommandFunction } from './util.js';
2
+ const THEMES = [
3
+ {
4
+ name: 'Animals',
5
+ words: ['CAT', 'DOG', 'BIRD', 'FISH', 'LION', 'BEAR', 'FROG', 'DUCK', 'WOLF', 'DEER'],
6
+ },
7
+ {
8
+ name: 'Fruits',
9
+ words: ['APPLE', 'MANGO', 'GRAPE', 'PEACH', 'PLUM', 'PEAR', 'LIME', 'KIWI', 'FIG', 'DATE'],
10
+ },
11
+ {
12
+ name: 'Colors',
13
+ words: ['RED', 'BLUE', 'GREEN', 'PINK', 'GOLD', 'TEAL', 'CORAL', 'TAN', 'IVORY', 'NAVY'],
14
+ },
15
+ {
16
+ name: 'Space',
17
+ words: ['STAR', 'MOON', 'SUN', 'MARS', 'EARTH', 'VENUS', 'COMET', 'ORBIT', 'NOVA', 'RING'],
18
+ },
19
+ {
20
+ name: 'Food',
21
+ words: ['PIZZA', 'TACO', 'SOUP', 'CAKE', 'RICE', 'BREAD', 'PASTA', 'SALAD', 'CHIP', 'PIE'],
22
+ },
23
+ ];
24
+ const SIZE = 15;
25
+ function makeGrid() {
26
+ return Array.from({ length: SIZE }, () => Array(SIZE).fill(''));
27
+ }
28
+ const DIRECTIONS = [
29
+ [0, 1], [1, 0], [1, 1], [1, -1],
30
+ [0, -1], [-1, 0], [-1, -1], [-1, 1],
31
+ ];
32
+ function placeWord(grid, word) {
33
+ const shuffledDirs = [...DIRECTIONS].sort(() => Math.random() - 0.5);
34
+ for (const [dr, dc] of shuffledDirs) {
35
+ for (let attempt = 0; attempt < 30; attempt++) {
36
+ const row = Math.floor(Math.random() * SIZE);
37
+ const col = Math.floor(Math.random() * SIZE);
38
+ let fits = true;
39
+ for (let i = 0; i < word.length; i++) {
40
+ const r = row + dr * i;
41
+ const c = col + dc * i;
42
+ if (r < 0 || r >= SIZE || c < 0 || c >= SIZE) {
43
+ fits = false;
44
+ break;
45
+ }
46
+ const cell = grid[r]?.[c];
47
+ if (cell && cell !== word[i]) {
48
+ fits = false;
49
+ break;
50
+ }
51
+ }
52
+ if (fits) {
53
+ for (let i = 0; i < word.length; i++) {
54
+ const r = row + dr * i;
55
+ const c = col + dc * i;
56
+ if (grid[r])
57
+ grid[r][c] = word[i];
58
+ }
59
+ return true;
60
+ }
61
+ }
62
+ }
63
+ return false;
64
+ }
65
+ const LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
66
+ function fillGrid(grid) {
67
+ for (let r = 0; r < SIZE; r++) {
68
+ for (let c = 0; c < SIZE; c++) {
69
+ if (!grid[r]?.[c]) {
70
+ if (grid[r])
71
+ grid[r][c] = LETTERS[Math.floor(Math.random() * 26)] ?? 'A';
72
+ }
73
+ }
74
+ }
75
+ }
76
+ function renderGrid(grid) {
77
+ return grid.map(row => row.join(' '));
78
+ }
79
+ async function printWordsearchWorker(_args, ctx) {
80
+ const theme = THEMES[Math.floor(Math.random() * THEMES.length)];
81
+ if (!theme)
82
+ throw new Error('No themes available');
83
+ const grid = makeGrid();
84
+ const placed = [];
85
+ for (const word of theme.words) {
86
+ if (placeWord(grid, word))
87
+ placed.push(word);
88
+ }
89
+ fillGrid(grid);
90
+ const lines = [
91
+ ...renderGrid(grid),
92
+ '',
93
+ 'Find these words:',
94
+ placed.join(' '),
95
+ ];
96
+ const job = {
97
+ urls: [],
98
+ header: `Word Search: ${theme.name}`,
99
+ lines,
100
+ };
101
+ await ctx.printJob(job);
102
+ return {
103
+ kind: "pass"
104
+ };
105
+ }
106
+ export const printWordsearch = {
107
+ aliases: ["wordsearch"],
108
+ invoke: tryExecCommandFunction(printWordsearchWorker)
109
+ };
@@ -0,0 +1,8 @@
1
+ import type { PublicRuntimeConfig, RuntimeConfig, RuntimeConfigPatch } from './types.js';
2
+ export declare function toPublicConfig(config: RuntimeConfig, configPath: string): PublicRuntimeConfig;
3
+ export declare function loadConfig(): Promise<{
4
+ config: RuntimeConfig;
5
+ configPath: string;
6
+ }>;
7
+ export declare function saveConfig(patch: RuntimeConfigPatch, current: RuntimeConfig, configPath: string): Promise<RuntimeConfig>;
8
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACR,mBAAmB,EACnB,aAAa,EACb,kBAAkB,EACrB,MAAM,YAAY,CAAC;AA4DpB,wBAAgB,cAAc,CAAC,MAAM,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,GAAG,mBAAmB,CAQ7F;AAED,wBAAsB,UAAU,IAAI,OAAO,CAAC;IAAE,MAAM,EAAE,aAAa,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC,CAoBzF;AAED,wBAAsB,UAAU,CAC5B,KAAK,EAAE,kBAAkB,EACzB,OAAO,EAAE,aAAa,EACtB,UAAU,EAAE,MAAM,GACnB,OAAO,CAAC,aAAa,CAAC,CAIxB"}
package/dist/config.js ADDED
@@ -0,0 +1,149 @@
1
+ import { readFile, writeFile } from 'fs/promises';
2
+ import { createHash } from 'crypto';
3
+ import { join } from 'path';
4
+ export const DEFAULT_CONFIG_FILE = 'windsor.config.json';
5
+ export function getConfigPath() {
6
+ if (process.env['WINDSOR_CONFIG_PATH'])
7
+ return process.env['WINDSOR_CONFIG_PATH'];
8
+ return join(process.cwd(), DEFAULT_CONFIG_FILE);
9
+ }
10
+ let _config = {
11
+ diagnosticsPort: 8080,
12
+ channels: [],
13
+ tokenUsage: [],
14
+ };
15
+ let _configPath = getConfigPath();
16
+ export function getCurrentConfig() {
17
+ return _config;
18
+ }
19
+ export function getConfigFilePath() {
20
+ return _configPath;
21
+ }
22
+ function defaultConfig() {
23
+ const envPort = Number.parseInt(process.env['DIAGNOSTICS_PORT'] ?? '8080', 10);
24
+ const diagnosticsPort = Number.isFinite(envPort) && envPort >= 1 && envPort <= 65535 ? envPort : 8080;
25
+ const result = {
26
+ diagnosticsPort,
27
+ channels: [],
28
+ tokenUsage: [],
29
+ };
30
+ const token = process.env['DISCORD_TOKEN']?.trim();
31
+ if (token)
32
+ result.discordToken = token;
33
+ const serverId = process.env['SERVER_ID']?.trim();
34
+ if (serverId)
35
+ result.serverId = serverId;
36
+ const openaiKey = process.env['OPENAI_API_KEY']?.trim();
37
+ if (openaiKey)
38
+ result.openaiKey = openaiKey;
39
+ return result;
40
+ }
41
+ function parseConfig(raw) {
42
+ if (typeof raw !== 'object' || raw === null) {
43
+ return defaultConfig();
44
+ }
45
+ const obj = raw;
46
+ const base = defaultConfig();
47
+ const result = {
48
+ diagnosticsPort: typeof obj['diagnosticsPort'] === 'number' ? obj['diagnosticsPort'] : base.diagnosticsPort,
49
+ channels: Array.isArray(obj['channels']) ? obj['channels'].flatMap(c => {
50
+ if (typeof c === 'object' && c !== null)
51
+ return [c];
52
+ return [];
53
+ }) : [],
54
+ tokenUsage: Array.isArray(obj['tokenUsage']) ? obj['tokenUsage'].flatMap(e => {
55
+ if (typeof e === 'object' && e !== null)
56
+ return [e];
57
+ return [];
58
+ }) : [],
59
+ };
60
+ if (typeof obj['discordToken'] === 'string' && obj['discordToken'].trim()) {
61
+ result.discordToken = obj['discordToken'].trim();
62
+ }
63
+ else if (base.discordToken) {
64
+ result.discordToken = base.discordToken;
65
+ }
66
+ if (typeof obj['serverId'] === 'string' && obj['serverId'].trim()) {
67
+ result.serverId = obj['serverId'].trim();
68
+ }
69
+ else if (base.serverId) {
70
+ result.serverId = base.serverId;
71
+ }
72
+ if (typeof obj['openaiKey'] === 'string' && obj['openaiKey'].trim()) {
73
+ result.openaiKey = obj['openaiKey'].trim();
74
+ }
75
+ else if (base.openaiKey) {
76
+ result.openaiKey = base.openaiKey;
77
+ }
78
+ if (typeof obj['passwordHash'] === 'string')
79
+ result.passwordHash = obj['passwordHash'];
80
+ if (typeof obj['iconCacheDir'] === 'string')
81
+ result.iconCacheDir = obj['iconCacheDir'];
82
+ if (typeof obj['printConfig'] === 'object' && obj['printConfig'] !== null) {
83
+ result.printConfig = obj['printConfig'];
84
+ }
85
+ return result;
86
+ }
87
+ export async function loadConfig() {
88
+ _configPath = getConfigPath();
89
+ try {
90
+ const raw = await readFile(_configPath, 'utf8');
91
+ _config = parseConfig(JSON.parse(raw));
92
+ return { config: _config, configPath: _configPath };
93
+ }
94
+ catch (err) {
95
+ if (err.code !== 'ENOENT') {
96
+ throw err;
97
+ }
98
+ }
99
+ _config = defaultConfig();
100
+ return { config: _config, configPath: _configPath };
101
+ }
102
+ export async function saveCurrentConfig() {
103
+ await writeFile(_configPath, `${JSON.stringify(_config, null, 2)}\n`, 'utf8');
104
+ }
105
+ export async function updateConfig(patch) {
106
+ const updated = { ..._config, ...patch };
107
+ _config = updated;
108
+ await saveCurrentConfig();
109
+ return _config;
110
+ }
111
+ export function hashPassword(password) {
112
+ return createHash('sha256').update(password).digest('hex');
113
+ }
114
+ export function checkPassword(password) {
115
+ if (!_config.passwordHash)
116
+ return true; // no password set
117
+ return hashPassword(password) === _config.passwordHash;
118
+ }
119
+ /**
120
+ * Reconcile channel configs against the current set of Discord channels.
121
+ * Match by ID first, then by name. Delete unmatched UNLESS all channels are missing (server blip).
122
+ * Mutates config.channels in place and saves.
123
+ */
124
+ export async function reconcileChannels(discordChannels) {
125
+ if (discordChannels.length === 0) {
126
+ // Server blip - don't delete anything
127
+ return;
128
+ }
129
+ const channelById = new Map(discordChannels.map(c => [c.id, c]));
130
+ const channelByName = new Map(discordChannels.map(c => [c.name, c]));
131
+ const reconciled = [];
132
+ for (const mapping of _config.channels) {
133
+ const byId = channelById.get(mapping.channelId);
134
+ if (byId) {
135
+ // ID match — update name if changed
136
+ reconciled.push({ ...mapping, channelName: byId.name });
137
+ continue;
138
+ }
139
+ const byName = channelByName.get(mapping.channelName);
140
+ if (byName) {
141
+ // Name match — update ID
142
+ reconciled.push({ ...mapping, channelId: byName.id });
143
+ continue;
144
+ }
145
+ // No match — delete this mapping
146
+ }
147
+ _config.channels = reconciled;
148
+ await saveCurrentConfig();
149
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/index.js ADDED
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env node
2
+ import { loadConfig, getCurrentConfig } from './config.js';
3
+ import { logEvent, startDiagnosticsServer, setDiscordChannels, setRestartHandler, setRefreshChannelsHandler } from './server.js';
4
+ import { createWindsorBot, checkScheduledTasks } from './bot.js';
5
+ import { ChannelType } from 'discord.js';
6
+ const config = getCurrentConfig();
7
+ startDiagnosticsServer(config.diagnosticsPort);
8
+ let bot = null;
9
+ let startInFlight = false;
10
+ async function startBot() {
11
+ if (startInFlight)
12
+ return;
13
+ const cfg = getCurrentConfig();
14
+ if (!cfg.discordToken) {
15
+ logEvent('info', 'No Discord token configured. Visit the control panel to set up.');
16
+ return;
17
+ }
18
+ startInFlight = true;
19
+ const nextBot = createWindsorBot();
20
+ try {
21
+ await nextBot.start(cfg.discordToken);
22
+ bot = nextBot;
23
+ // Register discord channels in server for the web UI
24
+ const guilds = [...nextBot.getClient().guilds.cache.values()];
25
+ const channels = [];
26
+ for (const guild of guilds) {
27
+ const fetched = await guild.channels.fetch();
28
+ for (const ch of fetched.values()) {
29
+ if (ch?.isTextBased() &&
30
+ 'name' in ch &&
31
+ ch.type !== ChannelType.GuildVoice &&
32
+ ch.type !== ChannelType.GuildStageVoice) {
33
+ channels.push({ id: ch.id, name: ch.name });
34
+ }
35
+ }
36
+ }
37
+ setDiscordChannels(channels);
38
+ logEvent('startup', 'Bot started successfully');
39
+ }
40
+ catch (err) {
41
+ logEvent('error', `Failed to start bot: ${err instanceof Error ? err.message : String(err)}`);
42
+ }
43
+ finally {
44
+ startInFlight = false;
45
+ }
46
+ }
47
+ export async function restartBot() {
48
+ logEvent('info', 'Restarting bot…');
49
+ if (bot) {
50
+ bot.destroy();
51
+ bot = null;
52
+ }
53
+ setDiscordChannels([]);
54
+ await loadConfig();
55
+ await startBot();
56
+ }
57
+ // Recurring task checker
58
+ setInterval(() => {
59
+ checkScheduledTasks();
60
+ }, 30_000);
61
+ setRestartHandler(restartBot);
62
+ async function refreshChannels() {
63
+ if (!bot) {
64
+ logEvent('info', 'Cannot refresh channels: bot not connected');
65
+ return;
66
+ }
67
+ logEvent('info', 'Refreshing Discord channels…');
68
+ const guilds = [...bot.getClient().guilds.cache.values()];
69
+ const channels = [];
70
+ for (const guild of guilds) {
71
+ const fetched = await guild.channels.fetch();
72
+ for (const ch of fetched.values()) {
73
+ if (ch?.isTextBased() &&
74
+ 'name' in ch &&
75
+ ch.type !== ChannelType.GuildVoice &&
76
+ ch.type !== ChannelType.GuildStageVoice) {
77
+ channels.push({ id: ch.id, name: ch.name });
78
+ }
79
+ }
80
+ }
81
+ setDiscordChannels(channels);
82
+ logEvent('info', `Refreshed ${channels.length} Discord channels`);
83
+ }
84
+ setRefreshChannelsHandler(refreshChannels);
85
+ await startBot();
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AACrE,OAAO,EAAE,QAAQ,EAAE,sBAAsB,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAEvE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,aAAa,EAAE,GAAG,MAAM,UAAU,EAAE,CAAC;AACjE,IAAI,aAAa,GAAG,aAAa,CAAC;AAClC,IAAI,GAAG,GAAwB,IAAI,CAAC;AACpC,IAAI,oBAAoB,GAAG,KAAK,CAAC;AAEjC,SAAS,kBAAkB;IACvB,MAAM,CAAC,kBAAkB,GAAG,aAAa,CAAC,QAAQ,CAAC;IAEnD,IAAI,aAAa,CAAC,WAAW,EAAE,CAAC;QAC5B,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,GAAG,aAAa,CAAC,WAAW,CAAC;IAC5D,CAAC;SAAM,CAAC;QACJ,OAAO,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IACvC,CAAC;AACL,CAAC;AAED,SAAS,aAAa;IAClB,IAAI,oBAAoB,IAAI,GAAG,IAAI,CAAC,aAAa,CAAC,YAAY;QAAE,OAAO;IAEvE,oBAAoB,GAAG,IAAI,CAAC;IAC5B,MAAM,OAAO,GAAG,IAAI,YAAY,CAAC,GAAG,EAAE,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;IAE/D,OAAO,CAAC,KAAK,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE;QAChD,GAAG,GAAG,OAAO,CAAC;QACd,QAAQ,CAAC,SAAS,EAAE,sBAAsB,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE;QACX,QAAQ,CAAC,OAAO,EAAE,0BAA0B,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACpG,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE;QACZ,oBAAoB,GAAG,KAAK,CAAC;IACjC,CAAC,CAAC,CAAC;AACP,CAAC;AAED,kBAAkB,EAAE,CAAC;AACrB,sBAAsB,CAClB,aAAa,CAAC,eAAe,EAC7B,GAAG,EAAE,CAAC,cAAc,CAAC,aAAa,EAAE,UAAU,CAAC,EAC/C,KAAK,EAAE,KAAK,EAAE,EAAE;IACZ,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,KAAK,EAAE,aAAa,EAAE,UAAU,CAAC,CAAC;IACnE,aAAa,GAAG,OAAO,CAAC;IACxB,kBAAkB,EAAE,CAAC;IACrB,aAAa,EAAE,CAAC;IAChB,OAAO,cAAc,CAAC,aAAa,EAAE,UAAU,CAAC,CAAC;AACrD,CAAC,CACJ,CAAC;AAEF,IAAI,CAAC,aAAa,CAAC,YAAY,EAAE,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,6EAA6E,CAAC,CAAC;AACpG,CAAC;AACD,aAAa,EAAE,CAAC"}