c64bridge 0.5.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 (138) hide show
  1. package/.github/prompts/README.md +6 -0
  2. package/.github/prompts/assembly-routine-session.prompt.md +13 -0
  3. package/.github/prompts/basic-program-session.prompt.md +13 -0
  4. package/.github/prompts/drive-management-session.prompt.md +13 -0
  5. package/.github/prompts/petscii-art-session.prompt.md +13 -0
  6. package/.github/prompts/play-classic-game.prompt.md +13 -0
  7. package/.github/prompts/sid-music-session.prompt.md +13 -0
  8. package/AGENTS.md +173 -0
  9. package/LICENSE +339 -0
  10. package/README.md +582 -0
  11. package/data/README.md +16 -0
  12. package/data/api/basic-api-spec.md +154 -0
  13. package/data/api/kernal-api-spec.md +68 -0
  14. package/data/assembly/assembly-spec.md +196 -0
  15. package/data/assembly/examples/effects/sinewave.asm +5 -0
  16. package/data/assembly/examples/graphics/sprite/mover.asm +15 -0
  17. package/data/assembly/examples/sid-file-structure.md +8 -0
  18. package/data/assembly/examples/sid-overview.md +11 -0
  19. package/data/audio/examples/Biosphere.sid +0 -0
  20. package/data/audio/sid-file-structure.md +91 -0
  21. package/data/audio/sid-programming-best-practices.md +198 -0
  22. package/data/audio/sid-spec.md +166 -0
  23. package/data/audio/sidwave.md +131 -0
  24. package/data/audio/sidwave.schema.json +64 -0
  25. package/data/basic/basic-spec.md +230 -0
  26. package/data/basic/examples/audio/alle-meine-entchen-expressive.bas +41 -0
  27. package/data/basic/examples/audio/c-major-scale-pleasant.bas +29 -0
  28. package/data/basic/examples/io/joystick.bas +5 -0
  29. package/data/basic/examples/video/bounce.bas +8 -0
  30. package/data/basic/examples/video/entchen-petscii.bas +16 -0
  31. package/data/basic/examples/video/hello-world.bas +4 -0
  32. package/data/basic/examples/video/wave.bas +7 -0
  33. package/data/context/bootstrap.md +148 -0
  34. package/data/context/chat.md +36 -0
  35. package/data/drive/drive-spec.md +215 -0
  36. package/data/embeddings_asm.json +1 -0
  37. package/data/embeddings_basic.json +1 -0
  38. package/data/embeddings_hardware.json +1 -0
  39. package/data/embeddings_mixed.json +1 -0
  40. package/data/embeddings_other.json +1 -0
  41. package/data/io/cia-spec.md +115 -0
  42. package/data/io/io-spec.md +124 -0
  43. package/data/memory/kernal-memory-map.md +227 -0
  44. package/data/memory/low-memory-map.md +133 -0
  45. package/data/memory/memory-map.md +186 -0
  46. package/data/printer/printer-commodore-bitmap.md +31 -0
  47. package/data/printer/printer-commodore.md +28 -0
  48. package/data/printer/printer-epson-bitmap.md +29 -0
  49. package/data/printer/printer-epson.md +30 -0
  50. package/data/printer/printer-prompts.md +29 -0
  51. package/data/printer/printer-spec.md +180 -0
  52. package/data/video/character-set.csv +257 -0
  53. package/data/video/vic-spec.md +276 -0
  54. package/dist/assemblyConverter.js +1299 -0
  55. package/dist/audio/record_and_analyze_audio.js +292 -0
  56. package/dist/basicConverter.js +193 -0
  57. package/dist/bootstrap/stdio-logger.js +32 -0
  58. package/dist/c64Client.js +1052 -0
  59. package/dist/chargen.js +197 -0
  60. package/dist/config.js +142 -0
  61. package/dist/context.js +157 -0
  62. package/dist/device.js +413 -0
  63. package/dist/index.js +7 -0
  64. package/dist/knowledge.js +454 -0
  65. package/dist/logger.js +182 -0
  66. package/dist/loggingHttpClient.js +88 -0
  67. package/dist/mcp-server.js +426 -0
  68. package/dist/petscii.js +135 -0
  69. package/dist/petsciiArt.js +356 -0
  70. package/dist/platform.js +82 -0
  71. package/dist/prompts/registry.js +560 -0
  72. package/dist/rag/discover.config.json +41 -0
  73. package/dist/rag/discover.js +401 -0
  74. package/dist/rag/embeddings.js +60 -0
  75. package/dist/rag/externalFetcher.js +934 -0
  76. package/dist/rag/indexer.js +770 -0
  77. package/dist/rag/init.js +139 -0
  78. package/dist/rag/knowledgeIndex.js +361 -0
  79. package/dist/rag/loggingRetriever.js +46 -0
  80. package/dist/rag/rateLimiter.js +127 -0
  81. package/dist/rag/retriever.js +74 -0
  82. package/dist/rag/sources.csv +31 -0
  83. package/dist/rag/types.js +5 -0
  84. package/dist/rag/urlUtils.js +38 -0
  85. package/dist/sidplayRunner.js +240 -0
  86. package/dist/sidwave.js +101 -0
  87. package/dist/sidwaveCompiler.js +329 -0
  88. package/dist/tools/audio.js +944 -0
  89. package/dist/tools/developer.js +515 -0
  90. package/dist/tools/errors.js +59 -0
  91. package/dist/tools/graphics.js +469 -0
  92. package/dist/tools/machineControl.js +267 -0
  93. package/dist/tools/memory.js +224 -0
  94. package/dist/tools/printer.js +482 -0
  95. package/dist/tools/programRunners.js +315 -0
  96. package/dist/tools/rag.js +153 -0
  97. package/dist/tools/registry.js +58 -0
  98. package/dist/tools/responses.js +36 -0
  99. package/dist/tools/schema.js +304 -0
  100. package/dist/tools/storage.js +740 -0
  101. package/dist/tools/streaming.js +132 -0
  102. package/dist/tools/types.js +99 -0
  103. package/dist/viceRunner.js +239 -0
  104. package/doc/MCP_SETUP.md +153 -0
  105. package/doc/README.md +7 -0
  106. package/doc/developer.md +312 -0
  107. package/doc/img/logo.png +0 -0
  108. package/doc/img/prompts/duck_petscii.png +0 -0
  109. package/doc/img/prompts/duck_song.png +0 -0
  110. package/doc/img/vscode/vscode-copilot-c64-chat-mode.png +0 -0
  111. package/doc/img/vscode/vscode-copilot-hello-world.png +0 -0
  112. package/doc/prompts/README.md +96 -0
  113. package/doc/prompts/user-prompt-ideas.md +147 -0
  114. package/doc/rest/c64-openapi.yaml +979 -0
  115. package/doc/rest/c64-rest-api.md +106 -0
  116. package/doc/troubleshooting-mcp.md +177 -0
  117. package/generated/c64/index.js +857 -0
  118. package/mcp.json +106 -0
  119. package/package.json +79 -0
  120. package/scripts/c64-cli.mjs +142 -0
  121. package/scripts/c64-tool.sh +83 -0
  122. package/scripts/check-package.mjs +89 -0
  123. package/scripts/cli.js +61 -0
  124. package/scripts/fetch-external-sources.mjs +67 -0
  125. package/scripts/generate-changelog.mjs +131 -0
  126. package/scripts/postbuild.mjs +101 -0
  127. package/scripts/prepare-release.mjs +58 -0
  128. package/scripts/rag-rebuild.mjs +53 -0
  129. package/scripts/register-ts-node.mjs +6 -0
  130. package/scripts/run-mcp-check.sh +104 -0
  131. package/scripts/run-tests.mjs +195 -0
  132. package/scripts/sid2wav.sh +94 -0
  133. package/scripts/smoke-run.sh +65 -0
  134. package/scripts/start-mock-c64.mjs +51 -0
  135. package/scripts/start-mock.mjs +31 -0
  136. package/scripts/start.mjs +72 -0
  137. package/scripts/update-readme.ts +164 -0
  138. package/scripts/verify-package.mjs +147 -0
