c64bridge 0.8.1 → 0.9.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 (77) hide show
  1. package/.github/prompts/steer.prompt.md +55 -0
  2. package/AGENTS.md +9 -7
  3. package/README.md +139 -95
  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 +921 -0
  12. package/dist/.tsbuildinfo +1 -1
  13. package/dist/device.js +86 -10
  14. package/dist/mcp-server.js +1 -1
  15. package/dist/prompts/registry.js +27 -25
  16. package/dist/rag/knowledgeIndex.js +112 -79
  17. package/dist/rag/resourceUris.js +93 -0
  18. package/dist/tools/audio.js +11 -11
  19. package/dist/tools/debug.js +1 -1
  20. package/dist/tools/developer.js +13 -13
  21. package/dist/tools/graphics.js +10 -10
  22. package/dist/tools/machineControl.js +5 -5
  23. package/dist/tools/memory.js +19 -11
  24. package/dist/tools/meta/index.js +1 -1
  25. package/dist/tools/printer.js +11 -12
  26. package/dist/tools/programRunners.js +16 -16
  27. package/dist/tools/rag.js +9 -9
  28. package/dist/tools/registry/config.js +1 -1
  29. package/dist/tools/registry/disk.js +1 -1
  30. package/dist/tools/registry/drive.js +1 -1
  31. package/dist/tools/registry/extract.js +1 -1
  32. package/dist/tools/registry/graphics.js +1 -1
  33. package/dist/tools/registry/memory.js +1 -1
  34. package/dist/tools/registry/platform.js +1 -1
  35. package/dist/tools/registry/printer.js +1 -1
  36. package/dist/tools/registry/program.js +1 -1
  37. package/dist/tools/registry/rag.js +1 -1
  38. package/dist/tools/registry/sound.js +1 -1
  39. package/dist/tools/registry/stream.js +1 -1
  40. package/dist/tools/registry/system.js +1 -1
  41. package/dist/tools/storage.js +15 -15
  42. package/dist/tools/streaming.js +3 -3
  43. package/dist/tools/types.js +41 -4
  44. package/dist/tools/vice.js +2 -5
  45. package/dist/vice/process.js +79 -2
  46. package/dist/vice/readiness.js +35 -0
  47. package/dist/vice/smokeOptions.js +44 -0
  48. package/dist/viceRunner.js +2 -0
  49. package/doc/plans/prompts/README.md +7 -7
  50. package/mcp/prompts.json +42 -59
  51. package/mcp/protocol-examples.json +2 -2
  52. package/mcp/resources.json +179 -166
  53. package/mcp/schemas/c64_config.schema.json +109 -326
  54. package/mcp/schemas/c64_debug.schema.json +158 -337
  55. package/mcp/schemas/c64_disk.schema.json +180 -262
  56. package/mcp/schemas/c64_drive.schema.json +31 -111
  57. package/mcp/schemas/c64_extract.schema.json +193 -291
  58. package/mcp/schemas/c64_graphics.schema.json +208 -308
  59. package/mcp/schemas/c64_memory.schema.json +94 -144
  60. package/mcp/schemas/c64_printer.schema.json +151 -205
  61. package/mcp/schemas/c64_program.schema.json +229 -367
  62. package/mcp/schemas/c64_rag.schema.json +25 -59
  63. package/mcp/schemas/c64_select_backend.schema.json +19 -26
  64. package/mcp/schemas/c64_sound.schema.json +346 -622
  65. package/mcp/schemas/c64_stream.schema.json +25 -54
  66. package/mcp/schemas/c64_system.schema.json +79 -212
  67. package/mcp/schemas/c64_vice.schema.json +24 -55
  68. package/mcp/server.json +1 -1
  69. package/mcp/tools.json +1935 -3443
  70. package/mcp.json +3 -3
  71. package/package.json +17 -14
  72. package/scripts/fetch-external-sources.mjs +1 -2
  73. package/scripts/run-coverage.mjs +26 -1
  74. package/scripts/run-tests.mjs +2 -2
  75. package/scripts/run-tests.ts +273 -52
  76. package/scripts/update-readme.ts +54 -2
  77. package/server.json +2 -2
