c64bridge 1.0.6 → 1.1.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 (136) hide show
  1. package/.github/skills/assembly-program/SKILL.md +37 -0
  2. package/.github/skills/basic-program/SKILL.md +40 -0
  3. package/.github/skills/cross-platform-demo/SKILL.md +32 -0
  4. package/.github/skills/drive-manager/SKILL.md +33 -0
  5. package/.github/skills/graphics-demo/SKILL.md +30 -0
  6. package/.github/skills/hello-world/SKILL.md +36 -0
  7. package/.github/skills/memory-debug/SKILL.md +32 -0
  8. package/.github/skills/pr-converge/SKILL.md +33 -0
  9. package/.github/skills/printer-job/SKILL.md +30 -0
  10. package/.github/skills/sid-music/SKILL.md +35 -0
  11. package/.github/skills/software-launch/SKILL.md +31 -0
  12. package/.github/skills/stream-control/SKILL.md +29 -0
  13. package/.github/skills/system-control/SKILL.md +32 -0
  14. package/AGENTS.md +28 -7
  15. package/README.md +70 -15
  16. package/data/assembly/assembly-spec.md +2 -0
  17. package/dist/c64Client.js +238 -60
  18. package/dist/chargen.js +0 -4
  19. package/dist/config.js +53 -28
  20. package/dist/device.js +92 -37
  21. package/dist/diagnostics.js +66 -5
  22. package/dist/hostPaths.js +128 -0
  23. package/dist/logger.js +3 -2
  24. package/dist/loggingHttpClient.js +71 -2
  25. package/dist/mcp-server.js +416 -291
  26. package/dist/numericArg.js +55 -0
  27. package/dist/petscii.js +13 -6
  28. package/dist/platform.js +12 -3
  29. package/dist/prompts/registry.js +6 -1
  30. package/dist/redact.js +166 -0
  31. package/dist/tools/access.js +66 -0
  32. package/dist/tools/debug.js +6 -0
  33. package/dist/tools/developer.js +77 -36
  34. package/dist/tools/errors.js +54 -3
  35. package/dist/tools/input.js +25 -8
  36. package/dist/tools/machineControl.js +113 -21
  37. package/dist/tools/memory.js +135 -30
  38. package/dist/tools/meta/artifacts.js +5 -5
  39. package/dist/tools/meta/background.js +216 -54
  40. package/dist/tools/meta/compilation.js +6 -5
  41. package/dist/tools/meta/config.js +20 -10
  42. package/dist/tools/meta/configInventory.js +25 -2
  43. package/dist/tools/meta/diagnostics.js +2 -1
  44. package/dist/tools/meta/filesystem.js +16 -14
  45. package/dist/tools/meta/graphics.js +48 -20
  46. package/dist/tools/meta/memory.js +45 -19
  47. package/dist/tools/meta/program.js +10 -7
  48. package/dist/tools/meta/screen.js +116 -25
  49. package/dist/tools/pauseGuard.js +23 -0
  50. package/dist/tools/pollValidator.js +53 -8
  51. package/dist/tools/programRunners.js +62 -18
  52. package/dist/tools/registry/batch.js +17 -11
  53. package/dist/tools/registry/config.js +9 -2
  54. package/dist/tools/registry/disk.js +12 -2
  55. package/dist/tools/registry/drive.js +3 -0
  56. package/dist/tools/registry/extract.js +3 -0
  57. package/dist/tools/registry/graphics.js +59 -6
  58. package/dist/tools/registry/index.js +17 -1
  59. package/dist/tools/registry/memory.js +11 -1
  60. package/dist/tools/registry/platform.js +4 -1
  61. package/dist/tools/registry/printer.js +2 -1
  62. package/dist/tools/registry/program.js +5 -1
  63. package/dist/tools/registry/rag.js +1 -0
  64. package/dist/tools/registry/sound.js +12 -1
  65. package/dist/tools/registry/stream.js +2 -1
  66. package/dist/tools/registry/system.js +8 -1
  67. package/dist/tools/schema.js +7 -7
  68. package/dist/tools/streaming.js +34 -4
  69. package/dist/tools/translation/assembler.js +247 -24
  70. package/dist/tools/translation/basicDetokenizer.js +80 -0
  71. package/dist/tools/translation/basicTokenizer.js +8 -3
  72. package/dist/tools/translation/disassembler.js +14 -0
  73. package/dist/tools/types.js +258 -7
  74. package/dist/tools/vice.js +5 -16
  75. package/dist/vicBitmap.js +1 -1
  76. package/dist/vice/mockServer.js +3 -0
  77. package/dist/vice/process.js +19 -0
  78. package/dist/vice/resourcePolicy.js +36 -0
  79. package/dist/vice/viceClient.js +22 -5
  80. package/doc/vice/support-matrix.md +17 -11
  81. package/mcp/schemas/c64_batch.schema.json +2 -1
  82. package/mcp/schemas/c64_config.schema.json +33 -466
  83. package/mcp/schemas/c64_debug.schema.json +3 -602
  84. package/mcp/schemas/c64_disk.schema.json +17 -344
  85. package/mcp/schemas/c64_drive.schema.json +2 -157
  86. package/mcp/schemas/c64_extract.schema.json +10 -364
  87. package/mcp/schemas/c64_graphics.schema.json +19 -426
  88. package/mcp/schemas/c64_input.schema.json +5 -318
  89. package/mcp/schemas/c64_memory.schema.json +23 -491
  90. package/mcp/schemas/c64_printer.schema.json +3 -258
  91. package/mcp/schemas/c64_program.schema.json +16 -491
  92. package/mcp/schemas/c64_rag.schema.json +1 -81
  93. package/mcp/schemas/c64_select_backend.schema.json +2 -36
  94. package/mcp/schemas/c64_sound.schema.json +20 -822
  95. package/mcp/schemas/c64_stream.schema.json +4 -75
  96. package/mcp/schemas/c64_system.schema.json +28 -348
  97. package/mcp/schemas/c64_vice.schema.json +2 -71
  98. package/mcp/server.json +1 -1
  99. package/mcp/tools.json +1990 -6900
  100. package/mcp.json +21 -1
  101. package/package.json +19 -8
  102. package/scripts/mockC64Server.mjs +44 -0
  103. package/scripts/run-coverage.mjs +27 -1
  104. package/scripts/smoke-run.sh +3 -2
  105. package/scripts/start.mjs +23 -1
  106. package/server.json +53 -3
  107. package/dist/.tsbuildinfo +0 -1
  108. package/doc/agent-state-spec.md +0 -243
  109. package/doc/plans/bun/baseline-metrics.md +0 -16
  110. package/doc/plans/bun/migration-checklist.md +0 -55
  111. package/doc/plans/bun/migration.md +0 -83
  112. package/doc/plans/improve-knowledge/notes.md +0 -43
  113. package/doc/plans/improve-knowledge/plan.md +0 -121
  114. package/doc/plans/improve-knowledge/rollout.md +0 -110
  115. package/doc/plans/meta-tools/README.md +0 -110
  116. package/doc/plans/meta-tools/analysis.md +0 -272
  117. package/doc/plans/meta-tools/catalog.md +0 -435
  118. package/doc/plans/meta-tools/notes.md +0 -192
  119. package/doc/plans/meta-tools/rollout.md +0 -368
  120. package/doc/plans/prompts/README.md +0 -96
  121. package/doc/plans/prompts/user-prompt-ideas.md +0 -147
  122. package/doc/plans/tool-consolidation/notes.md +0 -28
  123. package/doc/plans/tool-consolidation/plan.md +0 -169
  124. package/doc/plans/tool-consolidation/rollout.md +0 -104
  125. package/doc/plans/vice-backend-switch/plan.md +0 -486
  126. package/doc/plans/vice-backend-switch-2/plan.md +0 -48
  127. package/doc/plans/vice-support/notes.md +0 -63
  128. package/doc/plans/vice-support/plan.md +0 -88
  129. package/doc/plans/vice-support/rollout.md +0 -99
  130. package/doc/reviews/audit.md +0 -143
  131. package/doc/reviews/hardening/01-fable/prompt-handover.md +0 -125
  132. package/doc/reviews/hardening/01-fable/prompt.md +0 -98
  133. package/doc/reviews/hardening/01-fable/review-prompt.md +0 -468
  134. package/doc/reviews/hardening/01-fable/review.md +0 -1234
  135. package/doc/reviews/vice-support-review.md +0 -185
  136. package/scripts/compare-rest/rest-compare-2025-11-01T00-25-21-830Z.json +0 -2513
