c64bridge 1.0.5 → 1.0.7

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 (58) hide show
  1. package/AGENTS.md +1 -1
  2. package/README.md +3 -3
  3. package/data/sound/sidwave.md +113 -36
  4. package/data/sound/sidwave.schema.json +138 -28
  5. package/dist/.tsbuildinfo +1 -1
  6. package/dist/c64Client.js +83 -93
  7. package/dist/chargen.js +0 -4
  8. package/dist/config.js +26 -6
  9. package/dist/device.js +130 -24
  10. package/dist/diagnostics.js +14 -5
  11. package/dist/logger.js +3 -2
  12. package/dist/loggingHttpClient.js +12 -1
  13. package/dist/mcp-server.js +19 -9
  14. package/dist/petscii.js +0 -5
  15. package/dist/redact.js +166 -0
  16. package/dist/sidwave.js +453 -61
  17. package/dist/sidwaveCompiler.js +1424 -269
  18. package/dist/tools/audio.js +70 -12
  19. package/dist/tools/developer.js +17 -9
  20. package/dist/tools/errors.js +7 -0
  21. package/dist/tools/graphics.js +4 -4
  22. package/dist/tools/input.js +10 -13
  23. package/dist/tools/machineControl.js +7 -7
  24. package/dist/tools/memory.js +6 -6
  25. package/dist/tools/meta/audio.js +8 -2
  26. package/dist/tools/meta/config.js +5 -4
  27. package/dist/tools/meta/configInventory.js +25 -2
  28. package/dist/tools/meta/diagnostics.js +2 -1
  29. package/dist/tools/meta/graphics.js +1 -1
  30. package/dist/tools/meta/memory.js +4 -4
  31. package/dist/tools/meta/program.js +12 -8
  32. package/dist/tools/printer.js +4 -4
  33. package/dist/tools/programRunners.js +8 -8
  34. package/dist/tools/registry/batch.js +1 -7
  35. package/dist/tools/registry/disk.js +7 -5
  36. package/dist/tools/storage.js +21 -17
  37. package/dist/tools/streaming.js +2 -2
  38. package/dist/tools/translation/assembler.js +44 -12
  39. package/dist/tools/translation/basicTokenizer.js +5 -1
  40. package/dist/tools/translation/opcodeInventory.js +1 -1
  41. package/dist/tools/vice.js +1 -1
  42. package/dist/vice/mockServer.js +92 -5
  43. package/dist/vice/readiness.js +4 -9
  44. package/dist/vice/viceClient.js +18 -0
  45. package/doc/audit/secret-handling.md +61 -0
  46. package/mcp/schemas/c64_config.schema.json +19 -2
  47. package/mcp/schemas/c64_disk.schema.json +2 -2
  48. package/mcp/schemas/c64_input.schema.json +3 -3
  49. package/mcp/schemas/c64_program.schema.json +2 -2
  50. package/mcp/schemas/c64_vice.schema.json +3 -3
  51. package/mcp/server.json +1 -1
  52. package/mcp/tools.json +33 -12
  53. package/mcp.json +1 -1
  54. package/package.json +5 -2
  55. package/scripts/mockC64Server.mjs +15 -1
  56. package/scripts/run-coverage.mjs +31 -1
  57. package/scripts/update-readme.ts +3 -2
  58. package/server.json +20 -3