@@ -0,0 +1,482 @@
1
+ import { defineToolModule } from "./types.js";
2
+ import { arraySchema, booleanSchema, numberSchema, objectSchema, optionalSchema, stringSchema, } from "./schema.js";
3
+ import { textResult } from "./responses.js";
4
+ import { ToolExecutionError, ToolValidationError, toolErrorResult, unknownErrorResult, } from "./errors.js";
5
+ import { ToolError } from "./errors.js";
6
+ function toRecord(details) {
7
+ if (details && typeof details === "object") {
8
+ return details;
9
+ }
10
+ return undefined;
11
+ }
12
+ function normaliseFailure(details) {
13
+ if (details === undefined || details === null) {
14
+ return undefined;
15
+ }
16
+ if (typeof details === "object") {
17
+ return details;
18
+ }
19
+ return { value: details };
20
+ }
21
+ const secondaryAddressSchema = {
22
+ jsonSchema: {
23
+ description: "Secondary address for device 4 (0 = upper/graphics, 7 = lowercase).",
24
+ type: "integer",
25
+ enum: [0, 7],
26
+ },
27
+ parse(value, path) {
28
+ const resolvedPath = path ?? "$";
29
+ if (typeof value !== "number" || !Number.isInteger(value)) {
30
+ throw new ToolValidationError("Secondary address must be an integer", { path: resolvedPath });
31
+ }
32
+ if (value !== 0 && value !== 7) {
33
+ throw new ToolValidationError("Secondary address must be 0 or 7", { path: resolvedPath });
34
+ }
35
+ return value;
36
+ },
37
+ };
38
+ const printerTargetSchema = {
39
+ jsonSchema: {
40
+ description: "Printer target: Commodore MPS (PETSCII) or Epson FX (ESC/P).",
41
+ type: "string",
42
+ enum: ["commodore", "epson"],
43
+ default: "commodore",
44
+ },
45
+ parse(value, path) {
46
+ if (value === undefined || value === null) {
47
+ return "commodore";
48
+ }
49
+ const resolvedPath = path ?? "$";
50
+ if (typeof value !== "string") {
51
+ throw new ToolValidationError("Printer target must be a string", { path: resolvedPath });
52
+ }
53
+ const lowered = value.toLowerCase();
54
+ if (lowered !== "commodore" && lowered !== "epson") {
55
+ throw new ToolValidationError("Printer target must be 'commodore' or 'epson'", { path: resolvedPath });
56
+ }
57
+ return lowered;
58
+ },
59
+ };
60
+ const printTextArgsSchema = objectSchema({
61
+ description: "Generate a BASIC program that prints text to device 4 and executes it on the C64.",
62
+ properties: {
63
+ text: stringSchema({
64
+ description: "Text to print on the connected printer.",
65
+ minLength: 1,
66
+ }),
67
+ target: printerTargetSchema,
68
+ secondaryAddress: optionalSchema(secondaryAddressSchema),
69
+ formFeed: booleanSchema({
70
+ description: "Emit a form-feed (CHR$(12)) at the end of the job.",
71
+ default: false,
72
+ }),
73
+ },
74
+ required: ["text"],
75
+ additionalProperties: false,
76
+ });
77
+ const commodoreBitmapArgsSchema = objectSchema({
78
+ description: "Print a Commodore MPS bit-image row via generated BASIC.",
79
+ properties: {
80
+ columns: arraySchema(numberSchema({
81
+ description: "Column byte (0-255).",
82
+ integer: true,
83
+ minimum: 0,
84
+ maximum: 255,
85
+ }), {
86
+ description: "Sequence of bitmap columns.",
87
+ minItems: 1,
88
+ }),
89
+ repeats: optionalSchema(numberSchema({
90
+ description: "Number of times to repeat the row (1-255).",
91
+ integer: true,
92
+ minimum: 1,
93
+ maximum: 255,
94
+ })),
95
+ useSubRepeat: optionalSchema(numberSchema({
96
+ description: "Use BIM SUB to repeat the next byte this many times (1-255).",
97
+ integer: true,
98
+ minimum: 1,
99
+ maximum: 255,
100
+ })),
101
+ secondaryAddress: optionalSchema(secondaryAddressSchema),
102
+ ensureMsb: booleanSchema({
103
+ description: "Ensure the MSB of each byte is set (Commodore printers expect bit7=1).",
104
+ default: true,
105
+ }),
106
+ },
107
+ required: ["columns"],
108
+ additionalProperties: false,
109
+ });
110
+ const epsonModeSchema = {
111
+ jsonSchema: {
112
+ description: "ESC/P graphics mode.",
113
+ type: "string",
114
+ enum: ["K", "L", "Y", "Z", "*"],
115
+ default: "K",
116
+ },
117
+ parse(value, path) {
118
+ if (value === undefined || value === null) {
119
+ return "K";
120
+ }
121
+ const resolvedPath = path ?? "$";
122
+ if (typeof value !== "string") {
123
+ throw new ToolValidationError("ESC/P mode must be a string", { path: resolvedPath });
124
+ }
125
+ const upper = value.toUpperCase();
126
+ if (!["K", "L", "Y", "Z", "*"].includes(upper)) {
127
+ throw new ToolValidationError("Invalid ESC/P mode", { path: resolvedPath });
128
+ }
129
+ return upper;
130
+ },
131
+ };
132
+ const commodoreDllAttributeSchema = {
133
+ jsonSchema: {
134
+ description: "Attribute flag (0=normal, 1=graphics).",
135
+ type: "integer",
136
+ enum: [0, 1],
137
+ },
138
+ parse(value, path) {
139
+ const resolvedPath = path ?? "$";
140
+ if (typeof value !== "number" || !Number.isInteger(value)) {
141
+ throw new ToolValidationError("Attribute must be 0 or 1", { path: resolvedPath });
142
+ }
143
+ if (value !== 0 && value !== 1) {
144
+ throw new ToolValidationError("Attribute must be 0 or 1", { path: resolvedPath });
145
+ }
146
+ return value;
147
+ },
148
+ };
149
+ const epsonBitmapArgsSchema = objectSchema({
150
+ description: "Print an Epson FX bitmap row using ESC/P commands.",
151
+ properties: {
152
+ columns: arraySchema(numberSchema({
153
+ description: "Column byte (0-255).",
154
+ integer: true,
155
+ minimum: 0,
156
+ maximum: 255,
157
+ }), {
158
+ description: "Sequence of bitmap columns.",
159
+ minItems: 1,
160
+ }),
161
+ mode: epsonModeSchema,
162
+ density: optionalSchema(numberSchema({
163
+ description: "Density parameter used with '*' mode (0-3).",
164
+ integer: true,
165
+ minimum: 0,
166
+ maximum: 3,
167
+ })),
168
+ repeats: optionalSchema(numberSchema({
169
+ description: "Number of times to repeat the row (1-255).",
170
+ integer: true,
171
+ minimum: 1,
172
+ maximum: 255,
173
+ })),
174
+ timesPerLine: optionalSchema(numberSchema({
175
+ description: "Number of times to print the row per line (1-10).",
176
+ integer: true,
177
+ minimum: 1,
178
+ maximum: 10,
179
+ })),
180
+ },
181
+ required: ["columns"],
182
+ additionalProperties: false,
183
+ });
184
+ const commodoreDllCharSchema = objectSchema({
185
+ description: "Commodore MPS DLL character definition (11 columns).",
186
+ properties: {
187
+ a: optionalSchema(commodoreDllAttributeSchema),
188
+ columns: arraySchema(numberSchema({
189
+ description: "Column byte (0-255).",
190
+ integer: true,
191
+ minimum: 0,
192
+ maximum: 255,
193
+ }), {
194
+ description: "11 column bytes describing the character.",
195
+ minItems: 11,
196
+ maxItems: 11,
197
+ }),
198
+ },
199
+ required: ["columns"],
200
+ additionalProperties: false,
201
+ });
202
+ const commodoreDllArgsSchema = objectSchema({
203
+ description: "Define custom character bitmaps on Commodore MPS printers via DLL mode.",
204
+ properties: {
205
+ firstChar: numberSchema({
206
+ description: "PETSCII code of first character to redefine (33-126).",
207
+ integer: true,
208
+ minimum: 33,
209
+ maximum: 126,
210
+ }),
211
+ chars: arraySchema(commodoreDllCharSchema, {
212
+ description: "Character definitions to upload (1-32).",
213
+ minItems: 1,
214
+ maxItems: 32,
215
+ }),
216
+ secondaryAddress: optionalSchema(secondaryAddressSchema),
217
+ },
218
+ required: ["firstChar", "chars"],
219
+ additionalProperties: false,
220
+ });
221
+ function validateEpsonDensity(args) {
222
+ if (args.mode === "*" && args.density === undefined) {
223
+ throw new ToolValidationError("density is required when mode '*' is selected", { path: "$.density" });
224
+ }
225
+ }
226
+ export const printerModule = defineToolModule({
227
+ domain: "printer",
228
+ summary: "Printer workflow helpers for Commodore MPS and Epson FX devices, including prompt templates.",
229
+ resources: [
230
+ "c64://specs/printer",
231
+ "c64://docs/printer/guide",
232
+ "c64://docs/printer/commodore-text",
233
+ "c64://docs/printer/commodore-bitmap",
234
+ "c64://docs/printer/epson-text",
235
+ "c64://docs/printer/epson-bitmap",
236
+ "c64://docs/printer/prompts",
237
+ ],
238
+ prompts: ["printer-job"],
239
+ defaultTags: ["printer"],
240
+ workflowHints: [
241
+ "Reach for printer tools when the user references device 4, hardcopy output, or specific printer models.",
242
+ "Clarify which workflow (Commodore vs Epson) you chose so the user can prepare matching paper or ribbons.",
243
+ ],
244
+ tools: [
245
+ {
246
+ name: "print_text",
247
+ description: "Print text on device 4 using Commodore or Epson workflows. See c64://docs/printer/guide.",
248
+ summary: "Generates printer BASIC, opens device 4, prints text, and optionally emits a form feed.",
249
+ inputSchema: printTextArgsSchema.jsonSchema,
250
+ relatedResources: ["c64://docs/printer/guide", "c64://specs/printer"],
251
+ relatedPrompts: ["printer-job"],
252
+ tags: ["text"],
253
+ prerequisites: ["upload_and_run_basic"],
254
+ examples: [
255
+ {
256
+ name: "Print line",
257
+ description: "Print via Commodore device 4",
258
+ arguments: { text: "HELLO", target: "commodore", formFeed: true },
259
+ },
260
+ ],
261
+ workflowHints: [
262
+ "Invoke when the user wants BASIC-generated printer output; state which target (Commodore or Epson) you selected.",
263
+ "Mention whether you appended a form feed so they know if the page advanced.",
264
+ ],
265
+ async execute(args, ctx) {
266
+ try {
267
+ const parsed = printTextArgsSchema.parse(args ?? {});
268
+ ctx.logger.info("Printing text via BASIC program", {
269
+ target: parsed.target,
270
+ textLength: parsed.text.length,
271
+ secondaryAddress: parsed.secondaryAddress,
272
+ formFeed: parsed.formFeed,
273
+ });
274
+ const result = await ctx.client.printTextOnPrinterAndRun({
275
+ text: parsed.text,
276
+ target: parsed.target,
277
+ secondaryAddress: parsed.secondaryAddress,
278
+ formFeed: parsed.formFeed,
279
+ });
280
+ if (!result.success) {
281
+ throw new ToolExecutionError("C64 firmware reported failure while printing text", {
282
+ details: normaliseFailure(result.details),
283
+ });
284
+ }
285
+ return textResult("Printer job completed successfully.", {
286
+ success: true,
287
+ target: parsed.target,
288
+ textLength: parsed.text.length,
289
+ secondaryAddress: parsed.secondaryAddress ?? null,
290
+ formFeed: parsed.formFeed,
291
+ details: toRecord(result.details) ?? null,
292
+ });
293
+ }
294
+ catch (error) {
295
+ if (error instanceof ToolError) {
296
+ return toolErrorResult(error);
297
+ }
298
+ return unknownErrorResult(error);
299
+ }
300
+ },
301
+ },
302
+ {
303
+ name: "print_bitmap_commodore",
304
+ description: "Print a Commodore MPS bit-image row using BIM BASIC helpers.",
305
+ summary: "Generates BASIC that emits Commodore BIM commands for the provided bitmap columns.",
306
+ inputSchema: commodoreBitmapArgsSchema.jsonSchema,
307
+ relatedResources: ["c64://docs/printer/commodore-bitmap"],
308
+ relatedPrompts: ["printer-job"],
309
+ tags: ["bitmap", "commodore"],
310
+ prerequisites: ["upload_and_run_basic"],
311
+ examples: [
312
+ {
313
+ name: "Print row",
314
+ description: "Commodore bitmap columns",
315
+ arguments: { columns: [255, 0, 255], repeats: 2 },
316
+ },
317
+ ],
318
+ workflowHints: [
319
+ "Use for Commodore MPS graphics rows; highlight column count and repeats in your response.",
320
+ "Suggest running define_printer_chars first if the user needs custom glyphs alongside bitmaps.",
321
+ ],
322
+ async execute(args, ctx) {
323
+ try {
324
+ const parsed = commodoreBitmapArgsSchema.parse(args ?? {});
325
+ ctx.logger.info("Printing Commodore bitmap row", {
326
+ columnCount: parsed.columns.length,
327
+ repeats: parsed.repeats,
328
+ useSubRepeat: parsed.useSubRepeat,
329
+ secondaryAddress: parsed.secondaryAddress,
330
+ ensureMsb: parsed.ensureMsb,
331
+ });
332
+ const result = await ctx.client.printBitmapOnCommodoreAndRun({
333
+ columns: Array.from(parsed.columns, (value) => value & 0xff),
334
+ repeats: parsed.repeats,
335
+ useSubRepeat: parsed.useSubRepeat,
336
+ secondaryAddress: parsed.secondaryAddress,
337
+ ensureMsb: parsed.ensureMsb,
338
+ });
339
+ if (!result.success) {
340
+ throw new ToolExecutionError("C64 firmware reported failure while printing Commodore bitmap", {
341
+ details: normaliseFailure(result.details),
342
+ });
343
+ }
344
+ return textResult("Commodore bitmap row printed successfully.", {
345
+ success: true,
346
+ columnCount: parsed.columns.length,
347
+ repeats: parsed.repeats ?? 1,
348
+ useSubRepeat: parsed.useSubRepeat ?? null,
349
+ secondaryAddress: parsed.secondaryAddress ?? null,
350
+ ensureMsb: parsed.ensureMsb,
351
+ details: toRecord(result.details) ?? null,
352
+ });
353
+ }
354
+ catch (error) {
355
+ if (error instanceof ToolError) {
356
+ return toolErrorResult(error);
357
+ }
358
+ return unknownErrorResult(error);
359
+ }
360
+ },
361
+ },
362
+ {
363
+ name: "print_bitmap_epson",
364
+ description: "Print an Epson FX bit-image row using ESC/P commands.",
365
+ summary: "Generates BASIC that emits ESC/P graphics commands for Epson printers.",
366
+ inputSchema: epsonBitmapArgsSchema.jsonSchema,
367
+ relatedResources: ["c64://docs/printer/epson-bitmap"],
368
+ relatedPrompts: ["printer-job"],
369
+ tags: ["bitmap", "epson"],
370
+ prerequisites: ["upload_and_run_basic"],
371
+ examples: [
372
+ {
373
+ name: "Epson row",
374
+ description: "ESC/P graphics",
375
+ arguments: { columns: [170, 85, 170], mode: "K", repeats: 1 },
376
+ },
377
+ ],
378
+ workflowHints: [
379
+ "Pick this for Epson FX graphics; confirm ESC/P mode and density so the user can match expectations.",
380
+ "Warn if the bitmap is very wide and recommend batching columns to avoid memory limits.",
381
+ ],
382
+ async execute(args, ctx) {
383
+ try {
384
+ const parsed = epsonBitmapArgsSchema.parse(args ?? {});
385
+ validateEpsonDensity(parsed);
386
+ ctx.logger.info("Printing Epson bitmap row", {
387
+ columnCount: parsed.columns.length,
388
+ mode: parsed.mode,
389
+ density: parsed.density,
390
+ repeats: parsed.repeats,
391
+ timesPerLine: parsed.timesPerLine,
392
+ });
393
+ const result = await ctx.client.printBitmapOnEpsonAndRun({
394
+ columns: Array.from(parsed.columns, (value) => value & 0xff),
395
+ mode: parsed.mode,
396
+ density: parsed.density,
397
+ repeats: parsed.repeats,
398
+ timesPerLine: parsed.timesPerLine,
399
+ });
400
+ if (!result.success) {
401
+ throw new ToolExecutionError("C64 firmware reported failure while printing Epson bitmap", {
402
+ details: normaliseFailure(result.details),
403
+ });
404
+ }
405
+ return textResult("Epson bitmap row printed successfully.", {
406
+ success: true,
407
+ columnCount: parsed.columns.length,
408
+ mode: parsed.mode,
409
+ density: parsed.density ?? null,
410
+ repeats: parsed.repeats ?? 1,
411
+ timesPerLine: parsed.timesPerLine ?? 1,
412
+ details: toRecord(result.details) ?? null,
413
+ });
414
+ }
415
+ catch (error) {
416
+ if (error instanceof ToolError) {
417
+ return toolErrorResult(error);
418
+ }
419
+ return unknownErrorResult(error);
420
+ }
421
+ },
422
+ },
423
+ {
424
+ name: "define_printer_chars",
425
+ description: "Define custom characters on Commodore MPS printers using DLL mode.",
426
+ summary: "Uploads custom glyph data (11 columns each) for device 4 DLL mode.",
427
+ inputSchema: commodoreDllArgsSchema.jsonSchema,
428
+ relatedResources: ["c64://docs/printer/commodore-bitmap"],
429
+ relatedPrompts: ["printer-job"],
430
+ tags: ["dll", "commodore"],
431
+ prerequisites: ["upload_and_run_basic"],
432
+ examples: [
433
+ {
434
+ name: "Define glyphs",
435
+ description: "Two custom chars from columns",
436
+ arguments: { firstChar: 65, chars: [{ columns: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }, { columns: [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255] }] },
437
+ },
438
+ ],
439
+ workflowHints: [
440
+ "Use to preload custom glyphs; remind the user about firstChar offsets and max 32 characters per call.",
441
+ "Suggest printing a short sample afterwards so they can verify the new character set.",
442
+ ],
443
+ async execute(args, ctx) {
444
+ try {
445
+ const parsed = commodoreDllArgsSchema.parse(args ?? {});
446
+ ctx.logger.info("Defining Commodore DLL characters", {
447
+ firstChar: parsed.firstChar,
448
+ count: parsed.chars.length,
449
+ secondaryAddress: parsed.secondaryAddress,
450
+ });
451
+ const chars = parsed.chars.map((char) => ({
452
+ a: char.a,
453
+ columns: Array.from(char.columns, (value) => value & 0xff),
454
+ }));
455
+ const result = await ctx.client.defineCustomCharsOnCommodoreAndRun({
456
+ firstChar: parsed.firstChar,
457
+ chars,
458
+ secondaryAddress: parsed.secondaryAddress,
459
+ });
460
+ if (!result.success) {
461
+ throw new ToolExecutionError("C64 firmware reported failure while defining DLL characters", {
462
+ details: normaliseFailure(result.details),
463
+ });
464
+ }
465
+ return textResult("Custom printer characters defined successfully.", {
466
+ success: true,
467
+ firstChar: parsed.firstChar,
468
+ count: parsed.chars.length,
469
+ secondaryAddress: parsed.secondaryAddress ?? null,
470
+ details: toRecord(result.details) ?? null,
471
+ });
472
+ }
473
+ catch (error) {
474
+ if (error instanceof ToolError) {
475
+ return toolErrorResult(error);
476
+ }
477
+ return unknownErrorResult(error);
478
+ }
479
+ },
480
+ },
481
+ ],
482
+ });