package/mcp.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "c64bridge",
3
- "version": "1.0.6",
3
+ "version": "1.1.0",
4
4
  "description": "Stdio-based Model Context Protocol (MCP) server that bridges Commodore 64 Ultimate hardware with AI agents.",
5
5
  "entry": "dist/index.js",
6
6
  "type": "stdio",
@@ -36,6 +36,26 @@
36
36
  "description": "Path to configuration JSON",
37
37
  "default": "~/.c64bridge.json"
38
38
  },
39
+ "C64BRIDGE_ALLOWED_DIRS": {
40
+ "description": "Additional directories allowed for host file paths in tool arguments, separated by the path delimiter; * disables the check",
41
+ "default": ""
42
+ },
43
+ "C64BRIDGE_ACCESS": {
44
+ "description": "Access mode limiting which operations may be called (full, no-destructive, read-only)",
45
+ "default": "full"
46
+ },
47
+ "C64BRIDGE_HTTP_TOKEN": {
48
+ "description": "Bearer token required by the optional HTTP transport (--http)",
49
+ "default": ""
50
+ },
51
+ "C64BRIDGE_DIAGNOSTICS_KEEP": {
52
+ "description": "Number of diagnostics files kept at startup; files older than 30 days are always removed",
53
+ "default": "200"
54
+ },
55
+ "C64BRIDGE_VERBOSE_SCHEMA": {
56
+ "description": "Set to 1 to include full per-operation schemas in tools/list",
57
+ "default": ""
58
+ },
39
59
  "C64_MODE": {
40
60
  "description": "Select active backend (c64u/U64 hardware, u2/U2-family cartridge, or vice emulator)",
41
61
  "default": "c64u"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "c64bridge",
3
- "version": "1.0.6",
3
+ "version": "1.1.0",
4
4
  "mcpName": "io.github.chrisgleissner/c64bridge",
5
5
  "description": "C64 Bridge MCP server for Commodore 64 Ultimate hardware and VICE emulator control",
6
6
  "repository": {
@@ -23,6 +23,7 @@
23
23
  "mcp:node": "node dist/mcp-server.js",
24
24
  "mcp:build": "npm run build && node dist/mcp-server.js",
25
25
  "build": "VICE_VISIBLE=false node scripts/prebuild.mjs && VICE_VISIBLE=false tsc -p tsconfig.json && VICE_VISIBLE=false node scripts/postbuild.mjs && VICE_VISIBLE=false node scripts/invoke-bun.mjs scripts/update-readme.ts && VICE_VISIBLE=false node scripts/invoke-bun.mjs scripts/generate-mcp-interface.ts",
26
+ "prepack": "npm run build",
26
27
  "lint": "npm run check",
27
28
  "test": "node scripts/invoke-bun.mjs scripts/run-tests.ts",
28
29
  "test:node": "node scripts/run-tests.mjs",
@@ -71,10 +72,8 @@
71
72
  "@modelcontextprotocol/sdk": "^1.32.1",
72
73
  "axios": "^1.20.0",
73
74
  "date-fns": "^4.4.0",
74
- "fastify": "^5.12.5",
75
75
  "jimp": "^1.6.1",
76
- "meyda": "^5.6.3",
77
- "octokit": "^5.0.5",
76
+ "meyda": "5.6.2",
78
77
  "pitchfinder": "^2.3.4",
79
78
  "reflect-metadata": "^0.2.2",
80
79
  "yaml": "^2.9.0"
@@ -82,8 +81,13 @@
82
81
  "optionalDependencies": {
83
82
  "naudiodon": "^2.3.6"
84
83
  },
84
+ "overrides": {
85
+ "zod": "^4.6.6",
86
+ "hono": "^4.13.16"
87
+ },
85
88
  "devDependencies": {
86
89
  "@types/node": "^25.9.5",
90
+ "octokit": "^5.0.5",
87
91
  "tsx": "^4.23.11",
88
92
  "typescript": "^6.0.3"
89
93
  },
@@ -95,11 +99,18 @@
95
99
  "README.md",
96
100
  "mcp/**",
97
101
  "dist/**",
102
+ "!dist/.tsbuildinfo",
98
103
  "data/**",
99
- "doc/**",
104
+ "doc/developer.md",
105
+ "doc/troubleshooting-mcp.md",
106
+ "doc/c64u/**",
107
+ "doc/u2/**",
108
+ "doc/vice/**",
109
+ "doc/img/**",
100
110
  "scripts/**",
111
+ "!scripts/compare-rest/**",
101
112
  "generated/**/*.js",
102
- ".github/prompts/**"
103
- ],
104
- "prepack": "npm run build"
113
+ ".github/prompts/**",
114
+ ".github/skills/**"
115
+ ]
105
116
  }