@@ -685,7 +685,21 @@ export async function startMockC64Server(options = {}) {
685
685
  const lengthValue = routeUrl.searchParams.get("length") ?? "256";
686
686
  const address = parseNumeric(addressValue);
687
687
  const length = Math.max(0, parseNumeric(lengthValue, 10));
688
- const bytes = readMemoryWithHeartbeat(state, address, length);
688
+ // Same checks as the firmware (route_machine.cc readmem).
689
+ if (length < 1 || length > 65536) {
690
+ sendJson(res, { errors: ["Invalid length"] }, 400);
691
+ return;
692
+ }
693
+ if (address + length > 65536) {
694
+ sendJson(res, { errors: ["Memory read exceeds location $FFFF"] }, 400);
695
+ return;
696
+ }
697
+ const bytes = Uint8Array.from(readMemoryWithHeartbeat(state, address, length));
698
+ // SID registers $D400-$D418 are write-only; the Ultimate 64 reads them as $00.
699
+ for (let i = 0; i < bytes.length; i += 1) {
700
+ const target = address + i;
701
+ if (target >= 0xd400 && target <= 0xd418) bytes[i] = 0;
702
+ }
689
703
 
690
704
  const accept = String(req.headers["accept"] || "");
691
705
  if (accept.includes("application/octet-stream")) {
@@ -98,7 +98,10 @@ export async function main() {
98
98
  legOutputs.push(legPath);
99
99
  }
100
100
 
101
- const finalReport = mergeReports(await Promise.all(legOutputs.map(readReport)));
101
+ const finalReport = await dropNonCodeLines(
102
+ mergeReports(await Promise.all(legOutputs.map(readReport))),
103
+ (relativePath) => fs.readFile(path.join(repoRoot, relativePath), "utf8"),
104
+ );
102
105
  const finalPath = path.join(coverageDir, "lcov.info");
103
106
  await fs.writeFile(finalPath, serializeReport(finalReport), "utf8");
104
107
 
@@ -265,6 +268,33 @@ function parseReport(text) {
265
268
  return report;
266
269
  }
267
270
 
271
+ /**
272
+ * Bun's LCOV output lists blank and comment-only lines inside executed
273
+ * functions as executable lines that were never hit. Drop them, using each
274
+ * file's source, so coverage counts only code.
275
+ */
276
+ export async function dropNonCodeLines(report, readSource) {
277
+ const result = new Map();
278
+ for (const [filePath, record] of report.entries()) {
279
+ let sourceLines;
280
+ try {
281
+ sourceLines = (await readSource(filePath)).split(/\r?\n/);
282
+ } catch {
283
+ result.set(filePath, record);
284
+ continue;
285
+ }
286
+ const lines = new Map();
287
+ for (const [lineNo, hits] of record.lines.entries()) {
288
+ const text = (sourceLines[lineNo - 1] ?? "").trim();
289
+ const isComment = text.startsWith("//") || text.startsWith("/*") || text.startsWith("*");
290
+ if (text === "" || isComment) continue;
291
+ lines.set(lineNo, hits);
292
+ }
293
+ result.set(filePath, { ...record, lines });
294
+ }
295
+ return result;
296
+ }
297
+
268
298
  function mergeReports(reports) {
269
299
  const merged = new Map();
270
300
  for (const report of reports) {
@@ -485,8 +485,9 @@ export async function updateReadme(): Promise<boolean> {
485
485
  const generatedDocs = `\n\n${buildDocumentation()}\n\n`;
486
486
  const generatedEnv = `\n\n${buildEnvironmentDocumentation()}\n\n`;
487
487
  const nextReadme = readme
488
- .replace(docsPattern, `${START_MARKER}${generatedDocs}${END_MARKER}`)
489
- .replace(envPattern, `${ENV_START_MARKER}${generatedEnv}${ENV_END_MARKER}`);
488
+ // Replacement functions, not strings: generated text such as "$1E" must not be read as "$1".
489
+ .replace(docsPattern, () => `${START_MARKER}${generatedDocs}${END_MARKER}`)
490
+ .replace(envPattern, () => `${ENV_START_MARKER}${generatedEnv}${ENV_END_MARKER}`);
490
491
 
491
492
  if (nextReadme === readme) {
492
493
  return false;
package/server.json CHANGED
@@ -8,12 +8,12 @@
8
8
  "source": "github"
9
9
  },
10
10
  "websiteUrl": "https://github.com/chrisgleissner/c64bridge",
11
- "version": "1.0.5",
11
+ "version": "1.0.7",
12
12
  "packages": [
13
13
  {
14
14
  "registryType": "npm",
15
15
  "identifier": "c64bridge",
16
- "version": "1.0.5",
16
+ "version": "1.0.7",
17
17
  "transport": {
18
18
  "type": "stdio"
19
19
  },
@@ -38,11 +38,12 @@
38
38
  },
39
39
  {
40
40
  "name": "C64_MODE",
41
- "description": "Active backend.",
41
+ "description": "Active backend (c64u/U64 hardware, u2/U2-family cartridge, or vice emulator).",
42
42
  "format": "string",
43
43
  "default": "c64u",
44
44
  "choices": [
45
45
  "c64u",
46
+ "u2",
46
47
  "vice"
47
48
  ]
48
49
  },
@@ -64,6 +65,22 @@
64
65
  "format": "string",
65
66
  "isSecret": true
66
67
  },
68
+ {
69
+ "name": "U2_HOST",
70
+ "description": "U2/U2+/U2+L host name or IP address when C64_MODE=u2.",
71
+ "format": "string"
72
+ },
73
+ {
74
+ "name": "U2_PORT",
75
+ "description": "U2-family REST port when C64_MODE=u2.",
76
+ "format": "number"
77
+ },
78
+ {
79
+ "name": "U2_PASSWORD",
80
+ "description": "U2-family network password sent as X-Password.",
81
+ "format": "string",
82
+ "isSecret": true
83
+ },
67
84
  {
68
85
  "name": "VICE_BINARY",
69
86
  "description": "VICE emulator binary to launch.",