c64bridge 0.8.1 → 0.9.1

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 (92) hide show
  1. package/.github/prompts/steer.prompt.md +55 -0
  2. package/AGENTS.md +9 -7
  3. package/README.md +192 -154
  4. package/data/embeddings_asm.json +1 -1
  5. package/data/embeddings_basic.json +1 -1
  6. package/data/embeddings_hardware.json +1 -1
  7. package/data/embeddings_mixed.json +1 -1
  8. package/data/embeddings_other.json +1 -1
  9. package/data/graphics/petscii-style-guide.md +4 -4
  10. package/data/graphics/sprite-charset-best-practices.md +9 -9
  11. package/data/vice/vice-binary-monitor-spec.md +938 -0
  12. package/dist/.tsbuildinfo +1 -1
  13. package/dist/c64Client.js +76 -2
  14. package/dist/device.js +129 -13
  15. package/dist/mcp-server.js +1 -1
  16. package/dist/prompts/registry.js +27 -25
  17. package/dist/rag/indexer.js +18 -8
  18. package/dist/rag/knowledgeIndex.js +112 -79
  19. package/dist/rag/resourceUris.js +93 -0
  20. package/dist/sidwaveCompiler.js +1 -1
  21. package/dist/{assemblyConverter.js → tools/assember.js} +10 -1
  22. package/dist/tools/audio.js +11 -11
  23. package/dist/{basicConverter.js → tools/basicTokenizer.js} +1 -1
  24. package/dist/tools/debug.js +159 -6
  25. package/dist/tools/developer.js +13 -13
  26. package/dist/tools/disassembler.js +478 -0
  27. package/dist/tools/graphics.js +10 -10
  28. package/dist/tools/input.js +338 -0
  29. package/dist/tools/machineControl.js +5 -5
  30. package/dist/tools/memory.js +336 -12
  31. package/dist/tools/meta/index.js +1 -1
  32. package/dist/tools/printer.js +11 -12
  33. package/dist/tools/programRunners.js +61 -22
  34. package/dist/tools/rag.js +9 -9
  35. package/dist/tools/registry/batch.js +167 -0
  36. package/dist/tools/registry/config.js +1 -1
  37. package/dist/tools/registry/disk.js +1 -1
  38. package/dist/tools/registry/drive.js +1 -1
  39. package/dist/tools/registry/extract.js +1 -1
  40. package/dist/tools/registry/graphics.js +102 -2
  41. package/dist/tools/registry/index.js +4 -0
  42. package/dist/tools/registry/input.js +2 -0
  43. package/dist/tools/registry/memory.js +62 -2
  44. package/dist/tools/registry/platform.js +1 -1
  45. package/dist/tools/registry/printer.js +1 -1
  46. package/dist/tools/registry/program.js +1 -1
  47. package/dist/tools/registry/rag.js +1 -1
  48. package/dist/tools/registry/sound.js +1 -1
  49. package/dist/tools/registry/stream.js +1 -1
  50. package/dist/tools/registry/system.js +1 -1
  51. package/dist/tools/schema.js +4 -5
  52. package/dist/tools/storage.js +15 -15
  53. package/dist/tools/streaming.js +3 -3
  54. package/dist/tools/symbolRegistry.js +49 -0
  55. package/dist/tools/types.js +56 -4
  56. package/dist/tools/vice.js +2 -5
  57. package/dist/vice/process.js +120 -2
  58. package/dist/vice/readiness.js +35 -0
  59. package/dist/vice/smokeOptions.js +44 -0
  60. package/dist/vice/viceClient.js +5 -1
  61. package/dist/viceRunner.js +2 -0
  62. package/doc/plans/prompts/README.md +7 -7
  63. package/mcp/prompts.json +42 -59
  64. package/mcp/protocol-examples.json +2 -2
  65. package/mcp/resources.json +179 -166
  66. package/mcp/schemas/c64_batch.schema.json +53 -0
  67. package/mcp/schemas/c64_config.schema.json +525 -283
  68. package/mcp/schemas/c64_debug.schema.json +763 -304
  69. package/mcp/schemas/c64_disk.schema.json +499 -243
  70. package/mcp/schemas/c64_drive.schema.json +165 -90
  71. package/mcp/schemas/c64_extract.schema.json +533 -274
  72. package/mcp/schemas/c64_graphics.schema.json +607 -295
  73. package/mcp/schemas/c64_input.schema.json +136 -0
  74. package/mcp/schemas/c64_memory.schema.json +637 -128
  75. package/mcp/schemas/c64_printer.schema.json +393 -196
  76. package/mcp/schemas/c64_program.schema.json +699 -340
  77. package/mcp/schemas/c64_rag.schema.json +100 -54
  78. package/mcp/schemas/c64_select_backend.schema.json +49 -23
  79. package/mcp/schemas/c64_sound.schema.json +1092 -577
  80. package/mcp/schemas/c64_stream.schema.json +89 -47
  81. package/mcp/schemas/c64_system.schema.json +351 -185
  82. package/mcp/schemas/c64_vice.schema.json +85 -47
  83. package/mcp/server.json +1 -1
  84. package/mcp/tools.json +7299 -3346
  85. package/mcp.json +3 -3
  86. package/package.json +17 -14
  87. package/scripts/fetch-external-sources.mjs +1 -2
  88. package/scripts/run-coverage.mjs +33 -1
  89. package/scripts/run-tests.mjs +2 -2
  90. package/scripts/run-tests.ts +275 -52
  91. package/scripts/update-readme.ts +132 -3
  92. package/server.json +2 -2
