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,515 @@
1
+ import { defineToolModule } from "./types.js";
2
+ import { objectSchema, optionalSchema, stringSchema, } from "./schema.js";
3
+ import { jsonResult, textResult } from "./responses.js";
4
+ import { ToolError, ToolExecutionError, ToolValidationError, toolErrorResult, unknownErrorResult, } from "./errors.js";
5
+ function toRecord(details) {
6
+ if (details === undefined || details === null) {
7
+ return undefined;
8
+ }
9
+ if (typeof details === "object") {
10
+ return details;
11
+ }
12
+ return { value: details };
13
+ }
14
+ function withStructuredJson(payload, metadata) {
15
+ return jsonResult(payload, metadata ? { ...metadata, details: payload, raw: payload } : { details: payload, raw: payload });
16
+ }
17
+ const noArgsSchema = objectSchema({
18
+ description: "This tool does not require any arguments.",
19
+ properties: {},
20
+ additionalProperties: false,
21
+ });
22
+ const categorySchema = stringSchema({
23
+ description: "Configuration category reported by the Ultimate firmware (e.g. 'Audio').",
24
+ minLength: 1,
25
+ });
26
+ const itemSchema = optionalSchema(stringSchema({
27
+ description: "Specific configuration item inside the selected category (e.g. 'Volume').",
28
+ minLength: 1,
29
+ }));
30
+ const primitiveValueSchema = {
31
+ jsonSchema: {
32
+ description: "Primitive configuration value (string, number, or boolean).",
33
+ type: ["string", "number", "boolean"],
34
+ },
35
+ parse(value, path) {
36
+ const resolvedPath = path ?? "$";
37
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
38
+ return value;
39
+ }
40
+ throw new ToolValidationError("Configuration value must be a string, number, or boolean", {
41
+ path: resolvedPath,
42
+ details: { receivedType: typeof value },
43
+ });
44
+ },
45
+ };
46
+ const configSetArgsSchema = objectSchema({
47
+ description: "Arguments for updating a single configuration value.",
48
+ properties: {
49
+ category: categorySchema,
50
+ item: stringSchema({
51
+ description: "Configuration item name inside the category.",
52
+ minLength: 1,
53
+ }),
54
+ value: primitiveValueSchema,
55
+ },
56
+ required: ["category", "item", "value"],
57
+ additionalProperties: false,
58
+ });
59
+ const configGetArgsSchema = objectSchema({
60
+ description: "Arguments for reading configuration information.",
61
+ properties: {
62
+ category: categorySchema,
63
+ item: itemSchema,
64
+ },
65
+ required: ["category"],
66
+ additionalProperties: false,
67
+ });
68
+ const configBatchSchema = {
69
+ jsonSchema: {
70
+ description: "Nested object mapping categories to item/value pairs (primitive values only).",
71
+ type: "object",
72
+ additionalProperties: {
73
+ type: "object",
74
+ additionalProperties: {
75
+ type: ["string", "number", "boolean"],
76
+ },
77
+ },
78
+ },
79
+ parse(value, path) {
80
+ const resolvedPath = path ?? "$";
81
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
82
+ throw new ToolValidationError("Batch payload must be an object of categories", {
83
+ path: resolvedPath,
84
+ });
85
+ }
86
+ const input = value;
87
+ const categories = {};
88
+ for (const [category, items] of Object.entries(input)) {
89
+ if (typeof items !== "object" || items === null || Array.isArray(items)) {
90
+ throw new ToolValidationError("Each category must map to an object of item values", {
91
+ path: `${resolvedPath}.${category}`,
92
+ });
93
+ }
94
+ const parsedItems = {};
95
+ for (const [item, rawValue] of Object.entries(items)) {
96
+ const parsedValue = primitiveValueSchema.parse(rawValue, `${resolvedPath}.${category}.${item}`);
97
+ parsedItems[item] = parsedValue;
98
+ }
99
+ categories[category] = parsedItems;
100
+ }
101
+ if (Object.keys(categories).length === 0) {
102
+ throw new ToolValidationError("Batch payload must contain at least one category", {
103
+ path: resolvedPath,
104
+ });
105
+ }
106
+ return categories;
107
+ },
108
+ };
109
+ const debugWriteArgsSchema = objectSchema({
110
+ description: "Write a hex byte into the Ultimate debug register ($D7FF).",
111
+ properties: {
112
+ value: stringSchema({
113
+ description: "Hex value (00-FF) to write to the debug register.",
114
+ minLength: 1,
115
+ maxLength: 2,
116
+ pattern: /^[0-9A-Fa-f]{1,2}$/,
117
+ }),
118
+ },
119
+ required: ["value"],
120
+ additionalProperties: false,
121
+ });
122
+ export const developerModule = defineToolModule({
123
+ domain: "developer",
124
+ summary: "Configuration management, diagnostics, and helper utilities for advanced workflows.",
125
+ resources: [
126
+ "c64://context/bootstrap",
127
+ "c64://docs/index",
128
+ ],
129
+ prompts: ["memory-debug"],
130
+ defaultTags: ["developer", "config", "debug"],
131
+ workflowHints: [
132
+ "Use developer tools for firmware configuration, diagnostics, or advanced register tweaks.",
133
+ "Call out any risky operations (like flash writes) so the user understands the impact.",
134
+ ],
135
+ tools: [
136
+ {
137
+ name: "config_list",
138
+ description: "List configuration categories available on the Ultimate firmware.",
139
+ summary: "Fetches the configuration tree and returns firmware-reported categories.",
140
+ inputSchema: noArgsSchema.jsonSchema,
141
+ relatedResources: ["c64://context/bootstrap"],
142
+ tags: ["config", "list"],
143
+ workflowHints: [
144
+ "Run first to enumerate categories before reading or writing specific settings; summarise the top-level sections for the user.",
145
+ ],
146
+ async execute(args, ctx) {
147
+ try {
148
+ noArgsSchema.parse(args ?? {});
149
+ ctx.logger.info("Listing configuration categories");
150
+ const details = await ctx.client.configsList();
151
+ const payload = normalizeCategoryListing(details);
152
+ return withStructuredJson(payload, {
153
+ success: true,
154
+ categoryCount: Array.isArray(payload.categories)
155
+ ? payload.categories.length
156
+ : undefined,
157
+ });
158
+ }
159
+ catch (error) {
160
+ if (error instanceof ToolError) {
161
+ return toolErrorResult(error);
162
+ }
163
+ return unknownErrorResult(error);
164
+ }
165
+ },
166
+ },
167
+ {
168
+ name: "config_get",
169
+ description: "Read a configuration category or specific item.",
170
+ summary: "Returns firmware configuration data for the selected category (and item).",
171
+ inputSchema: configGetArgsSchema.jsonSchema,
172
+ relatedResources: ["c64://context/bootstrap"],
173
+ tags: ["config", "read"],
174
+ workflowHints: [
175
+ "Use after config_list to fetch details; restate category/item so the user can cross-check values before changes.",
176
+ ],
177
+ async execute(args, ctx) {
178
+ try {
179
+ const parsed = configGetArgsSchema.parse(args ?? {});
180
+ ctx.logger.info("Reading configuration", {
181
+ category: parsed.category,
182
+ item: parsed.item ?? null,
183
+ });
184
+ const details = await ctx.client.configGet(parsed.category, parsed.item);
185
+ return withStructuredJson({ value: details }, {
186
+ success: true,
187
+ category: parsed.category,
188
+ item: parsed.item ?? null,
189
+ });
190
+ }
191
+ catch (error) {
192
+ if (error instanceof ToolError) {
193
+ return toolErrorResult(error);
194
+ }
195
+ return unknownErrorResult(error);
196
+ }
197
+ },
198
+ },
199
+ {
200
+ name: "config_set",
201
+ description: "Set a configuration value within a category.",
202
+ summary: "Writes a primitive value to a specific configuration item via firmware APIs.",
203
+ inputSchema: configSetArgsSchema.jsonSchema,
204
+ relatedResources: ["c64://context/bootstrap"],
205
+ tags: ["config", "write"],
206
+ workflowHints: [
207
+ "Confirm the user-supplied value before writing; mention whether a reboot is required for the change to take effect.",
208
+ ],
209
+ async execute(args, ctx) {
210
+ try {
211
+ const parsed = configSetArgsSchema.parse(args ?? {});
212
+ ctx.logger.info("Setting configuration", {
213
+ category: parsed.category,
214
+ item: parsed.item,
215
+ valueType: typeof parsed.value,
216
+ });
217
+ const result = await ctx.client.configSet(parsed.category, parsed.item, String(parsed.value));
218
+ if (!result.success) {
219
+ throw new ToolExecutionError("C64 firmware reported failure while updating configuration", {
220
+ details: toRecord(result.details),
221
+ });
222
+ }
223
+ return textResult(`Configuration ${parsed.category}/${parsed.item} updated.`, {
224
+ success: true,
225
+ category: parsed.category,
226
+ item: parsed.item,
227
+ value: parsed.value,
228
+ details: toRecord(result.details) ?? null,
229
+ });
230
+ }
231
+ catch (error) {
232
+ if (error instanceof ToolError) {
233
+ return toolErrorResult(error);
234
+ }
235
+ return unknownErrorResult(error);
236
+ }
237
+ },
238
+ },
239
+ {
240
+ name: "config_batch_update",
241
+ description: "Apply multiple configuration changes in a single request.",
242
+ summary: "Sends a nested object of category/item values to the firmware batch endpoint.",
243
+ inputSchema: configBatchSchema.jsonSchema,
244
+ relatedResources: ["c64://context/bootstrap"],
245
+ tags: ["config", "write"],
246
+ workflowHints: [
247
+ "Use when multiple categories must be updated together; remind the user to review validation errors closely.",
248
+ ],
249
+ async execute(args, ctx) {
250
+ try {
251
+ const payload = configBatchSchema.parse(args ?? {});
252
+ ctx.logger.info("Batch updating configuration", {
253
+ categoryCount: Object.keys(payload).length,
254
+ });
255
+ const requestPayload = Object.fromEntries(Object.entries(payload).map(([category, entries]) => [
256
+ category,
257
+ Object.fromEntries(Object.entries(entries).map(([key, value]) => [key, String(value)])),
258
+ ]));
259
+ const result = await ctx.client.configBatchUpdate(requestPayload);
260
+ if (!result.success) {
261
+ throw new ToolExecutionError("C64 firmware reported failure during batch configuration update", {
262
+ details: toRecord(result.details),
263
+ });
264
+ }
265
+ return textResult("Configuration batch update applied.", {
266
+ success: true,
267
+ categoryCount: Object.keys(payload).length,
268
+ details: toRecord(result.details) ?? null,
269
+ });
270
+ }
271
+ catch (error) {
272
+ if (error instanceof ToolError) {
273
+ return toolErrorResult(error);
274
+ }
275
+ return unknownErrorResult(error);
276
+ }
277
+ },
278
+ },
279
+ {
280
+ name: "config_load_from_flash",
281
+ description: "Load configuration settings from flash storage.",
282
+ summary: "Restores the firmware configuration by reading the persisted flash snapshot.",
283
+ inputSchema: noArgsSchema.jsonSchema,
284
+ relatedResources: ["c64://context/bootstrap"],
285
+ tags: ["config", "flash"],
286
+ workflowHints: [
287
+ "Use when the user wants to revert to saved flash configuration; warn that unsaved RAM changes are discarded.",
288
+ ],
289
+ async execute(args, ctx) {
290
+ try {
291
+ noArgsSchema.parse(args ?? {});
292
+ ctx.logger.info("Loading configuration from flash");
293
+ const result = await ctx.client.configLoadFromFlash();
294
+ if (!result.success) {
295
+ throw new ToolExecutionError("C64 firmware reported failure while loading from flash", {
296
+ details: toRecord(result.details),
297
+ });
298
+ }
299
+ return textResult("Configuration loaded from flash.", {
300
+ success: true,
301
+ details: toRecord(result.details) ?? null,
302
+ });
303
+ }
304
+ catch (error) {
305
+ if (error instanceof ToolError) {
306
+ return toolErrorResult(error);
307
+ }
308
+ return unknownErrorResult(error);
309
+ }
310
+ },
311
+ },
312
+ {
313
+ name: "config_save_to_flash",
314
+ description: "Persist current configuration settings to flash storage.",
315
+ summary: "Saves the active configuration snapshot so it survives power cycles.",
316
+ inputSchema: noArgsSchema.jsonSchema,
317
+ relatedResources: ["c64://context/bootstrap"],
318
+ tags: ["config", "flash"],
319
+ workflowHints: [
320
+ "Run after configuration changes the user wants to persist; mention that flash writes take a moment.",
321
+ ],
322
+ async execute(args, ctx) {
323
+ try {
324
+ noArgsSchema.parse(args ?? {});
325
+ ctx.logger.info("Saving configuration to flash");
326
+ const result = await ctx.client.configSaveToFlash();
327
+ if (!result.success) {
328
+ throw new ToolExecutionError("C64 firmware reported failure while saving to flash", {
329
+ details: toRecord(result.details),
330
+ });
331
+ }
332
+ return textResult("Configuration saved to flash.", {
333
+ success: true,
334
+ details: toRecord(result.details) ?? null,
335
+ });
336
+ }
337
+ catch (error) {
338
+ if (error instanceof ToolError) {
339
+ return toolErrorResult(error);
340
+ }
341
+ return unknownErrorResult(error);
342
+ }
343
+ },
344
+ },
345
+ {
346
+ name: "config_reset_to_default",
347
+ description: "Reset configuration categories to their factory defaults.",
348
+ summary: "Instructs the firmware to discard overrides and restore default configuration values.",
349
+ inputSchema: noArgsSchema.jsonSchema,
350
+ relatedResources: ["c64://context/bootstrap"],
351
+ tags: ["config", "reset"],
352
+ workflowHints: [
353
+ "Confirm with the user before wiping settings; note that the firmware must restart to apply defaults fully.",
354
+ ],
355
+ async execute(args, ctx) {
356
+ try {
357
+ noArgsSchema.parse(args ?? {});
358
+ ctx.logger.info("Resetting configuration to defaults");
359
+ const result = await ctx.client.configResetToDefault();
360
+ if (!result.success) {
361
+ throw new ToolExecutionError("C64 firmware reported failure while resetting configuration", {
362
+ details: toRecord(result.details),
363
+ });
364
+ }
365
+ return textResult("Configuration reset to defaults.", {
366
+ success: true,
367
+ details: toRecord(result.details) ?? null,
368
+ });
369
+ }
370
+ catch (error) {
371
+ if (error instanceof ToolError) {
372
+ return toolErrorResult(error);
373
+ }
374
+ return unknownErrorResult(error);
375
+ }
376
+ },
377
+ },
378
+ {
379
+ name: "version",
380
+ description: "Retrieve Ultimate firmware and API version information.",
381
+ summary: "Calls the firmware version endpoint and returns the structured payload.",
382
+ inputSchema: noArgsSchema.jsonSchema,
383
+ relatedResources: ["c64://context/bootstrap"],
384
+ tags: ["diagnostics", "version"],
385
+ workflowHints: [
386
+ "Run when the user wonders which firmware build is active; include API and FPGA versions if available.",
387
+ ],
388
+ async execute(args, ctx) {
389
+ try {
390
+ noArgsSchema.parse(args ?? {});
391
+ ctx.logger.info("Fetching firmware version");
392
+ const details = await ctx.client.version();
393
+ return withStructuredJson(toRecord(details) ?? {}, {
394
+ success: true,
395
+ details: toRecord(details) ?? {},
396
+ });
397
+ }
398
+ catch (error) {
399
+ if (error instanceof ToolError) {
400
+ return toolErrorResult(error);
401
+ }
402
+ return unknownErrorResult(error);
403
+ }
404
+ },
405
+ },
406
+ {
407
+ name: "info",
408
+ description: "Retrieve Ultimate hardware information and status.",
409
+ summary: "Returns the raw diagnostics payload reported by the firmware info endpoint.",
410
+ inputSchema: noArgsSchema.jsonSchema,
411
+ relatedResources: ["c64://context/bootstrap"],
412
+ tags: ["diagnostics", "info"],
413
+ workflowHints: [
414
+ "Use to answer hardware capability questions; highlight serials, board revisions, or temperature if present.",
415
+ ],
416
+ async execute(args, ctx) {
417
+ try {
418
+ noArgsSchema.parse(args ?? {});
419
+ ctx.logger.info("Fetching hardware info");
420
+ const details = await ctx.client.info();
421
+ return withStructuredJson(toRecord(details) ?? {}, {
422
+ success: true,
423
+ details: toRecord(details) ?? {},
424
+ });
425
+ }
426
+ catch (error) {
427
+ if (error instanceof ToolError) {
428
+ return toolErrorResult(error);
429
+ }
430
+ return unknownErrorResult(error);
431
+ }
432
+ },
433
+ },
434
+ {
435
+ name: "debugreg_read",
436
+ description: "Read the Ultimate debug register ($D7FF).",
437
+ summary: "Returns the current hex value stored in the debug register.",
438
+ inputSchema: noArgsSchema.jsonSchema,
439
+ relatedResources: ["c64://context/bootstrap"],
440
+ tags: ["debug"],
441
+ workflowHints: [
442
+ "Read the register when debugging DMA or cartridge interactions; translate the hex value into context for the user.",
443
+ ],
444
+ async execute(args, ctx) {
445
+ try {
446
+ noArgsSchema.parse(args ?? {});
447
+ ctx.logger.info("Reading debug register");
448
+ const result = await ctx.client.debugregRead();
449
+ if (!result.success) {
450
+ throw new ToolExecutionError("C64 firmware reported failure while reading debug register", {
451
+ details: toRecord(result.details),
452
+ });
453
+ }
454
+ const details = toRecord(result.details) ?? {};
455
+ const value = typeof result.value === "string" ? result.value.toUpperCase() : null;
456
+ return textResult(`Debug register value: ${value ?? "(unknown)"}.`, {
457
+ success: true,
458
+ value,
459
+ details,
460
+ });
461
+ }
462
+ catch (error) {
463
+ if (error instanceof ToolError) {
464
+ return toolErrorResult(error);
465
+ }
466
+ return unknownErrorResult(error);
467
+ }
468
+ },
469
+ },
470
+ {
471
+ name: "debugreg_write",
472
+ description: "Write a value into the Ultimate debug register ($D7FF).",
473
+ summary: "Validates the hex input and forwards it to the firmware.",
474
+ inputSchema: debugWriteArgsSchema.jsonSchema,
475
+ relatedResources: ["c64://context/bootstrap"],
476
+ tags: ["debug"],
477
+ workflowHints: [
478
+ "Confirm with the user before writing arbitrary values; mention side effects on cartridge or DMA behaviour.",
479
+ ],
480
+ async execute(args, ctx) {
481
+ try {
482
+ const parsed = debugWriteArgsSchema.parse(args ?? {});
483
+ const value = parsed.value.toUpperCase();
484
+ ctx.logger.info("Writing debug register", { value });
485
+ const result = await ctx.client.debugregWrite(value);
486
+ if (!result.success) {
487
+ throw new ToolExecutionError("C64 firmware reported failure while writing debug register", {
488
+ details: toRecord(result.details),
489
+ });
490
+ }
491
+ return textResult(`Debug register written with ${value}.`, {
492
+ success: true,
493
+ value,
494
+ details: toRecord(result.details) ?? null,
495
+ });
496
+ }
497
+ catch (error) {
498
+ if (error instanceof ToolError) {
499
+ return toolErrorResult(error);
500
+ }
501
+ return unknownErrorResult(error);
502
+ }
503
+ },
504
+ },
505
+ ],
506
+ });
507
+ function normalizeCategoryListing(details) {
508
+ if (details &&
509
+ typeof details === "object" &&
510
+ !Array.isArray(details) &&
511
+ Object.prototype.hasOwnProperty.call(details, "categories")) {
512
+ return details;
513
+ }
514
+ return { categories: details };
515
+ }
@@ -0,0 +1,59 @@
1
+ import { textResult } from "./responses.js";
2
+ export class ToolError extends Error {
3
+ constructor(message, kind, options) {
4
+ super(message);
5
+ this.name = this.constructor.name;
6
+ this.kind = kind;
7
+ this.path = options?.path;
8
+ this.code = options?.code;
9
+ this.details = options?.details;
10
+ if (options?.cause !== undefined) {
11
+ this.cause = options.cause;
12
+ }
13
+ }
14
+ }
15
+ export class ToolValidationError extends ToolError {
16
+ constructor(message, options) {
17
+ super(message, "validation", options);
18
+ }
19
+ }
20
+ export class ToolExecutionError extends ToolError {
21
+ constructor(message, options) {
22
+ super(message, "execution", options);
23
+ }
24
+ }
25
+ export class ToolUnsupportedPlatformError extends ToolExecutionError {
26
+ constructor(tool, platform, supported) {
27
+ super(`Tool ${tool} is not available on platform ${platform}`, {
28
+ code: "unsupported_platform",
29
+ details: {
30
+ tool,
31
+ platform,
32
+ supported,
33
+ },
34
+ });
35
+ this.tool = tool;
36
+ this.platform = platform;
37
+ this.supported = supported;
38
+ }
39
+ }
40
+ export function toolErrorResult(error) {
41
+ const metadata = {
42
+ kind: error.kind,
43
+ ...(error.path !== undefined ? { path: error.path } : {}),
44
+ ...(error.code !== undefined ? { code: error.code } : {}),
45
+ ...(error.details !== undefined ? { details: error.details } : {}),
46
+ };
47
+ const message = error.path ? `${error.message} (at ${error.path})` : error.message;
48
+ const base = textResult(message, { error: metadata });
49
+ return { ...base, isError: true };
50
+ }
51
+ export function unknownErrorResult(error) {
52
+ if (error instanceof ToolError) {
53
+ return toolErrorResult(error);
54
+ }
55
+ const metadata = { kind: "unknown" };
56
+ const message = error instanceof Error ? error.message : String(error);
57
+ const base = textResult(message, { error: metadata });
58
+ return { ...base, isError: true };
59
+ }