@@ -88,6 +88,12 @@ function createInitialState() {
88
88
  networkPassword: null,
89
89
  forcedErrors: null,
90
90
  forcedErrorsUrlMatch: null,
91
+ // Test hooks for request serialisation: every request is counted while it
92
+ // is being served, optionally delayed so overlap becomes observable.
93
+ responseDelayMs: 0,
94
+ inFlight: 0,
95
+ maxInFlight: 0,
96
+ requestOrder: [],
91
97
  hangOnMachineInputProbe: false,
92
98
  lastPrg: null,
93
99
  lastLoadedPrg: null,
@@ -405,6 +411,21 @@ export async function startMockC64Server(options = {}) {
405
411
  // Track last request metadata
406
412
  state.lastRequest = { method, url, headers: req.headers };
407
413
 
414
+ state.inFlight += 1;
415
+ state.maxInFlight = Math.max(state.maxInFlight, state.inFlight);
416
+ if (state.requestOrder.length < 10_000) state.requestOrder.push(`${method} ${url}`);
417
+ let released = false;
418
+ const releaseInFlight = () => {
419
+ if (!released) {
420
+ released = true;
421
+ state.inFlight -= 1;
422
+ }
423
+ };
424
+ res.once("close", releaseInFlight);
425
+ if (state.responseDelayMs > 0) {
426
+ await new Promise((resolve) => setTimeout(resolve, state.responseDelayMs));
427
+ }
428
+
408
429
  if (state.networkPassword && req.headers["x-password"] !== state.networkPassword) {
409
430
  sendJson(res, { message: "Forbidden", errors: ["invalid network password"] }, 403);
410
431
  return;
@@ -814,6 +835,29 @@ export async function startMockC64Server(options = {}) {
814
835
  }
815
836
  }
816
837
 
838
+ const categoryFlashRoute = /^\/v1\/configs\/([^/:]+):(load_from_flash|save_to_flash|reset_to_default)(?:\?|$)/.exec(url);
839
+ if (categoryFlashRoute && method === "PUT") {
840
+ const requested = decodeURIComponent(categoryFlashRoute[1]);
841
+ const action = categoryFlashRoute[2];
842
+ const category = Object.keys(state.configs).find((name) => name.toLowerCase() === requested.toLowerCase());
843
+ if (!category) {
844
+ sendJson(res, { errors: [`Category ${requested} not found`] }, 404);
845
+ return;
846
+ }
847
+ const clone = (value) => JSON.parse(JSON.stringify(value));
848
+ if (action === "load_from_flash") {
849
+ if (state.flashSnapshot?.[category]) state.configs[category] = clone(state.flashSnapshot[category]);
850
+ } else if (action === "save_to_flash") {
851
+ state.flashSnapshot = state.flashSnapshot ?? {};
852
+ state.flashSnapshot[category] = clone(state.configs[category]);
853
+ } else {
854
+ state.configs[category] = clone(createDefaultConfigs()[category] ?? {});
855
+ }
856
+ state.lastConfigAction = { action, category };
857
+ sendJson(res, { result: action, category });
858
+ return;
859
+ }
860
+
817
861
  if (url.startsWith("/v1/configs/")) {
818
862
  const routeUrl = new URL(url, "http://mock.local");
819
863
  const segments = routeUrl.pathname.split("/").filter(Boolean).slice(2).map((segment) => decodeURIComponent(segment)); // remove v1 + configs
@@ -107,6 +107,32 @@ export async function main() {
107
107
 
108
108
  const summary = summariseReport(finalReport);
109
109
  console.log(JSON.stringify({ lines: summary }, null, 2));
110
+
111
+ // The merged report only carries line data, so only the line threshold can be enforced here.
112
+ const minimum = resolveLineThreshold(coverageConfig, process.env);
113
+ try {
114
+ enforceLineThreshold(summary, minimum);
115
+ } catch (error) {
116
+ console.error(error instanceof Error ? error.message : String(error));
117
+ process.exitCode = 1;
118
+ }
119
+ }
120
+
121
+ /** Minimum line coverage in percent: C64BRIDGE_COVERAGE_MIN_LINES overrides `lines` from .c8rc.json. */
122
+ export function resolveLineThreshold(config, env = process.env) {
123
+ const fromEnv = Number(env.C64BRIDGE_COVERAGE_MIN_LINES ?? "");
124
+ if (env.C64BRIDGE_COVERAGE_MIN_LINES !== undefined && env.C64BRIDGE_COVERAGE_MIN_LINES !== "" && Number.isFinite(fromEnv)) {
125
+ return fromEnv;
126
+ }
127
+ const fromConfig = Number(config?.lines);
128
+ return Number.isFinite(fromConfig) ? fromConfig : 0;
129
+ }
130
+
131
+ export function enforceLineThreshold(summary, minimum) {
132
+ const pct = Number(summary.pct);
133
+ if (!(pct >= minimum)) {
134
+ throw new Error(`Line coverage ${summary.pct}% is below the required ${minimum}%`);
135
+ }
110
136
  }
111
137
 
112
138
  export function buildCoverageBatches(testFiles, extraTests, env = process.env) {
@@ -327,7 +353,7 @@ function serializeReport(report) {
327
353
  return `${lines.join("\n")}\n`;
328
354
  }
329
355
 
330
- function summariseReport(report) {
356
+ export function summariseReport(report) {
331
357
  let covered = 0;
332
358
  let total = 0;
333
359
  for (const record of report.values()) {
@@ -28,7 +28,8 @@ case "$MODE" in
28
28
  fi
29
29
  (
30
30
  cd "$PACKAGE_DIR" || exit 1
31
- timeout --signal=TERM "$DURATION" node dist/index.js
31
+ # The server exits when stdin closes, so keep stdin open until the timeout ends the run.
32
+ sleep $((DURATION + 1)) | timeout --signal=TERM "$DURATION" node dist/index.js
32
33
  ) 2>&1 | tee "$LOGFILE"
33
34
  STATUS=${PIPESTATUS[0]}
34
35
  ;;
@@ -43,7 +44,7 @@ case "$MODE" in
43
44
  fi
44
45
  (
45
46
  cd "$PACKAGE_DIR" || exit 1
46
- timeout --signal=TERM "$DURATION" node dist/index.js
47
+ sleep $((DURATION + 1)) | timeout --signal=TERM "$DURATION" node dist/index.js
47
48
  ) 2>&1 | tee "$LOGFILE"
48
49
  STATUS=${PIPESTATUS[0]}
49
50
  ;;
package/scripts/start.mjs CHANGED
@@ -79,8 +79,30 @@ async function runWithBun(entryPath) {
79
79
  },
80
80
  });
81
81
 
82
- child.on('error', reject);
82
+ // Forward termination signals so the server child does not outlive the launcher.
83
+ const forwardedSignals = ['SIGTERM', 'SIGINT', 'SIGHUP'];
84
+ const forwarders = new Map();
85
+ for (const name of forwardedSignals) {
86
+ const forward = () => {
87
+ try {
88
+ child.kill(name);
89
+ } catch {}
90
+ };
91
+ forwarders.set(name, forward);
92
+ process.on(name, forward);
93
+ }
94
+ const removeForwarders = () => {
95
+ for (const [name, forward] of forwarders) {
96
+ process.removeListener(name, forward);
97
+ }
98
+ };
99
+
100
+ child.on('error', (error) => {
101
+ removeForwarders();
102
+ reject(error);
103
+ });
83
104
  child.on('exit', (code, signal) => {
105
+ removeForwarders();
84
106
  if (signal) {
85
107
  try {
86
108
  process.kill(process.pid, signal);
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.6",
11
+ "version": "1.1.0",
12
12
  "packages": [
13
13
  {
14
14
  "registryType": "npm",
15
15
  "identifier": "c64bridge",
16
- "version": "1.0.6",
16
+ "version": "1.1.0",
17
17
  "transport": {
18
18
  "type": "stdio"
19
19
  },
@@ -36,13 +36,47 @@
36
36
  "format": "filepath",
37
37
  "default": "~/.c64bridge.json"
38
38
  },
39
+ {
40
+ "name": "C64BRIDGE_ALLOWED_DIRS",
41
+ "description": "Additional directories allowed for host file paths in tool arguments, separated by the path delimiter; * disables the check.",
42
+ "format": "string"
43
+ },
44
+ {
45
+ "name": "C64BRIDGE_ACCESS",
46
+ "description": "Access mode limiting which operations may be called.",
47
+ "format": "string",
48
+ "default": "full",
49
+ "choices": [
50
+ "full",
51
+ "no-destructive",
52
+ "read-only"
53
+ ]
54
+ },
55
+ {
56
+ "name": "C64BRIDGE_HTTP_TOKEN",
57
+ "description": "Bearer token required by the optional HTTP transport.",
58
+ "format": "string",
59
+ "isSecret": true
60
+ },
61
+ {
62
+ "name": "C64BRIDGE_DIAGNOSTICS_KEEP",
63
+ "description": "Number of diagnostics files kept at startup (files older than 30 days are always removed).",
64
+ "format": "number",
65
+ "default": "200"
66
+ },
67
+ {
68
+ "name": "C64BRIDGE_VERBOSE_SCHEMA",
69
+ "description": "Set to 1 to include full per-operation schemas in tools/list.",
70
+ "format": "string"
71
+ },
39
72
  {
40
73
  "name": "C64_MODE",
41
- "description": "Active backend.",
74
+ "description": "Active backend (c64u/U64 hardware, u2/U2-family cartridge, or vice emulator).",
42
75
  "format": "string",
43
76
  "default": "c64u",
44
77
  "choices": [
45
78
  "c64u",
79
+ "u2",
46
80
  "vice"
47
81
  ]
48
82
  },
@@ -64,6 +98,22 @@
64
98
  "format": "string",
65
99
  "isSecret": true
66
100
  },
101
+ {
102
+ "name": "U2_HOST",
103
+ "description": "U2/U2+/U2+L host name or IP address when C64_MODE=u2.",
104
+ "format": "string"
105
+ },
106
+ {
107
+ "name": "U2_PORT",
108
+ "description": "U2-family REST port when C64_MODE=u2.",
109
+ "format": "number"
110
+ },
111
+ {
112
+ "name": "U2_PASSWORD",
113
+ "description": "U2-family network password sent as X-Password.",
114
+ "format": "string",
115
+ "isSecret": true
116
+ },
67
117
  {
68
118
  "name": "VICE_BINARY",
69
119
  "description": "VICE emulator binary to launch.",