c64bridge 1.0.7 → 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 (132) 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 +69 -14
  16. package/data/assembly/assembly-spec.md +2 -0
  17. package/dist/c64Client.js +238 -60
  18. package/dist/config.js +48 -26
  19. package/dist/device.js +92 -37
  20. package/dist/diagnostics.js +62 -2
  21. package/dist/hostPaths.js +128 -0
  22. package/dist/loggingHttpClient.js +59 -1
  23. package/dist/mcp-server.js +409 -286
  24. package/dist/numericArg.js +55 -0
  25. package/dist/petscii.js +13 -1
  26. package/dist/platform.js +12 -3
  27. package/dist/prompts/registry.js +6 -1
  28. package/dist/tools/access.js +66 -0
  29. package/dist/tools/debug.js +6 -0
  30. package/dist/tools/developer.js +67 -34
  31. package/dist/tools/errors.js +54 -3
  32. package/dist/tools/input.js +23 -6
  33. package/dist/tools/machineControl.js +112 -20
  34. package/dist/tools/memory.js +135 -30
  35. package/dist/tools/meta/artifacts.js +5 -5
  36. package/dist/tools/meta/background.js +216 -54
  37. package/dist/tools/meta/compilation.js +6 -5
  38. package/dist/tools/meta/config.js +15 -6
  39. package/dist/tools/meta/filesystem.js +16 -14
  40. package/dist/tools/meta/graphics.js +48 -20
  41. package/dist/tools/meta/memory.js +45 -19
  42. package/dist/tools/meta/program.js +10 -7
  43. package/dist/tools/meta/screen.js +116 -25
  44. package/dist/tools/pauseGuard.js +23 -0
  45. package/dist/tools/pollValidator.js +53 -8
  46. package/dist/tools/programRunners.js +62 -18
  47. package/dist/tools/registry/batch.js +16 -4
  48. package/dist/tools/registry/config.js +9 -2
  49. package/dist/tools/registry/disk.js +12 -2
  50. package/dist/tools/registry/drive.js +3 -0
  51. package/dist/tools/registry/extract.js +3 -0
  52. package/dist/tools/registry/graphics.js +59 -6
  53. package/dist/tools/registry/index.js +17 -1
  54. package/dist/tools/registry/memory.js +11 -1
  55. package/dist/tools/registry/platform.js +4 -1
  56. package/dist/tools/registry/printer.js +2 -1
  57. package/dist/tools/registry/program.js +5 -1
  58. package/dist/tools/registry/rag.js +1 -0
  59. package/dist/tools/registry/sound.js +12 -1
  60. package/dist/tools/registry/stream.js +2 -1
  61. package/dist/tools/registry/system.js +8 -1
  62. package/dist/tools/schema.js +7 -7
  63. package/dist/tools/streaming.js +34 -4
  64. package/dist/tools/translation/assembler.js +247 -24
  65. package/dist/tools/translation/basicDetokenizer.js +80 -0
  66. package/dist/tools/translation/basicTokenizer.js +8 -3
  67. package/dist/tools/translation/disassembler.js +14 -0
  68. package/dist/tools/types.js +258 -7
  69. package/dist/tools/vice.js +5 -16
  70. package/dist/vicBitmap.js +1 -1
  71. package/dist/vice/mockServer.js +3 -0
  72. package/dist/vice/process.js +19 -0
  73. package/dist/vice/resourcePolicy.js +36 -0
  74. package/dist/vice/viceClient.js +22 -5
  75. package/doc/vice/support-matrix.md +17 -11
  76. package/mcp/schemas/c64_batch.schema.json +2 -1
  77. package/mcp/schemas/c64_config.schema.json +25 -475
  78. package/mcp/schemas/c64_debug.schema.json +3 -602
  79. package/mcp/schemas/c64_disk.schema.json +17 -344
  80. package/mcp/schemas/c64_drive.schema.json +2 -157
  81. package/mcp/schemas/c64_extract.schema.json +10 -364
  82. package/mcp/schemas/c64_graphics.schema.json +19 -426
  83. package/mcp/schemas/c64_input.schema.json +5 -318
  84. package/mcp/schemas/c64_memory.schema.json +23 -491
  85. package/mcp/schemas/c64_printer.schema.json +3 -258
  86. package/mcp/schemas/c64_program.schema.json +16 -491
  87. package/mcp/schemas/c64_rag.schema.json +1 -81
  88. package/mcp/schemas/c64_select_backend.schema.json +2 -36
  89. package/mcp/schemas/c64_sound.schema.json +20 -822
  90. package/mcp/schemas/c64_stream.schema.json +4 -75
  91. package/mcp/schemas/c64_system.schema.json +28 -348
  92. package/mcp/schemas/c64_vice.schema.json +2 -71
  93. package/mcp/server.json +1 -1
  94. package/mcp/tools.json +1983 -6910
  95. package/mcp.json +21 -1
  96. package/package.json +16 -8
  97. package/scripts/mockC64Server.mjs +44 -0
  98. package/scripts/run-coverage.mjs +27 -1
  99. package/scripts/smoke-run.sh +3 -2
  100. package/scripts/start.mjs +23 -1
  101. package/server.json +35 -2
  102. package/dist/.tsbuildinfo +0 -1
  103. package/doc/agent-state-spec.md +0 -243
  104. package/doc/audit/secret-handling.md +0 -61
  105. package/doc/plans/bun/baseline-metrics.md +0 -16
  106. package/doc/plans/bun/migration-checklist.md +0 -55
  107. package/doc/plans/bun/migration.md +0 -83
  108. package/doc/plans/improve-knowledge/notes.md +0 -43
  109. package/doc/plans/improve-knowledge/plan.md +0 -121
  110. package/doc/plans/improve-knowledge/rollout.md +0 -110
  111. package/doc/plans/meta-tools/README.md +0 -110
  112. package/doc/plans/meta-tools/analysis.md +0 -272
  113. package/doc/plans/meta-tools/catalog.md +0 -435
  114. package/doc/plans/meta-tools/notes.md +0 -192
  115. package/doc/plans/meta-tools/rollout.md +0 -368
  116. package/doc/plans/prompts/README.md +0 -96
  117. package/doc/plans/prompts/user-prompt-ideas.md +0 -147
  118. package/doc/plans/tool-consolidation/notes.md +0 -28
  119. package/doc/plans/tool-consolidation/plan.md +0 -169
  120. package/doc/plans/tool-consolidation/rollout.md +0 -104
  121. package/doc/plans/vice-backend-switch/plan.md +0 -486
  122. package/doc/plans/vice-backend-switch-2/plan.md +0 -48
  123. package/doc/plans/vice-support/notes.md +0 -63
  124. package/doc/plans/vice-support/plan.md +0 -88
  125. package/doc/plans/vice-support/rollout.md +0 -99
  126. package/doc/reviews/audit.md +0 -143
  127. package/doc/reviews/hardening/01-fable/prompt-handover.md +0 -125
  128. package/doc/reviews/hardening/01-fable/prompt.md +0 -98
  129. package/doc/reviews/hardening/01-fable/review-prompt.md +0 -468
  130. package/doc/reviews/hardening/01-fable/review.md +0 -1234
  131. package/doc/reviews/vice-support-review.md +0 -185
  132. 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.7",
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.7",
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
76
  "meyda": "5.6.2",