@@ -1,10 +1,12 @@
1
- import { assemblyToPrg, AssemblyError } from "../assemblyConverter.js";
2
- import { basicToPrg } from "../basicConverter.js";
1
+ import fs from "node:fs";
2
+ import { assemblyToPrgDetailed, AssemblyError } from "./assember.js";
3
+ import { basicToPrg } from "./basicTokenizer.js";
3
4
  import { defineToolModule, OPERATION_DISCRIMINATOR, } from "./types.js";
4
- import { booleanSchema, objectSchema, stringSchema } from "./schema.js";
5
+ import { booleanSchema, objectSchema, optionalSchema, stringSchema } from "./schema.js";
5
6
  import { textResult } from "./responses.js";
6
7
  import { ToolExecutionError, ToolError, ToolValidationError, toolErrorResult, unknownErrorResult, } from "./errors.js";
7
8
  import { pollForProgramOutcome } from "./pollValidator.js";
9
+ import { clearViceSymbols, setViceSymbols, parseViceSymbolFile } from "./symbolRegistry.js";
8
10
  function extractFailureDetails(details) {
9
11
  if (details === undefined || details === null) {
10
12
  return undefined;
@@ -163,6 +165,14 @@ function normalizeRuntimeErrors(errors) {
163
165
  text: error.raw,
164
166
  }));
165
167
  }
