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
package/README.md ADDED
@@ -0,0 +1,128 @@
1
+ # Windsor: Your Friendly Household Helper Bot
2
+
3
+ You'll need:
4
+ * A Raspberry Pi (or equivalent)
5
+ * Thermal printer connected to its USB port
6
+
7
+ ## Initial Setup
8
+
9
+ ### Create a Discord Bot
10
+
11
+ <!-- Write instructions on how to create a Discord bot -->
12
+
13
+ ### Invite to Server
14
+
15
+ <!-- Write instructions on how to invite the bot to your server -->
16
+
17
+ ### Set up Raspberry Pi
18
+
19
+ <!-- Write high-level instructions on how to set up a headless Raspberry Pi -->
20
+
21
+ ### Configure Credentials
22
+
23
+ <!-- Write instructions on how to get the right kind of discord API token here -->
24
+
25
+ ## Raspbery Pi Setup
26
+
27
+ Windsor runs great on a Raspberry Pi.
28
+
29
+
30
+ ## Automatic Behaviors
31
+
32
+ Windsor will automatically convert all URLs in messages to QR codes. These always appear after the message text, regardless of where they appeared in the message. If the URL was in the middle of the message, it'll be replaced with `[link]`.
33
+
34
+ ## Channel Configuration
35
+
36
+ Windsor is primarily configured by assigning a *channel behaviors* to your Discord server's channels.
37
+ You don't need to assign a behavior for every channel; channels without a behavior won't be monitored.
38
+
39
+ The following channel behaviors are supported.
40
+
41
+ ### 🖨️ Immediate Print
42
+
43
+ In *immediate print* mode, messages posted to the channel are immediately printed off.
44
+ This is ideal for "to do" items.
45
+
46
+ <!-- TODO (human): photo of a sample -->
47
+
48
+ You can enable these settings in the configuration panel:
49
+ * **Header**: Include this text at the top of every printout.
50
+ * **Icon**: Includes an image based on the message content. Requires OpenAI key.
51
+ * **Metadata Footer**: Include footer lines with timestamp and message initiator.
52
+ * **Footer**: Include this text at the bottom of every printout.
53
+
54
+ ### 🛒 Accumulating List
55
+
56
+ In *accumulating list* mode, each printout is a list of messages since the prior printout.
57
+ This is ideal for shopping lists.
58
+
59
+ Trigger a printout of all unprinted items by posting the message "print" in the channel.
60
+
61
+ <!-- TODO (human): photo of a sample -->
62
+
63
+ You can enable these settings in the configuration panel:
64
+
65
+ * **Header**: Include this text at the top of every printout.
66
+ * **Checklist**: Include checkmark boxes next to each line
67
+ * **Metadata Footer**: Include footer lines with timestamp and message initiator
68
+ * **Footer**: Include this text at the bottom of every printout.
69
+
70
+ ### 🔄 Recurring Print
71
+
72
+ *Recurring print* lets you set up automatic recurring printouts.
73
+ These work the same way as *immediate print*, but with a repeating schedule.
74
+ This feature requires an OpenAI key.
75
+
76
+ If you have an OpenAI key, the recurrence schedule will be determined automatically based on the text of the message. For example, you can write:
77
+
78
+ > Take out the trash every other Tuesday at 7 PM
79
+
80
+ AI will understand almost any syntax, e.g. you can write messges like any of these:
81
+
82
+ > Every other Tuesday, run the dishwasher clean cycle
83
+ >
84
+ > Change out the batteries on the 3rd of the month at 2 PM
85
+ >
86
+ > Call Bob a week before his birthday (6/12)
87
+
88
+ If time of day isn't specified, the default is 8:00 AM local time.
89
+
90
+ Windsor will reply to you with its understood schedule. If you were misinterpreted, delete your message and rephrase in a new one.
91
+
92
+ ### On-Demand
93
+
94
+ In on-demand channels, only specific commands will be processed. Other messages will be ignored. See the list in this document for supported commands.
95
+
96
+ ## On-Demand Commands
97
+
98
+ Windsor includes many fun commands for printing one-off items
99
+
100
+ ### `/sudoku [kid | easy | medium | hard]`
101
+
102
+ Prints a Sudoku puzzle at the specified difficulty (defaults to `easy`).
103
+
104
+ ### `/wordsearch`
105
+
106
+ Prints a themed word search puzzle.
107
+ Themes are chosen randomly and are always kid-appropriate.
108
+
109
+ ## Local Development
110
+
111
+ ## Control panel and local config
112
+
113
+ Run the bot with:
114
+
115
+ ```bash
116
+ npm run dev
117
+ ```
118
+
119
+ Then open `http://localhost:8080` to use the web control panel.
120
+
121
+ The control panel is a Preact app served by the diagnostics server and can:
122
+
123
+ 1. Show runtime bot status and recent events.
124
+ 2. Save basic setup into a local `windsor.config.json` file (Discord token, server id, etc).
125
+
126
+ The browser app source lives in `src/web/app.tsx` and is bundled on-demand by the server using the esbuild API.
127
+
128
+ `windsor.config.json` is gitignored because it can contain secrets.
package/SPEC.md ADDED
@@ -0,0 +1,179 @@
1
+ # Windsor Specification: Implementation Guide
2
+
3
+ ## Channel Behavior Mapping
4
+
5
+ The server UI always immediately saves all config changes.
6
+
7
+ In the server config UI webpage, we see a list of channel->behavior mapping rows. Clicking on a row allows the user to see sub-configuration for that behavior (e.g. footer text). They can also delete a row. There's adjacent UI with a list of unmapped channels and list of behaviors; the user can pick one of each and then click "Add" to create the behavior mapping.
8
+
9
+ Internally, we store configuration in terms of both the channel name and ID, and match based on what we see on server startup or refresh: If the ID matches, use that and update our name. If none of the IDs match, see if a channel with the right name exists, update the ID instead. If neither exist, just delete that configuraiton entry. Be careful to not accidently nuke all config if we're encountering a server availabiliy blip - if *all* channels are missing, probably something has gone wrong, don't delete the config.
10
+
11
+ Channels may only have one behavior.
12
+
13
+ ## URL Detection and Transformation
14
+
15
+ URLs are detected as any `https://...` text; we don't need to support other protocols. Trailing punctuation is ignored.
16
+
17
+ If multiple URLs are in the same message, write `[link 1]`, `[link 2]`, etc, instead of `[link]`. Don't try to dedupe URLs. There is a maximum of 5 links per printout. If URL parsing fails, assume it wasn't a URL in the first pace. If a URL is too long to generate a printable QR code for, assume it wasn't a URL in the first place.
18
+
19
+ ## Idempotency
20
+
21
+ The bot does not require or assume 24/7 uptime. On startup you should scan prior messages and see what needs acting on. You only need to look at the last 100 messages per channel (500 for recurrences - assume we do not have more than that); messages older than this can be assumed to already be actioned.
22
+
23
+ For non-recurring items, use **reactions** to indicate that you've complied (e.g. printed or whatever) to a request. Use the ✅ reaction upon successfully fulfilling the request.
24
+
25
+ If printing fails, react with ⏸️ and post a reply that explains what went wrong.
26
+
27
+ The intended user workflow on fixing a printer is to power-cycle the Raspberry Pi, so you should attempt to re-process these messages on startup.
28
+
29
+ ## Print Layout
30
+
31
+ In general we assume an 80mm printer. Long text should be wrapped. Extremely long messages (800 characters (?) after stripping URLs) should be ignored and reacted with ⁉️.
32
+
33
+ For message content, we generally want to print at quite a large font, for readability. This should scale down for longer messages; our ideal is that we are printing something that is modestly square to rectangular (not a tiny strip, not a CVS receipt). Images should span the full width of the paper minus a small margin.
34
+
35
+ General format is:
36
+
37
+ * User-specified header (large font)
38
+ * Primary text (medium-large font, depending)
39
+ * Icon
40
+ * QR codes
41
+ * User-specified footer (medium font)
42
+ * Metadata footer (small font)
43
+
44
+ Timestamps are in local time, in the format `2026-08-17 7:34 PM`
45
+
46
+ ## Message Ingestion
47
+
48
+ Ignore user attachments, images, embeds, etc.. Only look at primary message content.
49
+ Ignore replies.
50
+ Ignore messages from your own account.
51
+
52
+ If parsing fails, react with ⁉️ and reply with something sensible related to the parsing failure that occurred.
53
+
54
+ Editing a message does not re-trigger bot activity.
55
+
56
+ ## Accumulating List Behavior
57
+
58
+ The message `print` (any casing, all spacing / punctuation ignored) triggers a print.
59
+ Print all items between this message and the prior print message (or 100 messages, whichever is shorter).
60
+ If this would yield 0 items, instead print the "previous" list (this would normally occur if the printer had run out of paper and the user is retrying).
61
+
62
+ Don't try to deduplicate items.
63
+ Ignore deleted messages in the chat.
64
+ Use the latest version of any edited message.
65
+
66
+ ## Recurring Print Behavior
67
+
68
+ We need to extract two main things from the user's message:
69
+ * The *message* part
70
+ * The *schedule* part
71
+
72
+ Use structured output using a zod schema to implement this.
73
+
74
+ Send the AI a message in this form:
75
+
76
+ > The user has asked for a recurring printout. It is currently [the current date and time]. Tell me the next time that I should do this, and what the non-schedule part of the message was (verbatim). If the user didn't specify a time of day, use 8:00 AM. Here's the user's message: [the user message]
77
+
78
+ You'll want to get back a JSON blob like this:
79
+ ```json
80
+ {
81
+ "message without schedule": "take out the trash",
82
+ "next occurrence": "2025-08-02 07:30 PM"
83
+ }
84
+ ```
85
+ Be flexible about date parsing.
86
+
87
+ Use majority-reasoning method of asking 5 times. You must get 60% consistency in the message portion and 60% consistency in the next occurrence portion (not necessarily from the same responses); if this doesn't happen, treat this as a failure to parse and react appropriately.
88
+
89
+ React to the message with the standard green checkmark and reply with:
90
+
91
+ > Got it. I will print ⟪take out the trash⟫ at 2025-08-02 07:30 PM
92
+
93
+ You'll ingest this message on re-starts to establish what scheduled tasks there are.
94
+ Enqueue a background task that checks for scheduled items maturing (check every 30s).
95
+
96
+ When a task matures, we'll of course print it as requested.
97
+ When print finishes, react to the reply with the checkmark emoji.
98
+
99
+ Then we'll return to the AI to ask for what the next occurrence is.
100
+ Note that a task might have finite occurrences, e.g. the user prompt might be "for the next five weeks, remind me at 2 PM to take my medicine on Tuesdays".
101
+
102
+ So we'll post a reply either like
103
+
104
+ > This occurrence has expired and no more prints are scheduled
105
+
106
+ *or*
107
+
108
+ > Printed at 2025-08-02 07:30 PM. The next print will be at 2025-08-09 07:30 PM.
109
+
110
+ You can thus interpret the absence of such a reply (but the presence of the checkmark) as meaning the prior run failed to reach the AI server, and needs a retry.
111
+
112
+ ## OpenAI Notes
113
+
114
+ Use `gpt-5.4-nano`, low reasoning, with structured outputs with zod v4 JSON schema for all tasks.
115
+
116
+ Keep a global rolling window of never consuming more than 200,000 tokens during any 24 hour period (something would be EXTREMELY wrong if this happens). If this happens, literally delete the AI key from your config.
117
+
118
+ ## Icon Feature
119
+
120
+ For our auto-generated icon, we'll use OpenAI API. Use `gpt-image-1-mini`, 1024x1024, auto quality, transparency on, png format, with a prompt like
121
+
122
+ > Black-on-transparent line drawing icon for the TODO item: "take out the trash". Do not produce any text. Use big, thick lines. No fine detailing.
123
+
124
+ Once you get an image back, store it somewhere sensible on disk with a name like `1837c1a3e.png` where the name is derived from a SHA of the prompt. Check this cache first before calling out to the API.
125
+
126
+ If you're trying to print an item and the AI API is not working for some reason, retry up to 3 times, then just give up and skip the icon, it's never mandatory.
127
+
128
+ ## On-Demand Commands and Interaction Model
129
+
130
+ Both `!command` and `/command` are supported.
131
+ Follow our standard idempotency model.
132
+ All users can run commands.
133
+
134
+ ## Persistence and Data Model
135
+
136
+ We have three primary data sources:
137
+ * Messages in the channel itself
138
+ * Our own messages and reactions, which indicate work we've already done
139
+ * Local config JSON file
140
+ * In-memory log. This is where we persist logging, etc - we do not need to persist logging between server restarts
141
+
142
+ ## Security and Access Control
143
+
144
+ In the server configuration page, users can optionally set a password (hash this and store it in the config file). Once this is set, the config server can only be accessed using that password; use WWW-Authenticate and ignore the username portion.
145
+
146
+ Channel security is up to Discord config; any user posting in a behavior-configured channel is assumed to be authorized to trigger whatever they're doing.
147
+
148
+ ## Printer Integration
149
+
150
+ We only support ESC/P protocol, and there must be at most one printer installed.
151
+ The server configuration page should show what printer is connected (if any), offer appropriate printer config options, and have a "print test page button" that prints a small test page demonstrating printer capabilities (fonts, images, alignments, etc).
152
+
153
+ ## Diagnostics
154
+
155
+ The server log, visible in the server config page, should show enough events to allow basic diagnostic health, e.g.
156
+ * Successful discord gateway connection
157
+ * Print success/fail
158
+
159
+ Do not include keepalive pings or other routine "non-events".
160
+
161
+ ## Testing
162
+
163
+ Write tasteful unit tests at module boundaries. Test for behavorial acceptance, not hardcoded results, and don't test things that TypeScript is already covering for us. Tests must be typechecked. Don't test things that are too much of a pain in the ass.
164
+
165
+ ## Revision notes: alpha
166
+
167
+ Apply these revisions to the spec or readme IF APPROPRIATE
168
+
169
+ * It should be more apparent in the server config whether a Discord token has been set yet. Same for OpenAI. Don't actually hide these, just always show them
170
+ * Use "server" in all user-facing config, never "guild"
171
+ * Server ID is required
172
+ * Setting the discord token and server ID should reload the channel list in the server UI
173
+ * Fix this: "(node:49701) DeprecationWarning: The ready event has been renamed to clientReady to distinguish it from the gateway READY event and will only emit under that name in v15. Please use clientReady instead."
174
+ * Add a "refresh channels" button to the server UI, next to the channel list
175
+ * Remove all "back compat" code, you don't need to be backward compatible with anything, this is an alpha project
176
+ * Command invocation should automatically trigger a log event without extra per-code command
177
+ * Don't use `class` in this project, use a function/revealing-module pattern instead
178
+ * Server config UI should have a "Restart Server" button
179
+ * Don't show voice channels or forum channels in the channel listing
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file