77
- "octokit": "^5.0.5",
78
77
  "pitchfinder": "^2.3.4",
79
78
  "reflect-metadata": "^0.2.2",
80
79
  "yaml": "^2.9.0"
@@ -83,10 +82,12 @@
83
82
  "naudiodon": "^2.3.6"
84
83
  },
85
84
  "overrides": {
86
- "zod": "^4.6.6"
85
+ "zod": "^4.6.6",
86
+ "hono": "^4.13.16"
87
87
  },
88
88
  "devDependencies": {
89
89
  "@types/node": "^25.9.5",
90
+ "octokit": "^5.0.5",
90
91
  "tsx": "^4.23.11",
91
92
  "typescript": "^6.0.3"
92
93
  },
@@ -98,11 +99,18 @@
98
99
  "README.md",
99
100
  "mcp/**",
100
101
  "dist/**",
102
+ "!dist/.tsbuildinfo",
101
103
  "data/**",
102
- "doc/**",
104
+ "doc/developer.md",
105
+ "doc/troubleshooting-mcp.md",
106
+ "doc/c64u/**",
107
+ "doc/u2/**",
108
+ "doc/vice/**",
109
+ "doc/img/**",
103
110
  "scripts/**",
111
+ "!scripts/compare-rest/**",
104
112
  "generated/**/*.js",
105
- ".github/prompts/**"
106
- ],
107
- "prepack": "npm run build"
113
+ ".github/prompts/**",
114
+ ".github/skills/**"
115
+ ]
108
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.7",
11
+ "version": "1.1.0",
12
12
  "packages": [
13
13
  {
14
14
  "registryType": "npm",
15
15
  "identifier": "c64bridge",
16
- "version": "1.0.7",
16
+ "version": "1.1.0",
17
17
  "transport": {
18
18
  "type": "stdio"
19
19
  },
@@ -36,6 +36,39 @@
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
74
  "description": "Active backend (c64u/U64 hardware, u2/U2-family cartridge, or vice emulator).",