168
+ export const __programRunnerHelpersForTests = {
169
+ extractFailureDetails,
170
+ toRecord,
171
+ stripOperationDiscriminator,
172
+ parseBasicRuntimeErrors,
173
+ attemptAutoFixBasicProgram,
174
+ normalizeRuntimeErrors,
175
+ };
166
176
  function structuredExecutionError(message, data) {
167
177
  return {
168
178
  content: [
@@ -221,6 +231,10 @@ const prgFileArgsSchema = objectSchema({
221
231
  description: prgPathDescription,
222
232
  minLength: 1,
223
233
  }),
234
+ symbolsFile: optionalSchema(stringSchema({
235
+ description: "Path to a VICE symbol file (.vs) to load alongside the PRG. Symbols are used by the disassemble tool when annotating addresses.",
236
+ minLength: 1,
237
+ })),
224
238
  },
225
239
  required: ["path"],
226
240
  additionalProperties: false,
@@ -394,7 +408,7 @@ async function executeUploadRunBasic(rawArgs, ctx) {
394
408
  format: "prg",
395
409
  entryAddress,
396
410
  prgSize: prg.length,
397
- resources: ["c64://specs/basic", "c64://context/bootstrap"],
411
+ resources: ["c64://basic/spec", "c64://guide/bootstrap"],
398
412
  ...(screenOutput ? { screen: screenOutput } : {}),
399
413
  ...(autoFixInfo
400
414
  ? {
@@ -424,9 +438,13 @@ async function executeUploadRunAsm(rawArgs, ctx) {
424
438
  sourceLength: parsed.program.length,
425
439
  ...(shouldVerify ? { verify: true } : {}),
426
440
  });
427
- // Assemble locally to expose structured metadata
428
- const prg = assemblyToPrg(parsed.program);
441
+ // Assemble locally to expose structured metadata and capture symbols
442
+ const asmResult = assemblyToPrgDetailed(parsed.program);
443
+ const prg = asmResult.prg;
429
444
  const entryAddress = prg.readUInt16LE(0);
445
+ if (ctx.platform.id === "vice") {
446
+ setViceSymbols(asmResult.symbols.entries());
447
+ }
430
448
  const result = await ctx.client.uploadAndRunAsm(parsed.program);
431
449
  if (!result.success) {
432
450
  return toolErrorResult(new ToolExecutionError("C64 firmware reported failure while running assembly program", {
@@ -460,7 +478,7 @@ async function executeUploadRunAsm(rawArgs, ctx) {
460
478
  format: "prg",
461
479
  entryAddress,
462
480
  prgSize: prg.length,
463
- resources: ["c64://specs/assembly", "c64://context/bootstrap"],
481
+ resources: ["c64://assembly/6510-spec", "c64://guide/bootstrap"],
464
482
  ...(shouldVerify && verified ? { verified: true } : {}),
465
483
  };
466
484
  const base = textResult("Assembly program assembled, uploaded, and executed successfully.", {
@@ -506,7 +524,7 @@ async function executeLoadPrg(rawArgs, ctx) {
506
524
  format: "prg",
507
525
  path: parsed.path,
508
526
  entryAddress: null,
509
- resources: ["c64://context/bootstrap"],
527
+ resources: ["c64://guide/bootstrap"],
510
528
  };
511
529
  const base = textResult(`PRG ${parsed.path} loaded into memory.`, {
512
530
  success: true,
@@ -527,6 +545,24 @@ async function executeRunPrg(rawArgs, ctx) {
527
545
  try {
528
546
  const parsed = prgFileArgsSchema.parse(rawArgs ?? {});
529
547
  ctx.logger.info("Running PRG file", { path: parsed.path });
548
+ let symbolsLoaded = 0;
549
+ if (ctx.platform.id === "vice") {
550
+ clearViceSymbols();
551
+ if (parsed.symbolsFile) {
552
+ try {
553
+ const content = await fs.promises.readFile(parsed.symbolsFile, "utf8");
554
+ const syms = parseViceSymbolFile(content);
555
+ setViceSymbols(syms.entries());
556
+ symbolsLoaded = syms.size;
557
+ ctx.logger.info("Loaded VICE symbols from file", { path: parsed.symbolsFile, count: symbolsLoaded });
558
+ }
559
+ catch (fileError) {
560
+ throw new ToolExecutionError(`Failed to read symbols file: ${parsed.symbolsFile}`, {
561
+ details: { message: fileError instanceof Error ? fileError.message : String(fileError) },
562
+ });
563
+ }
564
+ }
565
+ }
530
566
  const result = await ctx.client.runPrgFile(parsed.path);
531
567
  if (!result.success) {
532
568
  throw new ToolExecutionError("C64 firmware reported failure while running PRG", {
@@ -538,13 +574,16 @@ async function executeRunPrg(rawArgs, ctx) {
538
574
  format: "prg",
539
575
  path: parsed.path,
540
576
  entryAddress: null,
541
- resources: ["c64://context/bootstrap"],
577
+ resources: ["c64://guide/bootstrap"],
578
+ ...(symbolsLoaded > 0 ? { symbolsLoaded } : {}),
542
579
  };
543
- const base = textResult(`PRG ${parsed.path} loaded and executed.`, {
580
+ const symbolNote = symbolsLoaded > 0 ? ` Loaded ${symbolsLoaded} debug symbol(s).` : "";
581
+ const base = textResult(`PRG ${parsed.path} loaded and executed.${symbolNote}`, {
544
582
  success: true,
545
583
  path: parsed.path,
546
584
  entryAddress: null,
547
585
  details: toRecord(result.details) ?? null,
586
+ ...(symbolsLoaded > 0 ? { symbolsLoaded } : {}),
548
587
  });
549
588
  return { ...base, structuredContent: { type: "json", data } };
550
589
  }
@@ -570,7 +609,7 @@ async function executeRunCrt(rawArgs, ctx) {
570
609
  format: "crt",
571
610
  path: parsed.path,
572
611
  entryAddress: null,
573
- resources: ["c64://context/bootstrap"],
612
+ resources: ["c64://guide/bootstrap"],
574
613
  };
575
614
  const base = textResult(`PRG ${parsed.path} loaded and executed.`, {
576
615
  success: true,
@@ -598,9 +637,9 @@ export const programRunnersModule = defineToolModule({
598
637
  domain: "programs",
599
638
  summary: "Program uploaders, runners, and compilation workflows for BASIC, assembly, and PRG files.",
600
639
  resources: [
601
- "c64://context/bootstrap",
602
- "c64://specs/basic",
603
- "c64://specs/assembly",
640
+ "c64://guide/bootstrap",
641
+ "c64://basic/spec",
642
+ "c64://assembly/6510-spec",
604
643
  ],
605
644
  prompts: ["basic-program", "assembly-program"],
606
645
  defaultTags: ["programs", "execution"],
@@ -611,10 +650,10 @@ export const programRunnersModule = defineToolModule({
611
650
  tools: [
612
651
  {
613
652
  name: "upload_run_basic",
614
- description: "Upload a BASIC program to the C64 and execute it immediately. Refer to c64://specs/basic for syntax and device I/O.",
653
+ description: "Upload a BASIC program to the C64 and execute it immediately. Refer to c64://basic/spec for syntax and device I/O.",
615
654
  summary: "Uploads Commodore BASIC v2 source and runs it via Ultimate 64 firmware.",
616
655
  inputSchema: uploadBasicArgsSchema.jsonSchema,
617
- relatedResources: ["c64://specs/basic", "c64://docs/basic/pitfalls", "c64://context/bootstrap"],
656
+ relatedResources: ["c64://basic/spec", "c64://basic/pitfalls", "c64://guide/bootstrap"],
618
657
  relatedPrompts: ["basic-program"],
619
658
  tags: ["basic", "execution"],
620
659
  prerequisites: ["read_screen"],
@@ -636,10 +675,10 @@ export const programRunnersModule = defineToolModule({
636
675
  },
637
676
  {
638
677
  name: "upload_run_asm",
639
- description: "Assemble 6502/6510 source code, upload the PRG, and run it immediately. See c64://specs/assembly.",
678
+ description: "Assemble 6502/6510 source code, upload the PRG, and run it immediately. See c64://assembly/6510-spec.",
640
679
  summary: "Compiles assembly to a PRG and executes it on the C64 via Ultimate 64 firmware.",
641
680
  inputSchema: uploadAsmArgsSchema.jsonSchema,
642
- relatedResources: ["c64://specs/assembly", "c64://context/bootstrap"],
681
+ relatedResources: ["c64://assembly/6510-spec", "c64://guide/bootstrap"],
643
682
  relatedPrompts: ["assembly-program"],
644
683
  tags: ["assembly", "execution"],
645
684
  prerequisites: ["read_screen"],
@@ -664,7 +703,7 @@ export const programRunnersModule = defineToolModule({
664
703
  description: "Load a PRG into C64 memory without executing it.",
665
704
  summary: "Instructs the Ultimate firmware to transfer a PRG into memory without RUN.",
666
705
  inputSchema: prgFileArgsSchema.jsonSchema,
667
- relatedResources: ["c64://context/bootstrap"],
706
+ relatedResources: ["c64://guide/bootstrap"],
668
707
  tags: ["programs", "file"],
669
708
  prerequisites: ["drives_list"],
670
709
  examples: [
@@ -688,7 +727,7 @@ export const programRunnersModule = defineToolModule({
688
727
  description: "Run a PRG located on the Ultimate filesystem without uploading source.",
689
728
  summary: "Loads and executes a PRG file residing on attached storage.",
690
729
  inputSchema: prgFileArgsSchema.jsonSchema,
691
- relatedResources: ["c64://context/bootstrap"],
730
+ relatedResources: ["c64://guide/bootstrap"],
692
731
  tags: ["programs", "execution", "file"],
693
732
  workflowHints: [
694
733
  "Call when the user provides a PRG path and expects immediate execution without compiling.",
@@ -712,7 +751,7 @@ export const programRunnersModule = defineToolModule({
712
751
  description: "Run a cartridge image stored on the Ultimate filesystem.",
713
752
  summary: "Mounts and autostarts the specified CRT file through the firmware.",
714
753
  inputSchema: crtFileArgsSchema.jsonSchema,
715
- relatedResources: ["c64://context/bootstrap"],
754
+ relatedResources: ["c64://guide/bootstrap"],
716
755
  tags: ["programs", "cartridge"],
717
756
  workflowHints: [
718
757
  "Use for cartridge images and remind the user that the machine will reset into the CRT.",
@@ -742,7 +781,7 @@ export const programRunnersModule = defineToolModule({
742
781
  format: "crt",
743
782
  path: parsed.path,
744
783
  entryAddress: null,
745
- resources: ["c64://context/bootstrap"],
784
+ resources: ["c64://guide/bootstrap"],
746
785
  };
747
786
  const base = textResult(`CRT ${parsed.path} mounted and started.`, {
748
787
  success: true,
package/dist/tools/rag.js CHANGED
@@ -23,18 +23,18 @@ const ragRetrieveArgsSchema = objectSchema({
23
23
  });
24
24
  function createRagTool(language, options) {
25
25
  const primaryResourceUris = language === "basic"
26
- ? ["c64://specs/basic", "c64://context/bootstrap"]
27
- : ["c64://specs/assembly", "c64://context/bootstrap"];
26
+ ? ["c64://basic/spec", "c64://guide/bootstrap"]
27
+ : ["c64://assembly/6510-spec", "c64://guide/bootstrap"];
28
28
  return {
29
29
  name: language === "basic" ? "rag_retrieve_basic" : "rag_retrieve_asm",
30
30
  description: language === "basic"
31
- ? "Retrieve BASIC references from local knowledge. See c64://specs/basic before coding."
32
- : "Retrieve 6502/6510 assembly references from local knowledge. See c64://specs/assembly.",
31
+ ? "Retrieve BASIC references from local knowledge. See c64://basic/spec before coding."
32
+ : "Retrieve 6502/6510 assembly references from local knowledge. See c64://assembly/6510-spec.",
33
33
  summary: options.summary,
34
34
  inputSchema: ragRetrieveArgsSchema.jsonSchema,
35
35
  relatedResources: language === "basic"
36
- ? ["c64://specs/basic"]
37
- : ["c64://specs/assembly"],
36
+ ? ["c64://basic/spec"]
37
+ : ["c64://assembly/6510-spec"],
38
38
  relatedPrompts: language === "basic"
39
39
  ? ["basic-program"]
40
40
  : ["assembly-program"],
@@ -175,9 +175,9 @@ export const ragModule = defineToolModule({
175
175
  domain: "rag",
176
176
  summary: "Retrieval-augmented generation helpers for BASIC and assembly examples.",
177
177
  resources: [
178
- "c64://specs/basic",
179
- "c64://specs/assembly",
180
- "c64://docs/index",
178
+ "c64://basic/spec",
179
+ "c64://assembly/6510-spec",
180
+ "c64://guide/index",
181
181
  ],
182
182
  prompts: ["basic-program", "assembly-program"],
183
183
  defaultTags: ["rag", "search"],
@@ -0,0 +1,167 @@
1
+ import { defineToolModule, } from "../types.js";
2
+ import { getPlatformStatus } from "../../platform.js";
3
+ import { arraySchema, booleanSchema, objectSchema, optionalSchema, stringSchema, } from "../schema.js";
4
+ import { jsonResult } from "../responses.js";
5
+ import { ToolValidationError, unknownErrorResult } from "../errors.js";
6
+ const commandSchema = objectSchema({
7
+ description: "A single batch command.",
8
+ properties: {
9
+ tool: stringSchema({ description: "MCP tool name (e.g. c64_memory, c64_debug).", minLength: 1 }),
10
+ args: objectSchema({
11
+ description: "Arguments to pass to the tool (must include 'op' for grouped tools).",
12
+ properties: {},
13
+ additionalProperties: true,
14
+ }),
15
+ description: optionalSchema(stringSchema({ description: "Optional label for this command in the result.", maxLength: 128 })),
16
+ },
17
+ required: ["tool", "args"],
18
+ additionalProperties: false,
19
+ });
20
+ const executeBatchArgsSchema = objectSchema({
21
+ description: "Execute multiple tool calls in one request.",
22
+ properties: {
23
+ commands: arraySchema(commandSchema, {
24
+ description: "Ordered list of commands to execute.",
25
+ minItems: 1,
26
+ }),
27
+ stopOnError: optionalSchema(booleanSchema({
28
+ description: "Halt the batch after the first failure. Default: true.",
29
+ default: true,
30
+ })),
31
+ },
32
+ required: ["commands"],
33
+ additionalProperties: false,
34
+ });
35
+ export const batchModuleGroup = defineToolModule({
36
+ domain: "batch",
37
+ summary: "Execute multiple tool calls in a single MCP request for reduced round-trip overhead.",
38
+ supportedPlatforms: ["c64u", "vice"],
39
+ resources: [],
40
+ prompts: [],
41
+ defaultTags: ["batch", "orchestration"],
42
+ workflowHints: [
43
+ "Use execute_batch when you need to run 3 or more sequential operations — it eliminates per-call network overhead.",
44
+ "Each command must include 'tool' and 'args'; grouped tools require 'op' inside args.",
45
+ ],
46
+ tools: [
47
+ {
48
+ name: "c64_batch",
49
+ description: "Execute multiple c64bridge tool calls in a single request. Reduces latency for multi-step workflows.",
50
+ summary: "Runs an ordered list of tool commands and returns all results with per-command timing.",
51
+ inputSchema: executeBatchArgsSchema.jsonSchema,
52
+ tags: ["batch", "orchestration", "grouped"],
53
+ examples: [
54
+ {
55
+ name: "Read registers then step",
56
+ description: "Get registers, step one instruction, get registers again",
57
+ arguments: {
58
+ commands: [
59
+ { tool: "c64_debug", args: { op: "get_registers" }, description: "before" },
60
+ { tool: "c64_debug", args: { op: "step", count: 1 }, description: "step" },
61
+ { tool: "c64_debug", args: { op: "get_registers" }, description: "after" },
62
+ ],
63
+ },
64
+ },
65
+ {
66
+ name: "Clear screen and type text",
67
+ description: "Fill screen RAM then feed keyboard",
68
+ arguments: {
69
+ commands: [
70
+ { tool: "c64_memory", args: { op: "fill_memory", address: "$0400", length: 1000, pattern: "20" } },
71
+ { tool: "c64_input", args: { op: "write_text", text: "HELLO{RETURN}" } },
72
+ ],
73
+ },
74
+ },
75
+ ],
76
+ execute: async (rawArgs, ctx) => {
77
+ try {
78
+ const parsed = executeBatchArgsSchema.parse(rawArgs);
79
+ const stopOnError = parsed.stopOnError !== false;
80
+ // Lazy import to avoid circular dependency at module-init time.
81
+ const { toolRegistry } = await import("./index.js");
82
+ const results = [];
83
+ let succeeded = 0;
84
+ let failed = 0;
85
+ const batchStart = Date.now();
86
+ for (let i = 0; i < parsed.commands.length; i++) {
87
+ const cmd = parsed.commands[i];
88
+ const toolName = typeof cmd.tool === "string" ? cmd.tool : "";
89
+ const args = (typeof cmd.args === "object" && cmd.args !== null) ? cmd.args : {};
90
+ const label = typeof cmd.description === "string" ? cmd.description : undefined;
91
+ if (!toolName) {
92
+ const err = { index: i, tool: String(cmd.tool), description: label, success: false, error: "tool name must be a non-empty string", elapsedMs: 0 };
93
+ results.push(err);
94
+ failed++;
95
+ if (stopOnError)
96
+ break;
97
+ continue;
98
+ }
99
+ if (toolName === "c64_batch") {
100
+ results.push({
101
+ index: i,
102
+ tool: toolName,
103
+ description: label,
104
+ success: false,
105
+ error: "Nested c64_batch execution is not supported.",
106
+ elapsedMs: 0,
107
+ });
108
+ failed++;
109
+ if (stopOnError)
110
+ break;
111
+ continue;
112
+ }
113
+ const cmdStart = Date.now();
114
+ try {
115
+ const toolResult = await toolRegistry.invoke(toolName, args, {
116
+ ...ctx,
117
+ platform: getPlatformStatus(),
118
+ });
119
+ const elapsedMs = Date.now() - cmdStart;
120
+ const isError = toolResult.isError === true;
121
+ const text = toolResult.content.map((c) => c.text).join("\n");
122
+ const resultPayload = toolResult.structuredContent?.data ?? text;
123
+ if (isError) {
124
+ results.push({ index: i, tool: toolName, description: label, success: false, error: text, elapsedMs });
125
+ failed++;
126
+ if (stopOnError)
127
+ break;
128
+ }
129
+ else {
130
+ results.push({ index: i, tool: toolName, description: label, success: true, result: resultPayload, elapsedMs });
131
+ succeeded++;
132
+ }
133
+ }
134
+ catch (err) {
135
+ const elapsedMs = Date.now() - cmdStart;
136
+ const message = err instanceof Error ? err.message : String(err);
137
+ results.push({ index: i, tool: toolName, description: label, success: false, error: message, elapsedMs });
138
+ failed++;
139
+ if (stopOnError)
140
+ break;
141
+ }
142
+ }
143
+ const totalElapsedMs = Date.now() - batchStart;
144
+ const summary = {
145
+ total: parsed.commands.length,
146
+ executed: results.length,
147
+ succeeded,
148
+ failed,
149
+ elapsedMs: totalElapsedMs,
150
+ results,
151
+ };
152
+ ctx.logger.info("Batch complete", { total: parsed.commands.length, succeeded, failed, elapsedMs: totalElapsedMs });
153
+ return jsonResult(summary, { success: failed === 0, succeeded, failed, total: parsed.commands.length });
154
+ }
155
+ catch (error) {
156
+ if (error instanceof ToolValidationError) {
157
+ return {
158
+ content: [{ type: "text", text: error.message }],
159
+ isError: true,
160
+ };
161
+ }
162
+ return unknownErrorResult(error);
163
+ }
164
+ },
165
+ },
166
+ ],
167
+ });
@@ -151,7 +151,7 @@ const configOperationHandlers = createOperationHandlers(configOperations);
151
151
  export const configModuleGroup = defineToolModule({
152
152
  domain: "config",
153
153
  summary: "Grouped configuration management, diagnostics, and snapshot workflows.",
154
- resources: ["c64://context/bootstrap", "c64://docs/index"],
154
+ resources: ["c64://guide/bootstrap", "c64://guide/index"],
155
155
  prompts: ["memory-debug"],
156
156
  defaultTags: ["config", "diagnostics"],
157
157
  supportedPlatforms: ["c64u", "vice"],
@@ -252,7 +252,7 @@ export const diskModuleGroup = defineToolModule({
252
252
  domain: "storage",
253
253
  summary: "Grouped disk image management, mounting, and discovery tools.",
254
254
  supportedPlatforms: ["c64u", "vice"],
255
- resources: ["c64://context/bootstrap"],
255
+ resources: ["c64://guide/bootstrap"],
256
256
  prompts: ["drive-management"],
257
257
  defaultTags: ["storage", "drive"],
258
258
  workflowHints: [
@@ -34,7 +34,7 @@ export const driveModuleGroup = defineToolModule({
34
34
  domain: "drive",
35
35
  summary: "Grouped drive power, reset, ROM, and mode helpers.",
36
36
  supportedPlatforms: ["c64u", "vice"],
37
- resources: ["c64://context/bootstrap"],
37
+ resources: ["c64://guide/bootstrap"],
38
38
  defaultTags: ["drive", "hardware"],
39
39
  workflowHints: [
40
40
  "State the resulting power/mode/ROM so the user can reconcile IEC behaviour.",
@@ -33,7 +33,7 @@ const extractOperationHandlers = createOperationHandlers(extractOperations);
33
33
  export const extractModule = defineToolModule({
34
34
  domain: "extract",
35
35
  summary: "Grouped extraction helpers for sprites, charsets, memory dumps, and diagnostics.",
36
- resources: ["c64://context/bootstrap", "c64://specs/basic", "c64://specs/assembly"],
36
+ resources: ["c64://guide/bootstrap", "c64://basic/spec", "c64://assembly/6510-spec"],
37
37
  defaultTags: ["extract", "diagnostics"],
38
38
  workflowHints: [
39
39
  "Pause the machine when advised so dumps and sprite scans remain stable.",
@@ -3,8 +3,8 @@ import { createOperationDispatcher, defineToolModule, discriminatedUnionSchema,
3
3
  import { graphicsModule, graphicsOperationHandlers as groupedGraphicsHandlers } from "../graphics.js";
4
4
  import { booleanSchema, literalSchema, numberSchema, objectSchema, optionalSchema, stringSchema, } from "../schema.js";
5
5
  import { buildDescriptorIndex, ensureDescriptor, extendSchemaWithOp, createOperationHandlers, } from "./utils.js";
6
- import { ToolError, toolErrorResult, unknownErrorResult, } from "../errors.js";
7
6
  import { jsonResult } from "../responses.js";
7
+ import { ToolError, toolErrorResult, unknownErrorResult, } from "../errors.js";
8
8
  const graphicsDescriptorIndex = buildDescriptorIndex(graphicsModule);
9
9
  const captureFrameArgsSchema = objectSchema({
10
10
  description: "Capture one or more complete video frames from the active backend.",
@@ -30,6 +30,72 @@ const captureFrameArgsSchema = objectSchema({
30
30
  required: ["op"],
31
31
  additionalProperties: false,
32
32
  });
33
+ // ---------------------------------------------------------------------------
34
+ // get_display_state helpers — VIC-II register decoding
35
+ // ---------------------------------------------------------------------------
36
+ function parseVicState(d011, d016, d018, dd00, d020, d021) {
37
+ const bank = (~dd00) & 0x03; // CIA2 Port A bits 0-1 select VIC bank (inverted)
38
+ const bankBase = bank * 0x4000;
39
+ const bitmapMode = (d011 & 0x20) !== 0;
40
+ const extendedColor = (d011 & 0x40) !== 0;
41
+ const multicolor = (d016 & 0x10) !== 0;
42
+ const screenVisible = (d011 & 0x10) !== 0;
43
+ const raster8 = (d011 & 0x80) !== 0;
44
+ const vertScroll = d011 & 0x07;
45
+ const horizScroll = d016 & 0x07;
46
+ const columns40 = (d016 & 0x08) !== 0;
47
+ const rows25 = (d011 & 0x08) !== 0;
48
+ // D018 bits 4-7 = screen RAM offset (× $0400); bits 1-3 = char/bitmap pointer (× $0800)
49
+ const screenRamOffset = ((d018 >> 4) & 0x0f) * 0x0400;
50
+ const screenRamAddress = bankBase + screenRamOffset;
51
+ const charOrBitmapPointer = ((d018 >> 1) & 0x07) * 0x0800;
52
+ const charOrBitmapAddress = bankBase + charOrBitmapPointer;
53
+ let mode;
54
+ if (bitmapMode && multicolor)
55
+ mode = "multicolor_bitmap";
56
+ else if (bitmapMode)
57
+ mode = "hires_bitmap";
58
+ else if (extendedColor)
59
+ mode = "extended_color_text";
60
+ else if (multicolor)
61
+ mode = "multicolor_text";
62
+ else
63
+ mode = "standard_text";
64
+ return {
65
+ mode,
66
+ bank,
67
+ bankBase: `$${bankBase.toString(16).toUpperCase().padStart(4, "0")}`,
68
+ screenRamAddress: `$${screenRamAddress.toString(16).toUpperCase().padStart(4, "0")}`,
69
+ charOrBitmapAddress: `$${charOrBitmapAddress.toString(16).toUpperCase().padStart(4, "0")}`,
70
+ screenVisible,
71
+ bitmapMode,
72
+ extendedColor,
73
+ multicolor,
74
+ rows25,
75
+ columns40,
76
+ vertScroll,
77
+ horizScroll,
78
+ raster8,
79
+ borderColor: d020 & 0x0f,
80
+ backgroundColor: d021 & 0x0f,
81
+ registers: {
82
+ d011: `$${d011.toString(16).toUpperCase().padStart(2, "0")}`,
83
+ d016: `$${d016.toString(16).toUpperCase().padStart(2, "0")}`,
84
+ d018: `$${d018.toString(16).toUpperCase().padStart(2, "0")}`,
85
+ d020: `$${d020.toString(16).toUpperCase().padStart(2, "0")}`,
86
+ d021: `$${d021.toString(16).toUpperCase().padStart(2, "0")}`,
87
+ dd00: `$${dd00.toString(16).toUpperCase().padStart(2, "0")}`,
88
+ },
89
+ };
90
+ }
91
+ const getDisplayStateArgsSchema = objectSchema({
92
+ description: "Read VIC-II and CIA2 registers to determine the current graphics mode and memory layout. Works on C64U and VICE; both backends are read through shared memory primitives so the response shape is identical.",
93
+ properties: {
94
+ op: literalSchema("get_display_state"),
95
+ },
96
+ required: ["op"],
97
+ additionalProperties: false,
98
+ });
33
99
  const graphicsOperations = [
34
100
  {
35
101
  op: "capture_frame",
@@ -105,12 +171,41 @@ const graphicsOperations = [
105
171
  schema: extendSchemaWithOp("render_bitmap", ensureDescriptor(graphicsDescriptorIndex, "render_bitmap").inputSchema, { description: "Import an image file, convert it to VIC-II bitmap memory, write it into RAM, and display it." }),
106
172
  handler: groupedGraphicsHandlers.render_bitmap,
107
173
  },
174
+ {
175
+ op: "get_display_state",
176
+ schema: getDisplayStateArgsSchema.jsonSchema,
177
+ handler: async (_rawArgs, ctx) => {
178
+ try {
179
+ // Use cross-platform memory reads so the same handler works on both
180
+ // C64U and VICE. The VIC-II and CIA2 registers live in machine memory
181
+ // either way; the underlying facade hides the transport difference.
182
+ const vicRegs = await ctx.client.readMemoryRaw(0xD011, 8);
183
+ const colorRegs = await ctx.client.readMemoryRaw(0xD020, 2);
184
+ const cia2 = await ctx.client.readMemoryRaw(0xDD00, 1);
185
+ const d011 = vicRegs[0] ?? 0;
186
+ const d016 = vicRegs[5] ?? 0; // $D016 = $D011 + 5
187
+ const d018 = vicRegs[7] ?? 0; // $D018 = $D011 + 7
188
+ const d020 = colorRegs[0] ?? 0;
189
+ const d021 = colorRegs[1] ?? 0;
190
+ const dd00 = cia2[0] ?? 0x03;
191
+ const backend = await ctx.client.getActiveBackendType();
192
+ const state = { ...parseVicState(d011, d016, d018, dd00, d020, d021), backend };
193
+ ctx.logger.info("Read VIC-II display state", { mode: state.mode, bank: state.bank, backend });
194
+ return jsonResult(state, { success: true, mode: state.mode, backend });
195
+ }
196
+ catch (error) {
197
+ if (error instanceof ToolError)
198
+ return toolErrorResult(error);
199
+ return unknownErrorResult(error);
200
+ }
201
+ },
202
+ },
108
203
  ];
109
204
  const graphicsOperationHandlers = createOperationHandlers(graphicsOperations);
110
205
  export const graphicsModuleGroup = defineToolModule({
111
206
  domain: "graphics",
112
207
  summary: "Grouped frame capture and graphics rendering helpers.",
113
- resources: ["c64://specs/vic", "c64://specs/basic", "c64://specs/assembly"],
208
+ resources: ["c64://graphics/vic/spec", "c64://basic/spec", "c64://assembly/6510-spec"],
114
209
  prompts: ["graphics-demo", "basic-program", "assembly-program"],
115
210
  defaultTags: ["graphics", "vic"],
116
211
  workflowHints: [
@@ -154,6 +249,11 @@ export const graphicsModuleGroup = defineToolModule({
154
249
  description: "Convert a PNG into VIC bitmap memory and display it",
155
250
  arguments: { op: "render_bitmap", imagePath: "./artifacts/sample.png", format: "hires" },
156
251
  },
252
+ {
253
+ name: "Read VIC-II state",
254
+ description: "Decode graphics mode, memory layout, and colours from VIC-II registers (cross-platform)",
255
+ arguments: { op: "get_display_state" },
256
+ },
157
257
  ],
158
258
  execute: createOperationDispatcher("c64_graphics", graphicsOperationHandlers),
159
259
  },
@@ -14,6 +14,8 @@ import { extractModule } from "./extract.js";
14
14
  import { streamModule } from "./stream.js";
15
15
  import { debugModuleGroup as debugModule } from "../debug.js";
16
16
  import { viceModuleGroup as viceModule } from "../vice.js";
17
+ import { inputModuleGroup as inputModule } from "./input.js";
18
+ import { batchModuleGroup as batchModule } from "./batch.js";
17
19
  const modules = [
18
20
  programModule,
19
21
  memoryModule,
@@ -30,6 +32,8 @@ const modules = [
30
32
  streamModule,
31
33
  debugModule,
32
34
  viceModule,
35
+ inputModule,
36
+ batchModule,
33
37
  ];
34
38
  const toolMap = new Map();
35
39
  for (const module of modules) {
@@ -0,0 +1,2 @@
1
+ import { inputModule } from "../input.js";
2
+ export { inputModule as inputModuleGroup };