package/dist/device.js CHANGED
@@ -18,6 +18,18 @@ const DEFAULT_VICE_PORT = 6502;
18
18
  function delay(ms) {
19
19
  return new Promise((resolve) => setTimeout(resolve, ms));
20
20
  }
21
+ async function resumeViceMonitor(client, timeoutMs = 250) {
22
+ try {
23
+ const exitMonitor = client.exitMonitor().catch(() => { });
24
+ await Promise.race([
25
+ exitMonitor,
26
+ delay(timeoutMs),
27
+ ]);
28
+ }
29
+ catch {
30
+ // Ignore resume failures while tearing down a monitor session.
31
+ }
32
+ }
21
33
  function coalesceMemoryWriteBlocks(blocks) {
22
34
  const merged = [];
23
35
  for (const block of blocks) {
@@ -215,12 +227,18 @@ export class ViceBackend {
215
227
  const configBinary = config.exe !== undefined
216
228
  ? configuredString(config.exe) ?? (typeof config.exe === "string" ? config.exe : String(config.exe))
217
229
  : undefined;
218
- this.exe = resolveViceBinary({ envBinary, configBinary });
230
+ const resolvedLaunch = resolveViceLaunch({
231
+ envBinary,
232
+ configBinary,
233
+ envDirectory: configuredString(process.env.VICE_DIRECTORY),
234
+ configDirectory: configuredString(config.directory),
235
+ });
236
+ this.exe = resolvedLaunch.binary;
219
237
  const envHost = normaliseViceHost(process.env.VICE_HOST);
220
238
  const envPort = normaliseVicePort(process.env.VICE_PORT);
221
239
  this.host = firstDefined(envHost, normaliseViceHost(config.host)) ?? DEFAULT_VICE_HOST;
222
240
  this.port = firstDefined(envPort, normaliseVicePort(config.port)) ?? DEFAULT_VICE_PORT;
223
- this.directory = firstDefined(resolveViceDirectory(configuredString(process.env.VICE_DIRECTORY)), resolveViceDirectory(configuredString(config.directory)), findViceResourceDirectory(this.exe));
241
+ this.directory = resolvedLaunch.directory;
224
242
  this.mockMode = (process.env.VICE_TEST_TARGET || "").toLowerCase() === "mock";
225
243
  const hostLower = this.host.toLowerCase();
226
244
  const isLocal = hostLower === "127.0.0.1" || hostLower === "localhost";
@@ -238,6 +256,10 @@ export class ViceBackend {
238
256
  console.error("[vice-backend] probing existing VICE", this.host, this.port);
239
257
  await client.connect(this.port, this.host);
240
258
  await client.info();
259
+ try {
260
+ await client.exitMonitor();
261
+ }
262
+ catch { }
241
263
  if (this.debugEnabled)
242
264
  console.error("[vice-backend] existing VICE is reachable");
243
265
  return true;
@@ -257,6 +279,10 @@ export class ViceBackend {
257
279
  try {
258
280
  await client.connect(this.port, this.host);
259
281
  await client.info();
282
+ try {
283
+ await client.exitMonitor();
284
+ }
285
+ catch { }
260
286
  return;
261
287
  }
262
288
  catch (error) {
@@ -275,6 +301,7 @@ export class ViceBackend {
275
301
  const client = new ViceClient();
276
302
  try {
277
303
  await client.connect(this.port, this.host);
304
+ await client.reset();
278
305
  const readiness = await waitForBasicReady(client, { timeoutMs, ensurePrompt: true });
279
306
  if (!readiness.pointersOk || !readiness.promptOk) {
280
307
  throw new Error(`VICE machine at ${this.host}:${this.port} did not reach the BASIC prompt within ${timeoutMs}ms`);
@@ -358,7 +385,7 @@ export class ViceBackend {
358
385
  ViceBackend.supervisors.clear();
359
386
  });
360
387
  }
361
- async withClient(fn) {
388
+ async withClient(fn, options) {
362
389
  const previous = this.monitorQueue;
363
390
  let releaseQueue = () => { };
364
391
  this.monitorQueue = new Promise((resolve) => {
@@ -385,6 +412,9 @@ export class ViceBackend {
385
412
  return await fn(client);
386
413
  }
387
414
  finally {
415
+ if (options?.resumeOnClose !== false) {
416
+ await resumeViceMonitor(client);
417
+ }
388
418
  if (this.debugEnabled)
389
419
  console.error("[vice-backend] closing VICE monitor connection");
390
420
  client.close();
@@ -532,7 +562,7 @@ export class ViceBackend {
532
562
  catch {
533
563
  // Ignore transport errors during quit; the emulator will terminate regardless.
534
564
  }
535
- });
565
+ }, { resumeOnClose: false });
536
566
  if (managedHandle) {
537
567
  try {
538
568
  await managedHandle.stop();
@@ -775,9 +805,45 @@ function resolveViceBinary(options, findBinary = which) {
775
805
  }
776
806
  return findBinary("x64sc") ?? findBinary("x64") ?? "x64sc";
777
807
  }
808
+ function resolveViceLaunch(options, dependencies = {}) {
809
+ const findBinary = dependencies.findBinary ?? which;
810
+ const isResourceDirectory = dependencies.isResourceDirectory ?? isViceResourceDirectory;
811
+ const explicitBinary = firstDefined(options.envBinary, options.configBinary);
812
+ const explicitDirectory = firstDefined(resolveViceDirectoryWithValidator(options.envDirectory, isResourceDirectory), resolveViceDirectoryWithValidator(options.configDirectory, isResourceDirectory));
813
+ let binary = resolveViceBinary({ envBinary: options.envBinary, configBinary: options.configBinary }, findBinary);
814
+ if (explicitDirectory) {
815
+ return { binary, directory: explicitDirectory };
816
+ }
817
+ const adjacentDirectory = findViceResourceDirectory(binary, {
818
+ allowGlobalFallback: false,
819
+ isResourceDirectory,
820
+ });
821
+ if (adjacentDirectory) {
822
+ return { binary, directory: adjacentDirectory };
823
+ }
824
+ if (explicitBinary) {
825
+ return {
826
+ binary,
827
+ directory: findViceResourceDirectory(binary, {
828
+ allowGlobalFallback: true,
829
+ isResourceDirectory,
830
+ }),
831
+ };
832
+ }
833
+ return {
834
+ binary,
835
+ directory: findViceResourceDirectory(binary, {
836
+ allowGlobalFallback: true,
837
+ isResourceDirectory,
838
+ }),
839
+ };
840
+ }
778
841
  export function __resolveViceBinaryForTests(options, findBinary) {
779
842
  return resolveViceBinary(options, findBinary ?? which);
780
843
  }
844
+ export function __resolveViceLaunchForTests(options, dependencies) {
845
+ return resolveViceLaunch(options, dependencies);
846
+ }
781
847
  export async function createFacade(logger, options) {
782
848
  // Caller-forced preference: use c64u with provided base URL (used by tests and server wiring)
783
849
  if (options?.preferredC64uBaseUrl) {
@@ -935,7 +1001,15 @@ function resolveViceDirectory(value) {
935
1001
  }
936
1002
  return isViceResourceDirectory(input) ? input : undefined;
937
1003
  }
938
- function findViceResourceDirectory(binaryPath) {
1004
+ function resolveViceDirectoryWithValidator(value, isResourceDirectory) {
1005
+ const input = configuredString(value);
1006
+ if (!input) {
1007
+ return undefined;
1008
+ }
1009
+ return isResourceDirectory(input) ? input : undefined;
1010
+ }
1011
+ function findViceResourceDirectory(binaryPath, options = {}) {
1012
+ const isResourceDirectory = options.isResourceDirectory ?? isViceResourceDirectory;
939
1013
  const candidates = new Set();
940
1014
  const resolvedBinary = configuredString(binaryPath);
941
1015
  if (resolvedBinary) {
@@ -943,12 +1017,14 @@ function findViceResourceDirectory(binaryPath) {
943
1017
  candidates.add(path.resolve(binaryDir, "..", "share", "vice"));
944
1018
  candidates.add(path.resolve(binaryDir, "..", "..", "share", "vice"));
945
1019
  }
946
- candidates.add("/usr/local/share/vice");
947
- candidates.add("/usr/share/vice");
948
- candidates.add("/opt/homebrew/share/vice");
949
- candidates.add("/opt/local/share/vice");
1020
+ if (options.allowGlobalFallback !== false) {
1021
+ candidates.add("/usr/local/share/vice");
1022
+ candidates.add("/usr/share/vice");
1023
+ candidates.add("/opt/homebrew/share/vice");
1024
+ candidates.add("/opt/local/share/vice");
1025
+ }
950
1026
  for (const candidate of candidates) {
951
- if (isViceResourceDirectory(candidate)) {
1027
+ if (isResourceDirectory(candidate)) {
952
1028
  return candidate;
953
1029
  }
954
1030
  }
@@ -8,6 +8,7 @@ import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSche
8
8
  import { loadConfig } from "./config.js";
9
9
  import { C64Client } from "./c64Client.js";
10
10
  import { listKnowledgeResources, readKnowledgeResource, } from "./rag/knowledgeIndex.js";
11
+ import { PLATFORM_RESOURCE_URI } from "./rag/resourceUris.js";
11
12
  import { createLazyRagRetriever, initRag } from "./rag/init.js";
12
13
  import { getMcpServerImplementationInfo } from "./mcp/metadata.js";
13
14
  import { toolRegistry } from "./tools/registry/index.js";
@@ -417,7 +418,6 @@ async function main() {
417
418
  diagnosticsFile: diagnostics.filePath,
418
419
  });
419
420
  }
420
- const PLATFORM_RESOURCE_URI = "c64://platform/status";
421
421
  function createPlatformResourceDescriptor() {
422
422
  return {
423
423
  uri: PLATFORM_RESOURCE_URI,
@@ -1,4 +1,5 @@
1
1
  import { listKnowledgeResources } from "../rag/knowledgeIndex.js";
2
+ import { RESOURCE_URIS } from "../rag/resourceUris.js";
2
3
  import { toolRegistry } from "../tools/registry/index.js";
3
4
  const ASSEMBLY_HARDWARE_NOTES = {
4
5
  sid: [
@@ -34,6 +35,7 @@ const ROUTING_CORE_SEGMENT = {
34
35
  content: [
35
36
  "Prompts in this repository define intent and routing only.",
36
37
  "Execution logic, validation steps, and safety rules live exclusively in `.github/skills/*/SKILL.md`.",
38
+ `When the request targets the \`vice\` backend, read \`${RESOURCE_URIS.vice.binaryMonitorSpec}\` before executing the skill so Binary Monitor single-client behaviour, trap/resume semantics, and monitor-side side effects stay in scope.`,
37
39
  "Use the referenced skill, extract missing inputs from the user request, execute the skill, and summarize the outcome.",
38
40
  ].join("\n"),
39
41
  };
@@ -115,7 +117,7 @@ export function createPromptRegistry() {
115
117
  name: "hello-world",
116
118
  title: "Hello World Workflow",
117
119
  description: "Route ultra-fast hello-world and smoke-test requests to the canonical greeting skill.",
118
- requiredResources: ["c64://context/bootstrap", "c64://context/fast-paths", "c64://docs/index"],
120
+ requiredResources: [RESOURCE_URIS.guide.bootstrap, RESOURCE_URIS.guide.fastPaths, RESOURCE_URIS.guide.index],
119
121
  optionalResources: [],
120
122
  tools: ["c64_program"],
121
123
  tags: ["hello", "demo", "greeting", "smoke-test"],
@@ -134,8 +136,8 @@ export function createPromptRegistry() {
134
136
  name: "basic-program",
135
137
  title: "BASIC Program Workflow",
136
138
  description: "Route bespoke Commodore BASIC v2 requests to the canonical BASIC skill.",
137
- requiredResources: ["c64://specs/basic", "c64://context/bootstrap", "c64://docs/index"],
138
- optionalResources: ["c64://context/fast-paths"],
139
+ requiredResources: [RESOURCE_URIS.basic.spec, RESOURCE_URIS.guide.bootstrap, RESOURCE_URIS.guide.index],
140
+ optionalResources: [RESOURCE_URIS.guide.fastPaths],
139
141
  tools: ["c64_program", "c64_memory"],
140
142
  tags: ["basic", "program"],
141
143
  },
@@ -150,7 +152,7 @@ export function createPromptRegistry() {
150
152
  name: "cross-platform-demo",
151
153
  title: "Cross-Platform Demo Workflow",
152
154
  description: "Route quick visible demo requests to the cross-platform demo skill.",
153
- requiredResources: ["c64://context/bootstrap", "c64://context/fast-paths", "c64://docs/index"],
155
+ requiredResources: [RESOURCE_URIS.guide.bootstrap, RESOURCE_URIS.guide.fastPaths, RESOURCE_URIS.guide.index],
154
156
  optionalResources: [],
155
157
  tools: ["c64_program"],
156
158
  tags: ["demo", "platform", "greeting"],
@@ -166,8 +168,8 @@ export function createPromptRegistry() {
166
168
  name: "preset-music-demo",
167
169
  title: "Preset Music Demo Workflow",
168
170
  description: "Route quick recognizable tune requests to the SID music skill.",
169
- requiredResources: ["c64://specs/sid", "c64://specs/sidwave", "c64://context/fast-paths"],
170
- optionalResources: ["c64://docs/sid/best-practices"],
171
+ requiredResources: [RESOURCE_URIS.sound.sid.spec, RESOURCE_URIS.sound.sidwave.spec, RESOURCE_URIS.guide.fastPaths],
172
+ optionalResources: [RESOURCE_URIS.sound.sid.bestPractices],
171
173
  tools: ["c64_sound"],
172
174
  tags: ["sid", "music", "demo"],
173
175
  },
@@ -181,8 +183,8 @@ export function createPromptRegistry() {
181
183
  name: "assembly-program",
182
184
  title: "Assembly Program Workflow",
183
185
  description: "Route 6502/6510 routine requests to the canonical assembly skill.",
184
- requiredResources: ["c64://specs/assembly", "c64://specs/vic", "c64://specs/sid", "c64://context/bootstrap"],
185
- optionalResources: ["c64://docs/sid/best-practices"],
186
+ requiredResources: [RESOURCE_URIS.assembly.spec6510, RESOURCE_URIS.graphics.vic.spec, RESOURCE_URIS.sound.sid.spec, RESOURCE_URIS.guide.bootstrap],
187
+ optionalResources: [RESOURCE_URIS.sound.sid.bestPractices],
186
188
  tools: ["c64_program", "c64_memory"],
187
189
  tags: ["assembly", "program"],
188
190
  },
@@ -201,7 +203,7 @@ export function createPromptRegistry() {
201
203
  selectOptionalResources: (args) => {
202
204
  const hardware = args.hardware;
203
205
  return hardware === "sid" || hardware === "multi"
204
- ? ["c64://docs/sid/best-practices"]
206
+ ? [RESOURCE_URIS.sound.sid.bestPractices]
205
207
  : [];
206
208
  },
207
209
  renderRoutingNotes: (args) => {
@@ -220,12 +222,12 @@ export function createPromptRegistry() {
220
222
  title: "SID Composition Workflow",
221
223
  description: "Route SID playback and composition work to the canonical SID skill.",
222
224
  requiredResources: [
223
- "c64://specs/sid",
224
- "c64://specs/sidwave",
225
- "c64://docs/sid/file-structure",
226
- "c64://docs/sid/best-practices",
225
+ RESOURCE_URIS.sound.sid.spec,
226
+ RESOURCE_URIS.sound.sidwave.spec,
227
+ RESOURCE_URIS.sound.sid.fileFormat,
228
+ RESOURCE_URIS.sound.sid.bestPractices,
227
229
  ],
228
- optionalResources: ["c64://context/fast-paths"],
230
+ optionalResources: [RESOURCE_URIS.guide.fastPaths],
229
231
  tools: ["c64_sound"],
230
232
  tags: ["sid", "music"],
231
233
  },
@@ -239,8 +241,8 @@ export function createPromptRegistry() {
239
241
  name: "graphics-demo",
240
242
  title: "Graphics Demo Workflow",
241
243
  description: "Route graphics requests to the canonical graphics skill.",
242
- requiredResources: ["c64://specs/vic", "c64://context/bootstrap"],
243
- optionalResources: ["c64://specs/assembly", "c64://specs/charset", "c64://docs/petscii-style"],
244
+ requiredResources: [RESOURCE_URIS.graphics.vic.spec, RESOURCE_URIS.guide.bootstrap],
245
+ optionalResources: [RESOURCE_URIS.assembly.spec6510, RESOURCE_URIS.graphics.characterSet, RESOURCE_URIS.graphics.petscii.styleGuide],
244
246
  tools: ["c64_program", "c64_memory", "c64_graphics"],
245
247
  tags: ["graphics", "vic"],
246
248
  },
@@ -271,12 +273,12 @@ export function createPromptRegistry() {
271
273
  name: "printer-job",
272
274
  title: "Printer Job Workflow",
273
275
  description: "Route printer work to the canonical printer skill.",
274
- requiredResources: ["c64://specs/printer", "c64://docs/printer/guide", "c64://docs/printer/prompts"],
276
+ requiredResources: [RESOURCE_URIS.printer.spec, RESOURCE_URIS.printer.promptGuide],
275
277
  optionalResources: [
276
- "c64://docs/printer/commodore-text",
277
- "c64://docs/printer/commodore-bitmap",
278
- "c64://docs/printer/epson-text",
279
- "c64://docs/printer/epson-bitmap",
278
+ RESOURCE_URIS.printer.commodore.text,
279
+ RESOURCE_URIS.printer.commodore.bitmap,
280
+ RESOURCE_URIS.printer.epson.text,
281
+ RESOURCE_URIS.printer.epson.bitmap,
280
282
  ],
281
283
  tools: ["c64_printer"],
282
284
  tags: ["printer"],
@@ -296,10 +298,10 @@ export function createPromptRegistry() {
296
298
  selectOptionalResources: (args) => {
297
299
  const printerType = args.printerType;
298
300
  if (printerType === "commodore") {
299
- return ["c64://docs/printer/commodore-text", "c64://docs/printer/commodore-bitmap"];
301
+ return [RESOURCE_URIS.printer.commodore.text, RESOURCE_URIS.printer.commodore.bitmap];
300
302
  }
301
303
  if (printerType === "epson") {
302
- return ["c64://docs/printer/epson-text", "c64://docs/printer/epson-bitmap"];
304
+ return [RESOURCE_URIS.printer.epson.text, RESOURCE_URIS.printer.epson.bitmap];
303
305
  }
304
306
  return [];
305
307
  },
@@ -315,7 +317,7 @@ export function createPromptRegistry() {
315
317
  name: "memory-debug",
316
318
  title: "Memory Debug Workflow",
317
319
  description: "Route reversible memory inspection or patching work to the canonical memory skill.",
318
- requiredResources: ["c64://context/bootstrap", "c64://specs/assembly", "c64://docs/index"],
320
+ requiredResources: [RESOURCE_URIS.guide.bootstrap, RESOURCE_URIS.assembly.spec6510, RESOURCE_URIS.guide.index],
319
321
  optionalResources: [],
320
322
  tools: ["c64_memory", "c64_system"],
321
323
  tags: ["memory", "debug"],
@@ -330,7 +332,7 @@ export function createPromptRegistry() {
330
332
  name: "drive-manager",
331
333
  title: "Drive Manager Workflow",
332
334
  description: "Route disk-image and drive-state requests to the canonical drive skill.",
333
- requiredResources: ["c64://context/bootstrap"],
335
+ requiredResources: [RESOURCE_URIS.guide.bootstrap],
334
336
  optionalResources: [],
335
337
  tools: ["c64_disk", "c64_drive"],
336
338
  tags: ["drive", "storage"],