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,740 @@
1
+ import { defineToolModule } from "./types.js";
2
+ import { numberSchema, objectSchema, optionalSchema, stringSchema, } from "./schema.js";
3
+ import { textResult } from "./responses.js";
4
+ import { ToolError, ToolExecutionError, toolErrorResult, unknownErrorResult, } from "./errors.js";
5
+ function createNoArgsSchema(description) {
6
+ return objectSchema({
7
+ description,
8
+ properties: {},
9
+ additionalProperties: false,
10
+ });
11
+ }
12
+ function toRecord(details) {
13
+ if (details && typeof details === "object") {
14
+ return details;
15
+ }
16
+ return undefined;
17
+ }
18
+ function normaliseFailure(details) {
19
+ if (details === undefined || details === null) {
20
+ return undefined;
21
+ }
22
+ if (typeof details === "object") {
23
+ return details;
24
+ }
25
+ return { value: details };
26
+ }
27
+ const drivesListArgsSchema = createNoArgsSchema("No arguments are required to list the Ultimate drive slots and their current images.");
28
+ const driveIdentifierSchema = stringSchema({
29
+ description: "Drive identifier such as drive8, drive9, or unit name defined in the Ultimate UI.",
30
+ minLength: 1,
31
+ });
32
+ const diskImagePathSchema = stringSchema({
33
+ description: "Absolute or Ultimate filesystem path to the disk image to mount or manage.",
34
+ minLength: 1,
35
+ });
36
+ const driveMountArgsSchema = objectSchema({
37
+ description: "Parameters for mounting a disk image onto a specific Ultimate drive slot.",
38
+ properties: {
39
+ drive: driveIdentifierSchema,
40
+ image: diskImagePathSchema,
41
+ type: optionalSchema(stringSchema({
42
+ description: "Override the disk image type when auto-detection is incorrect.",
43
+ enum: ["d64", "g64", "d71", "g71", "d81"],
44
+ })),
45
+ mode: optionalSchema(stringSchema({
46
+ description: "Attachment mode controlling how the firmware treats the mounted image.",
47
+ enum: ["readwrite", "readonly", "unlinked"],
48
+ })),
49
+ },
50
+ required: ["drive", "image"],
51
+ additionalProperties: false,
52
+ });
53
+ const driveOnlyArgsSchema = objectSchema({
54
+ description: "Parameters requiring only the target drive identifier.",
55
+ properties: {
56
+ drive: driveIdentifierSchema,
57
+ },
58
+ required: ["drive"],
59
+ additionalProperties: false,
60
+ });
61
+ const driveModeArgsSchema = objectSchema({
62
+ description: "Parameters for setting the Ultimate drive emulation mode.",
63
+ properties: {
64
+ drive: driveIdentifierSchema,
65
+ mode: stringSchema({
66
+ description: "Drive hardware profile to emulate.",
67
+ enum: ["1541", "1571", "1581"],
68
+ }),
69
+ },
70
+ required: ["drive", "mode"],
71
+ additionalProperties: false,
72
+ });
73
+ const driveLoadRomArgsSchema = objectSchema({
74
+ description: "Parameters for loading a custom ROM into an Ultimate drive slot.",
75
+ properties: {
76
+ drive: driveIdentifierSchema,
77
+ path: diskImagePathSchema,
78
+ },
79
+ required: ["drive", "path"],
80
+ additionalProperties: false,
81
+ });
82
+ const fileInfoArgsSchema = objectSchema({
83
+ description: "Parameters for inspecting a file on the Ultimate filesystem.",
84
+ properties: {
85
+ path: diskImagePathSchema,
86
+ },
87
+ required: ["path"],
88
+ additionalProperties: false,
89
+ });
90
+ const diskNameSchema = optionalSchema(stringSchema({
91
+ description: "Optional disk label (max 16 characters, converted to PETSCII by the firmware).",
92
+ minLength: 1,
93
+ maxLength: 16,
94
+ }));
95
+ const createD64ArgsSchema = objectSchema({
96
+ description: "Parameters for creating a blank D64 disk image on the Ultimate filesystem.",
97
+ properties: {
98
+ path: diskImagePathSchema,
99
+ tracks: optionalSchema(numberSchema({
100
+ description: "Track count (35 for standard, 40 for extended).",
101
+ integer: true,
102
+ minimum: 35,
103
+ maximum: 40,
104
+ })),
105
+ diskname: diskNameSchema,
106
+ },
107
+ required: ["path"],
108
+ additionalProperties: false,
109
+ });
110
+ const createD71ArgsSchema = objectSchema({
111
+ description: "Parameters for creating a blank D71 disk image on the Ultimate filesystem.",
112
+ properties: {
113
+ path: diskImagePathSchema,
114
+ diskname: diskNameSchema,
115
+ },
116
+ required: ["path"],
117
+ additionalProperties: false,
118
+ });
119
+ const createD81ArgsSchema = objectSchema({
120
+ description: "Parameters for creating a blank D81 disk image on the Ultimate filesystem.",
121
+ properties: {
122
+ path: diskImagePathSchema,
123
+ diskname: diskNameSchema,
124
+ },
125
+ required: ["path"],
126
+ additionalProperties: false,
127
+ });
128
+ const createDnpArgsSchema = objectSchema({
129
+ description: "Parameters for creating a blank DNP disk image on the Ultimate filesystem.",
130
+ properties: {
131
+ path: diskImagePathSchema,
132
+ tracks: numberSchema({
133
+ description: "Number of tracks to allocate in the CMD-native DNP image.",
134
+ integer: true,
135
+ minimum: 1,
136
+ maximum: 255,
137
+ }),
138
+ diskname: diskNameSchema,
139
+ },
140
+ required: ["path", "tracks"],
141
+ additionalProperties: false,
142
+ });
143
+ export const storageModule = defineToolModule({
144
+ domain: "storage",
145
+ summary: "Drive management, disk image creation, and file inspection utilities.",
146
+ resources: ["c64://context/bootstrap"],
147
+ prompts: ["memory-debug"],
148
+ defaultTags: ["drive", "storage"],
149
+ workflowHints: [
150
+ "Reach for storage tools when the user mentions drives, disk images, or Ultimate slots.",
151
+ "Spell out which slot or path you touched so the user can replicate actions on hardware.",
152
+ ],
153
+ tools: [
154
+ {
155
+ name: "drives_list",
156
+ description: "List Ultimate drive slots and their currently mounted images. Read c64://context/bootstrap for drive safety.",
157
+ summary: "Fetches firmware drive status including mounted images, power state, and modes.",
158
+ inputSchema: drivesListArgsSchema.jsonSchema,
159
+ relatedResources: ["c64://context/bootstrap"],
160
+ tags: ["drive", "status"],
161
+ prerequisites: [],
162
+ examples: [
163
+ {
164
+ name: "Enumerate drives",
165
+ description: "List drives and images",
166
+ arguments: {},
167
+ },
168
+ ],
169
+ workflowHints: [
170
+ "Call first when you need to orient on current drive state; summarise mounted images and power flags for the user.",
171
+ ],
172
+ async execute(args, ctx) {
173
+ try {
174
+ drivesListArgsSchema.parse(args ?? {});
175
+ ctx.logger.info("Listing Ultimate drives");
176
+ const details = await ctx.client.drivesList();
177
+ return textResult("Retrieved Ultimate drive status.", {
178
+ success: true,
179
+ drives: details,
180
+ });
181
+ }
182
+ catch (error) {
183
+ if (error instanceof ToolError) {
184
+ return toolErrorResult(error);
185
+ }
186
+ return unknownErrorResult(error);
187
+ }
188
+ },
189
+ },
190
+ {
191
+ name: "drive_mount",
192
+ description: "Mount a disk image onto a specific Ultimate drive slot.",
193
+ summary: "Validates parameters and instructs the firmware to mount an image with optional type/mode overrides.",
194
+ inputSchema: driveMountArgsSchema.jsonSchema,
195
+ relatedResources: ["c64://context/bootstrap"],
196
+ tags: ["drive", "mount"],
197
+ prerequisites: ["drives_list"],
198
+ examples: [
199
+ {
200
+ name: "Mount D64",
201
+ description: "Mount demo.d64 on drive8",
202
+ arguments: { drive: "drive8", image: "/tmp/demo.d64", type: "d64", mode: "readwrite" },
203
+ },
204
+ ],
205
+ workflowHints: [
206
+ "Use when the user provides an image path; confirm drive slot, type, and mode in the reply.",
207
+ "Suggest running drive_on if the slot was powered down before mounting.",
208
+ ],
209
+ async execute(args, ctx) {
210
+ try {
211
+ const parsed = driveMountArgsSchema.parse(args ?? {});
212
+ ctx.logger.info("Mounting disk image", {
213
+ drive: parsed.drive,
214
+ image: parsed.image,
215
+ type: parsed.type ?? null,
216
+ mode: parsed.mode ?? null,
217
+ });
218
+ const result = await ctx.client.driveMount(parsed.drive, parsed.image, {
219
+ type: parsed.type,
220
+ mode: parsed.mode,
221
+ });
222
+ if (!result.success) {
223
+ throw new ToolExecutionError("C64 firmware reported failure while mounting disk image", {
224
+ details: normaliseFailure(result.details),
225
+ });
226
+ }
227
+ const details = toRecord(result.details) ?? {};
228
+ const finalMode = typeof details.mode === "string" ? details.mode : parsed.mode ?? null;
229
+ const finalType = typeof details.type === "string" ? details.type : parsed.type ?? null;
230
+ return textResult(`Mounted ${parsed.image} on ${parsed.drive}.`, {
231
+ success: true,
232
+ drive: parsed.drive,
233
+ image: parsed.image,
234
+ type: finalType,
235
+ mode: finalMode,
236
+ details,
237
+ });
238
+ }
239
+ catch (error) {
240
+ if (error instanceof ToolError) {
241
+ return toolErrorResult(error);
242
+ }
243
+ return unknownErrorResult(error);
244
+ }
245
+ },
246
+ },
247
+ {
248
+ name: "drive_remove",
249
+ description: "Remove the currently mounted disk image from an Ultimate drive slot.",
250
+ summary: "Ejects the image and leaves the drive empty without powering it off.",
251
+ inputSchema: driveOnlyArgsSchema.jsonSchema,
252
+ relatedResources: ["c64://context/bootstrap"],
253
+ tags: ["drive", "unmount"],
254
+ prerequisites: ["drives_list"],
255
+ examples: [
256
+ {
257
+ name: "Eject drive8",
258
+ description: "Remove current image",
259
+ arguments: { drive: "drive8" },
260
+ },
261
+ ],
262
+ workflowHints: [
263
+ "Call after confirming the user wants to eject an image; tell them the slot is now empty.",
264
+ ],
265
+ async execute(args, ctx) {
266
+ try {
267
+ const parsed = driveOnlyArgsSchema.parse(args ?? {});
268
+ ctx.logger.info("Removing disk image", { drive: parsed.drive });
269
+ const result = await ctx.client.driveRemove(parsed.drive);
270
+ if (!result.success) {
271
+ throw new ToolExecutionError("C64 firmware reported failure while removing disk image", {
272
+ details: normaliseFailure(result.details),
273
+ });
274
+ }
275
+ return textResult(`Removed mounted image from ${parsed.drive}.`, {
276
+ success: true,
277
+ drive: parsed.drive,
278
+ details: toRecord(result.details) ?? {},
279
+ });
280
+ }
281
+ catch (error) {
282
+ if (error instanceof ToolError) {
283
+ return toolErrorResult(error);
284
+ }
285
+ return unknownErrorResult(error);
286
+ }
287
+ },
288
+ },
289
+ {
290
+ name: "drive_reset",
291
+ description: "Reset the selected Ultimate drive slot.",
292
+ summary: "Issues a IEC reset for the chosen drive without cycling the entire machine.",
293
+ inputSchema: driveOnlyArgsSchema.jsonSchema,
294
+ relatedResources: ["c64://context/bootstrap"],
295
+ tags: ["drive", "reset"],
296
+ prerequisites: ["drives_list"],
297
+ examples: [
298
+ {
299
+ name: "Reset drive8",
300
+ description: "IEC reset",
301
+ arguments: { drive: "drive8" },
302
+ },
303
+ ],
304
+ workflowHints: [
305
+ "Use when drive firmware needs a kick after errors; mention that mounted images remain but state resets.",
306
+ ],
307
+ async execute(args, ctx) {
308
+ try {
309
+ const parsed = driveOnlyArgsSchema.parse(args ?? {});
310
+ ctx.logger.info("Resetting drive", { drive: parsed.drive });
311
+ const result = await ctx.client.driveReset(parsed.drive);
312
+ if (!result.success) {
313
+ throw new ToolExecutionError("C64 firmware reported failure while resetting drive", {
314
+ details: normaliseFailure(result.details),
315
+ });
316
+ }
317
+ return textResult(`Drive ${parsed.drive} reset issued.`, {
318
+ success: true,
319
+ drive: parsed.drive,
320
+ details: toRecord(result.details) ?? {},
321
+ });
322
+ }
323
+ catch (error) {
324
+ if (error instanceof ToolError) {
325
+ return toolErrorResult(error);
326
+ }
327
+ return unknownErrorResult(error);
328
+ }
329
+ },
330
+ },
331
+ {
332
+ name: "drive_on",
333
+ description: "Power on a specific Ultimate drive slot.",
334
+ summary: "Ensures the selected drive is reported as powered and ready on the IEC bus.",
335
+ inputSchema: driveOnlyArgsSchema.jsonSchema,
336
+ relatedResources: ["c64://context/bootstrap"],
337
+ tags: ["drive", "power"],
338
+ prerequisites: ["drives_list"],
339
+ examples: [
340
+ {
341
+ name: "Power on",
342
+ description: "Turn on drive8",
343
+ arguments: { drive: "drive8" },
344
+ },
345
+ ],
346
+ workflowHints: [
347
+ "Power a slot on before mounting or accessing disks; note the previous state in your response.",
348
+ ],
349
+ async execute(args, ctx) {
350
+ try {
351
+ const parsed = driveOnlyArgsSchema.parse(args ?? {});
352
+ ctx.logger.info("Powering on drive", { drive: parsed.drive });
353
+ const result = await ctx.client.driveOn(parsed.drive);
354
+ if (!result.success) {
355
+ throw new ToolExecutionError("C64 firmware reported failure while powering on drive", {
356
+ details: normaliseFailure(result.details),
357
+ });
358
+ }
359
+ return textResult(`Drive ${parsed.drive} powered on.`, {
360
+ success: true,
361
+ drive: parsed.drive,
362
+ power: "on",
363
+ details: toRecord(result.details) ?? {},
364
+ });
365
+ }
366
+ catch (error) {
367
+ if (error instanceof ToolError) {
368
+ return toolErrorResult(error);
369
+ }
370
+ return unknownErrorResult(error);
371
+ }
372
+ },
373
+ },
374
+ {
375
+ name: "drive_off",
376
+ description: "Power off a specific Ultimate drive slot.",
377
+ summary: "Disables the drive without altering mounted media so it can be re-enabled later.",
378
+ inputSchema: driveOnlyArgsSchema.jsonSchema,
379
+ relatedResources: ["c64://context/bootstrap"],
380
+ tags: ["drive", "power"],
381
+ prerequisites: ["drives_list"],
382
+ examples: [
383
+ {
384
+ name: "Power off",
385
+ description: "Turn off drive8",
386
+ arguments: { drive: "drive8" },
387
+ },
388
+ ],
389
+ workflowHints: [
390
+ "Warn the user that powering off ejects the drive from C64 view; suggest unmounting images if necessary first.",
391
+ ],
392
+ async execute(args, ctx) {
393
+ try {
394
+ const parsed = driveOnlyArgsSchema.parse(args ?? {});
395
+ ctx.logger.info("Powering off drive", { drive: parsed.drive });
396
+ const result = await ctx.client.driveOff(parsed.drive);
397
+ if (!result.success) {
398
+ throw new ToolExecutionError("C64 firmware reported failure while powering off drive", {
399
+ details: normaliseFailure(result.details),
400
+ });
401
+ }
402
+ return textResult(`Drive ${parsed.drive} powered off.`, {
403
+ success: true,
404
+ drive: parsed.drive,
405
+ power: "off",
406
+ details: toRecord(result.details) ?? {},
407
+ });
408
+ }
409
+ catch (error) {
410
+ if (error instanceof ToolError) {
411
+ return toolErrorResult(error);
412
+ }
413
+ return unknownErrorResult(error);
414
+ }
415
+ },
416
+ },
417
+ {
418
+ name: "drive_load_rom",
419
+ description: "Temporarily load a custom ROM into an Ultimate drive slot.",
420
+ summary: "Points the drive at an alternative ROM file without flashing permanent storage.",
421
+ inputSchema: driveLoadRomArgsSchema.jsonSchema,
422
+ relatedResources: ["c64://context/bootstrap"],
423
+ tags: ["drive", "rom"],
424
+ prerequisites: ["drives_list"],
425
+ examples: [
426
+ {
427
+ name: "Load ROM",
428
+ description: "Load custom.rom to drive8",
429
+ arguments: { drive: "drive8", path: "/roms/custom.rom" },
430
+ },
431
+ ],
432
+ workflowHints: [
433
+ "Use when the user requests custom DOS ROMs; remind them a reset may be required afterwards.",
434
+ ],
435
+ async execute(args, ctx) {
436
+ try {
437
+ const parsed = driveLoadRomArgsSchema.parse(args ?? {});
438
+ ctx.logger.info("Loading drive ROM", { drive: parsed.drive, path: parsed.path });
439
+ const result = await ctx.client.driveLoadRom(parsed.drive, parsed.path);
440
+ if (!result.success) {
441
+ throw new ToolExecutionError("C64 firmware reported failure while loading drive ROM", {
442
+ details: normaliseFailure(result.details),
443
+ });
444
+ }
445
+ return textResult(`Drive ${parsed.drive} ROM loaded from ${parsed.path}.`, {
446
+ success: true,
447
+ drive: parsed.drive,
448
+ path: parsed.path,
449
+ details: toRecord(result.details) ?? {},
450
+ });
451
+ }
452
+ catch (error) {
453
+ if (error instanceof ToolError) {
454
+ return toolErrorResult(error);
455
+ }
456
+ return unknownErrorResult(error);
457
+ }
458
+ },
459
+ },
460
+ {
461
+ name: "drive_mode",
462
+ description: "Set the emulation mode for an Ultimate drive slot (1541/1571/1581).",
463
+ summary: "Switches the drive hardware profile to match the mounted media and desired behaviour.",
464
+ inputSchema: driveModeArgsSchema.jsonSchema,
465
+ relatedResources: ["c64://context/bootstrap"],
466
+ tags: ["drive", "mode"],
467
+ prerequisites: ["drives_list"],
468
+ examples: [
469
+ {
470
+ name: "Set mode",
471
+ description: "Set drive8 to 1571",
472
+ arguments: { drive: "drive8", mode: "1571" },
473
+ },
474
+ ],
475
+ workflowHints: [
476
+ "Invoke when the user needs to switch between 1541/1571/1581 behaviours; restate the resulting mode in your reply.",
477
+ ],
478
+ async execute(args, ctx) {
479
+ try {
480
+ const parsed = driveModeArgsSchema.parse(args ?? {});
481
+ ctx.logger.info("Setting drive mode", { drive: parsed.drive, mode: parsed.mode });
482
+ const result = await ctx.client.driveSetMode(parsed.drive, parsed.mode);
483
+ if (!result.success) {
484
+ throw new ToolExecutionError("C64 firmware reported failure while setting drive mode", {
485
+ details: normaliseFailure(result.details),
486
+ });
487
+ }
488
+ return textResult(`Drive ${parsed.drive} set to ${parsed.mode} mode.`, {
489
+ success: true,
490
+ drive: parsed.drive,
491
+ mode: parsed.mode,
492
+ details: toRecord(result.details) ?? {},
493
+ });
494
+ }
495
+ catch (error) {
496
+ if (error instanceof ToolError) {
497
+ return toolErrorResult(error);
498
+ }
499
+ return unknownErrorResult(error);
500
+ }
501
+ },
502
+ },
503
+ {
504
+ name: "file_info",
505
+ description: "Inspect metadata for a file on the Ultimate filesystem.",
506
+ summary: "Queries the REST API for file size, type, and other details useful when managing disk images.",
507
+ inputSchema: fileInfoArgsSchema.jsonSchema,
508
+ relatedResources: ["c64://context/bootstrap"],
509
+ tags: ["storage", "info"],
510
+ prerequisites: [],
511
+ examples: [
512
+ {
513
+ name: "Inspect file",
514
+ description: "Read info for /tmp/demo.d64",
515
+ arguments: { path: "/tmp/demo.d64" },
516
+ },
517
+ ],
518
+ workflowHints: [
519
+ "Use to inspect Ultimate filesystem paths before mounting; report size and type so the user can validate the asset.",
520
+ ],
521
+ async execute(args, ctx) {
522
+ try {
523
+ const parsed = fileInfoArgsSchema.parse(args ?? {});
524
+ ctx.logger.info("Fetching file info", { path: parsed.path });
525
+ const details = await ctx.client.filesInfo(parsed.path);
526
+ return textResult(`Retrieved file info for ${parsed.path}.`, {
527
+ success: true,
528
+ path: parsed.path,
529
+ details,
530
+ });
531
+ }
532
+ catch (error) {
533
+ if (error instanceof ToolError) {
534
+ return toolErrorResult(error);
535
+ }
536
+ return unknownErrorResult(error);
537
+ }
538
+ },
539
+ },
540
+ {
541
+ name: "create_d64",
542
+ description: "Create a blank D64 disk image on the Ultimate filesystem.",
543
+ summary: "Allocates a 35 or 40 track D64 image with an optional disk name.",
544
+ inputSchema: createD64ArgsSchema.jsonSchema,
545
+ relatedResources: ["c64://context/bootstrap"],
546
+ tags: ["disk", "create"],
547
+ prerequisites: ["drives_list"],
548
+ examples: [
549
+ {
550
+ name: "Create D64",
551
+ description: "Create /tmp/new.d64",
552
+ arguments: { path: "/tmp/new.d64", tracks: 35, diskname: "DISK1" },
553
+ },
554
+ ],
555
+ workflowHints: [
556
+ "Use when the user needs a new D64 image; confirm track count and disk name in your summary.",
557
+ ],
558
+ async execute(args, ctx) {
559
+ try {
560
+ const parsed = createD64ArgsSchema.parse(args ?? {});
561
+ ctx.logger.info("Creating D64 image", {
562
+ path: parsed.path,
563
+ tracks: parsed.tracks ?? null,
564
+ diskname: parsed.diskname ?? null,
565
+ });
566
+ const result = await ctx.client.filesCreateD64(parsed.path, {
567
+ tracks: parsed.tracks,
568
+ diskname: parsed.diskname,
569
+ });
570
+ if (!result.success) {
571
+ throw new ToolExecutionError("C64 firmware reported failure while creating D64 image", {
572
+ details: normaliseFailure(result.details),
573
+ });
574
+ }
575
+ return textResult(`Created D64 image at ${parsed.path}.`, {
576
+ success: true,
577
+ path: parsed.path,
578
+ tracks: parsed.tracks ?? null,
579
+ diskname: parsed.diskname ?? null,
580
+ details: toRecord(result.details) ?? {},
581
+ });
582
+ }
583
+ catch (error) {
584
+ if (error instanceof ToolError) {
585
+ return toolErrorResult(error);
586
+ }
587
+ return unknownErrorResult(error);
588
+ }
589
+ },
590
+ },
591
+ {
592
+ name: "create_d71",
593
+ description: "Create a blank D71 disk image on the Ultimate filesystem.",
594
+ summary: "Allocates a 1571-compatible disk image with optional labelling.",
595
+ inputSchema: createD71ArgsSchema.jsonSchema,
596
+ relatedResources: ["c64://context/bootstrap"],
597
+ tags: ["disk", "create"],
598
+ prerequisites: ["drives_list"],
599
+ examples: [
600
+ {
601
+ name: "Create D71",
602
+ description: "Create /tmp/new.d71",
603
+ arguments: { path: "/tmp/new.d71", diskname: "DISK2" },
604
+ },
605
+ ],
606
+ workflowHints: [
607
+ "Reach for this when the user wants a 1571 disk; mention track count and that the image is double-sided.",
608
+ ],
609
+ async execute(args, ctx) {
610
+ try {
611
+ const parsed = createD71ArgsSchema.parse(args ?? {});
612
+ ctx.logger.info("Creating D71 image", {
613
+ path: parsed.path,
614
+ diskname: parsed.diskname ?? null,
615
+ });
616
+ const result = await ctx.client.filesCreateD71(parsed.path, {
617
+ diskname: parsed.diskname,
618
+ });
619
+ if (!result.success) {
620
+ throw new ToolExecutionError("C64 firmware reported failure while creating D71 image", {
621
+ details: normaliseFailure(result.details),
622
+ });
623
+ }
624
+ return textResult(`Created D71 image at ${parsed.path}.`, {
625
+ success: true,
626
+ path: parsed.path,
627
+ diskname: parsed.diskname ?? null,
628
+ details: toRecord(result.details) ?? {},
629
+ });
630
+ }
631
+ catch (error) {
632
+ if (error instanceof ToolError) {
633
+ return toolErrorResult(error);
634
+ }
635
+ return unknownErrorResult(error);
636
+ }
637
+ },
638
+ },
639
+ {
640
+ name: "create_d81",
641
+ description: "Create a blank D81 disk image on the Ultimate filesystem.",
642
+ summary: "Allocates a 1581-compatible disk image with optional labelling.",
643
+ inputSchema: createD81ArgsSchema.jsonSchema,
644
+ relatedResources: ["c64://context/bootstrap"],
645
+ tags: ["disk", "create"],
646
+ prerequisites: ["drives_list"],
647
+ examples: [
648
+ {
649
+ name: "Create D81",
650
+ description: "Create /tmp/new.d81",
651
+ arguments: { path: "/tmp/new.d81", diskname: "DISK3" },
652
+ },
653
+ ],
654
+ workflowHints: [
655
+ "Use when the user needs a 1581 disk; remind them about 40 track layout and optional disk name.",
656
+ ],
657
+ async execute(args, ctx) {
658
+ try {
659
+ const parsed = createD81ArgsSchema.parse(args ?? {});
660
+ ctx.logger.info("Creating D81 image", {
661
+ path: parsed.path,
662
+ diskname: parsed.diskname ?? null,
663
+ });
664
+ const result = await ctx.client.filesCreateD81(parsed.path, {
665
+ diskname: parsed.diskname,
666
+ });
667
+ if (!result.success) {
668
+ throw new ToolExecutionError("C64 firmware reported failure while creating D81 image", {
669
+ details: normaliseFailure(result.details),
670
+ });
671
+ }
672
+ return textResult(`Created D81 image at ${parsed.path}.`, {
673
+ success: true,
674
+ path: parsed.path,
675
+ diskname: parsed.diskname ?? null,
676
+ details: toRecord(result.details) ?? {},
677
+ });
678
+ }
679
+ catch (error) {
680
+ if (error instanceof ToolError) {
681
+ return toolErrorResult(error);
682
+ }
683
+ return unknownErrorResult(error);
684
+ }
685
+ },
686
+ },
687
+ {
688
+ name: "create_dnp",
689
+ description: "Create a blank DNP disk image on the Ultimate filesystem.",
690
+ summary: "Allocates a CMD-native DNP image with custom track count and optional label.",
691
+ inputSchema: createDnpArgsSchema.jsonSchema,
692
+ relatedResources: ["c64://context/bootstrap"],
693
+ tags: ["disk", "create"],
694
+ prerequisites: [
695
+ "drives_list"
696
+ ],
697
+ examples: [
698
+ {
699
+ name: "Create DNP",
700
+ description: "Create /tmp/new.dnp with 80 tracks",
701
+ arguments: { path: "/tmp/new.dnp", tracks: 80, diskname: "DISK4" },
702
+ },
703
+ ],
704
+ workflowHints: [
705
+ "Use for CMD-native DNP images; clarify track count and remind about CMD hardware compatibility.",
706
+ ],
707
+ async execute(args, ctx) {
708
+ try {
709
+ const parsed = createDnpArgsSchema.parse(args ?? {});
710
+ ctx.logger.info("Creating DNP image", {
711
+ path: parsed.path,
712
+ tracks: parsed.tracks,
713
+ diskname: parsed.diskname ?? null,
714
+ });
715
+ const result = await ctx.client.filesCreateDnp(parsed.path, parsed.tracks, {
716
+ diskname: parsed.diskname,
717
+ });
718
+ if (!result.success) {
719
+ throw new ToolExecutionError("C64 firmware reported failure while creating DNP image", {
720
+ details: normaliseFailure(result.details),
721
+ });
722
+ }
723
+ return textResult(`Created DNP image at ${parsed.path}.`, {
724
+ success: true,
725
+ path: parsed.path,
726
+ tracks: parsed.tracks,
727
+ diskname: parsed.diskname ?? null,
728
+ details: toRecord(result.details) ?? {},
729
+ });
730
+ }
731
+ catch (error) {
732
+ if (error instanceof ToolError) {
733
+ return toolErrorResult(error);
734
+ }
735
+ return unknownErrorResult(error);
736
+ }
737
+ },
738
+ },
739
+ ],
740
+ });